Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.