Skip to content

Upgrade#26

Merged
jpdeleon merged 4 commits into
mainfrom
upgrade
May 15, 2026
Merged

Upgrade#26
jpdeleon merged 4 commits into
mainfrom
upgrade

Conversation

@jpdeleon

Copy link
Copy Markdown
Owner

No description provided.

jpdeleon added 4 commits May 15, 2026 15:32
Until now, each of the four templates (index, gallery, compare,
tls_summary) carried its own copy of theme variables, button styles,
theme-toggle CSS+JS, @Keyframes spin, and the .page-loading overlay.
The same blocks had already drifted (e.g. the tls_summary spinner
used .spinner while the others used .pl-spinner — a regression we
had to fix mid-session), and the duplication prevented browsers
from caching CSS/JS across page navigations.

This commit consolidates the truly-shared chrome into two new files
and slims each template to its page-specific styles only.

New files
- quicklook/app/static/css/quicklook.css: theme variables (light +
  dark), * box-sizing, a colour, .btn family, .theme-toggle,
  @Keyframes spin, .page-loading family (overlay + spinner + panel
  text). Variables that are used on only one page (--row-alt,
  --thead-bg, --pill-bg/--pill-fg, --log-bg, etc.) also live here so
  every page shares the same palette.
- quicklook/app/static/js/quicklook.js: applyTheme() function with
  early-init that respects localStorage["ql-theme"] and
  prefers-color-scheme, plus the #themeToggle click handler. Both
  exported globally so per-page scripts can reuse them.

Templates
- All four templates gain
    <link rel="stylesheet" href="{{ url_for('static', filename='css/quicklook.css') }}">
  in <head> and
    <script src="{{ url_for('static', filename='js/quicklook.js') }}"></script>
  before their existing inline <script>.
- Removed the duplicated CSS blocks from each (theme vars, *
  box-sizing, a, .btn family, .theme-toggle, @Keyframes spin,
  .page-loading family).
- Removed the duplicated theme JS from each (applyTheme + init IIFE
  + themeToggle click handler).
- Page-specific overrides kept inline:
  * body max-width per page (960 / 1100 / 1400)
  * index home page keeps a chunkier .btn padding override for the
    primary submit button
  * index .header .theme-toggle is repositioned absolutely
  * tls_summary uses a brighter --hover-bg for the dense table view
    via a body-level variable override
  * tls_summary .page-loading .text small overrides to monospace +
    word-break so long absolute paths wrap cleanly.

Backend
- No Python changes. Flask's default static_folder
  (quicklook/app/static/) already serves the new assets at
  /static/css/quicklook.css and /static/js/quicklook.js.

Verification
- All four pages return 200 via app.test_client().
- Each rendered page contains both 'css/quicklook.css' and
  'js/quicklook.js' references.
- The two static endpoints themselves return 200.

This is Tier 1 of the four-tier GUI plan documented at
~/.claude/plans/what-is-the-cause-vectorized-sutherland.md . Tiers 2
(unified top-bar partial), 3 (TLS Summary frozen Target column +
URL-persisted filters + Reset), and 4 (empty states + Result-panel
fade-in) follow in subsequent commits.
Until now each page had a slightly different navigation pattern:
- home: theme-toggle in the .header and a 3-button footer-nav at
  the bottom
- gallery / compare: a .top-bar-left with Back + Compare + Summary
  buttons + theme-toggle, plus a .controls block on the right
- tls_summary: four buttons inside .top-bar followed by .controls

This commit consolidates the four primary destinations into a single
shared partial so the four pages have an identical nav surface.

New file
- quicklook/app/templates/partials/topbar.html
  Renders a <nav class="topbar"> with Home / Gallery / Compare /
  Summary anchors plus the theme-toggle. The Summary anchor keeps
  id="summaryLink" so existing click handlers (loading overlay)
  still bind. The caller sets `{% set current = 'home'|'gallery'
  |'compare'|'summary' %}` and the matching anchor gets
  aria-current="page" for keyboard / screen-reader semantics.

Shared CSS (quicklook.css)
- New rules:
    .topbar { display: flex; align-items: center; gap: 8px;
              flex-wrap: wrap; margin-bottom: 12px; }
    .topbar a[aria-current="page"] {
      background: var(--primary); color: #fff;
      border-color: var(--primary);
      pointer-events: none; cursor: default;
    }
  The active anchor visually pops as a filled primary button and
  cannot be clicked (it's the current page).

Templates
- gallery.html / compare.html: replaced the bespoke .top-bar-left
  block (Back + middle links + theme-toggle) with
    {% set current = '<page>' %}
    {% include 'partials/topbar.html' %}
- tls_summary.html: replaced its four-button .top-bar with the
  partial; the search + count + (formerly) theme-toggle in .controls
  now live in a leaner second .top-bar row.
- index.html:
  * Removed the theme-toggle from .header (it now lives in the
    topbar above).
  * Removed the entire bottom footer-nav (Gallery / Compare / TLS
    Summary links — same destinations as the topbar).
  * Narrowed the chunky .btn override from `.btn { padding: 10px
    24px }` to `.btn-row .btn:not(.btn-sm), .result-actions .btn`
    so the topbar's small buttons aren't accidentally chunky.
  * Updated the inline loading-overlay click handler to look up
    `#summaryLink` (the partial's id) instead of the old
    `#tlsSummaryLink`.

Verification
- All four pages return 200 via app.test_client().
- Each rendered page contains `class="topbar"` and the expected
  aria-current="page" attribute on the correct anchor.

Next up: Tier 3 (TLS Summary frozen Target column + URL-persisted
filters + Reset) and Tier 4 (empty states + Result-panel fade-in).
…refactor)

Tier 3 — TLS Summary table improvements
- Frozen Target column. Added class="tls-table" to the existing
  table and a sticky position rule on the first <th>/<td>. The
  Target name now stays visible while scrolling horizontally
  through the 18-column table, with a thin right-edge shadow to
  cue the freeze. Hover and alternating-row background are also
  respected on the sticky column.
- URL-persisted column filters. Filter inputs are now mirrored
  into the URL as ?f_<key>=<expr> where <key> is the matching
  <th>'s data-key. On page load, those params are restored into
  the inputs and applyFilters() runs once with updateUrl=false to
  avoid an immediate URL rewrite. Filters change via input events
  rewrite the URL via history.replaceState (no reload). This
  makes a filtered view bookmarkable / shareable.
- "Reset" button next to the global search. Clears the search
  input, every per-column filter, and (transitively) every f_*
  URL param via applyFilters(). Reuses the existing helper — no
  duplicated reset logic.

Tier 4 — Empty states and Result-panel fade-in
- New shared CSS class .empty-state (dashed-border card with
  muted text, themed for light + dark) and shared .result-section
  fade-in rule (display + opacity transition over 300 ms).
- Removed the old inline .result-section display rules from
  index.html in favour of the shared one.
- index.html: instead of hiding the entire Recent Results
  section when empty, the section is always shown and an empty-
  state placeholder lives inside it with a hint "submit a target
  above and the rendered thumbnail will appear here." The
  existing updateRecentResults() helper removes the placeholder
  on the first card, and the delete handler restores it when the
  last card is removed.
- gallery.html: replaced the bare .empty-msg paragraph with the
  shared .empty-state look and added a CTA — "run your first
  target" links back home when no images exist; "Clear filter"
  links back to the unfiltered gallery when the empty state is
  due to a search miss.
- tls_summary.html: the existing .empty div became .empty-state
  and now links to the home page so first-time users have a
  starting point.

No backend changes; no schema changes. All four pages still
render 200 via app.test_client(). The Tier 3 frozen-column /
URL-filter changes are scoped to tls_summary.html and don't
affect other pages.
Sector handling (quicklook/tql.py + quicklook/app/templates/index.html)
- Backend: when the user-supplied sector isn't in self.all_sectors,
  TessQuickLook.get_lc() now logs a warning and falls back to the
  latest available sector instead of raising NoDataError. Previously
  a stale localStorage value (e.g. sector=98 carried over from a
  different target) killed the run.
- GUI: live "Available: <chips>" hint appears under the Sector input
  after the user blurs the Target Name or changes the Pipeline. Each
  chip is click-to-fill and the current value is highlighted. Submit
  is blocked with a red toast if the chosen sector isn't in the list
  and the field flashes red, focused.

Exptime handling (quicklook/app/app.py + quicklook/tql.py)
- The GUI's "auto" option submits exptime="" (empty string). The
  pipeline used to treat that as a real filter
  (`tbl["t_exptime"] == ""` -> mask all False), zeroing the search
  result and falsely triggering the TGLC ePSF fallback even when MAST
  had products. Fixed at both layers: app.py coerces the form value
  via `(kwargs.get("exptime") or None)`, and get_lc()'s filter switch
  is now `if kwargs.get("exptime"):` (skips on any falsy value).

Corrupt-cache self-heal (quicklook/tql.py)
- New _download_with_retry helper wraps both lightkurve download
  call sites in get_lc(). On `LightkurveError("Error in reading Data
  product <PATH> of type <TYPE>")` it parses the path with a regex,
  deletes the offending .fits from ~/.lightkurve/cache/, and retries
  once. Crucially the deletion runs on EVERY caught iteration --
  including the last attempt before re-raising -- so a retry that
  re-downloads a still-corrupt copy doesn't leave the bad file on
  disk after the failure. Verified with a synthetic LightkurveError
  in a tmpdir: 2 attempts fire, 2 deletes log, file vanishes.

--each-pipeline (quicklook/cli/ql.py, app.py, utils.py + index.html)
- New helper utils.get_available_pipelines(target_name) parallel to
  get_available_sectors(), returning sorted lowercase pipeline names
  from `lk.search_lightcurve(...).table["provenance_name"]`. Always
  includes "tglc" so the local ePSF fallback is offered even when
  MAST lists no TGLC HLSP.
- CLI: new --each-pipeline flag (mutually exclusive with --each-
  sector). Reuses run_ql_for_sector with sector=-1 and a varying
  pipeline. Same ProcessPoolExecutor parallelism, --jobs / --cores /
  oversubscription warning, and pass/fail summary.
- Flask: GET /available-pipelines and POST /each-pipeline-submit
  mirror the existing sector routes. Job names follow the form
  `{target}_{pipeline}` and sector is forced to -1 server-side.
- GUI: new "Run each available pipeline" checkbox below the Pipeline
  dropdown, persisted in localStorage and reset by Reset-to-Defaults.
  Mutually exclusive with each_sector: ticking one un-ticks (and
  greys out) the other, and disables the redundant dropdown / sector
  inputs while in effect. Form submit branches to the each-pipeline
  flow first, then each-sector, then the standard single submit.

Gallery: restore "job survives page switch" perception
  (quicklook/app/templates/gallery.html)
- Auto-refresh default flipped from ON to OFF. The previous default
  reloaded the page when a running job finished, which surprised
  users who interpreted the reload + missing banner as "job was
  halted" -- when in reality the job had completed server-side. Opt-
  in only now via the existing checkbox + localStorage preference.
- Running-jobs banner is much more prominent: bigger spinner,
  status-run colors (matching the home page's status bar), label
  "Job running:" plus an italic hint "(server-side; clicking the
  name returns to the live log on the home page)". Each job name is
  underlined and links to /?watch=<name> which re-attaches the live
  log on home.

Job Queue: per-row X delete (quicklook/app/app.py + index.html)
- New POST /delete-job/<target>. Refuses running jobs (must cancel
  first). For queued jobs sets the cancel_event so the worker skips
  them once picked up. Pops the entry from the in-memory jobs dict,
  best-effort drops the matching job_history row from SQLite, and
  best-effort unlinks the per-target log file at
  app/static/outputs/logs/<target>.log. Idempotent: POSTing to an
  unknown target returns {"ok": true}.
- GUI: refreshQueue() now appends a x button to each non-running
  queue-item (rightmost). Click confirms, hits /delete-job, toasts
  the result, and refreshes the queue. Reuses the existing
  .delete-btn class for visual consistency with Recent Results.

Pipeline dropdown: include "tasoc"
- ALL_TESS_PIPELINES in tql.py is `["spoc","tasoc"] +
  FULL_FRAME_TESS_PIPELINES`. tasoc had been dropped earlier when
  the dropdown was first wired to ALL_TESS_PIPELINES; this commit
  restores it so the GUI dropdown lists all pipelines the search
  results can produce.
@jpdeleon jpdeleon merged commit 1a837a6 into main May 15, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant