Main-window UI refresh (Phases 0–6) - #18
Merged
Merged
Conversation
Design spec for the main-window refresh: elevated titled panel framing (B+C hybrid), Excel-style zebra striping with a defined row-state precedence, Option-B metadata pane (selected file/folder nested under the tree context), and the intuitiveness wins (unified selection, sync tooltips/legend, live status segments, empty states, toolbar grouping + density, search wiring). Token-first; rides the existing URL/navigate render model. Includes the eight resolved questions from code review and the standalone HTML mockups approved during brainstorming. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1 of the main-window UI refresh. Adds the surface tokens the refresh consumes (zebra stripe, row-selected fill + accent bar, pane-header tint, new-file highlight) to design.py and emits them from build_root_css; DESIGN.md §02 + §07 kept in lock-step. Also fixes four CSS vars that shipping components already referenced but the :root block never declared, so they silently resolved to empty and were dropped -- most visibly the new-file row highlight (file_list.py) was invisible, plus the breadcrumb link/bar tint and the relay-badge text colour: --color-highlight, --color-link, --color-bg-subtle, --color-on-info. The spec named three; a var-audit during implementation found the fourth (--color-on-info, metadata_pane.py:130). A new test_theme.py regression guard scans the ui/ tree for any var(--...) referenced but not declared, so this bug class can't reappear. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2 of the main-window UI refresh -- pure, NiceGUI-free logic landed and tested before any component consumes it (Phase 4). - file_list.row_background(): resolver for the row-state precedence Selected > New-file > Tombstone > Zebra. Tombstone contributes no fill (suppresses the zebra stripe) but its dim/italic decoration is additive, so a selected tombstone keeps both treatments. Zebra parity is index-based, not CSS :nth-child, so interleaved state rows never shift the stripe. - sync_status_icon: add strict=False tolerant mode returning neutral props (muted dash) for None/unknown status; strict=True default preserves the original raise-on-unknown contract for existing callers. - sync_rollup.sync_rollup(): worst-of folder rollup over SyncStatus values (failed > blocked_by_validation > pending > synced > cleaned); None/unknown ignored, empty -> None. UI-only ordering (the enum is schema-committed and deliberately order-free). 25 new unit tests; ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3 of the main-window UI refresh (spec §4.1, the approved B+C hybrid). - New components/framed_pane.py: a context-manager helper rendering an elevated card (surface fill, hairline border, radius-md, shadow-sm) topped by an uppercase title strip with an optional right-aligned count pill, then a scrollable body it yields -- so children of the with-block nest inside the body, not the card root. Pure style-fragment helpers (card_style / header_style / body_style) stay unit-testable without NiceGUI; each ends with ';' so callers can append overrides, and every token carries a literal fallback since register_theme isn't on every route. - main.py: wrap Explorer, Files (with a count pill -- '0 items' for a selected-but-empty folder, suppressed when nothing is selected), and the footer in the framed shell; add a gap between splitter panels. The metadata pane gets the card frame via card_style() but keeps its existing tabs in place of a title strip (approved default), appending overflow:auto so the tab panels scroll within the frame. All existing data-testids preserved. 17 unit tests (incl. a body-nesting invariant assertion); ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 4 (Option B): single-clicking a file/folder row in the Files pane highlights it and shows its metadata as a sub-card beneath the right pane's node context -- all carried on the existing URL/navigate model (?file=/?q=/?density=), no new reactive paradigm. - mount: resolve ?file= against the in-memory folder feed (_build_selected_file / _build_selected_folder, one-level scan + sync_rollup, degrade-don't-raise); thread file/q/density through _main and _build_main_state; add on_select_file + per-folder refresh (_refresh_selected_folder, OQ-1/A) that also records the coordinator tick. - file_list: selectable rows (files AND folders) styled via the Phase-2 row_background resolver with a tolerant sync-status icon; FileListState.selected_path. - metadata_pane: SELECTED FILE / SELECTED FOLDER sub-card that appends beneath the node dispatch (and the empty state) via a fall-through chain; pure selected_file_card_title. - framed_pane: optional header_extra slot (hosts the Files refresh button). ruff + ruff format + mypy clean; tests/unit/ui 603 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live-demo refinements on top of the Phase 4 selection work: - theme: call register_theme() app-wide (shared=True) from mount_ui so the :root design tokens finally reach the browser -- the grey app canvas and resolved var(--color-*) tokens were dead before (register_theme was never wired). file_list.row_background gains literal token fallbacks so the selection fill / accent bar / zebra still render pre-theme. - main: the metadata pane now floats as an overlay popover over the right of the Files pane (was a docked column); the vertical "Metadata" tab toggles it (right_pane param) and sits on the popover's left edge. NB: diverges from the approved spec's docked Option B -- a deliberate UX change. - main: the Files header groups its count + per-folder refresh on the left (framed_pane gains count_left). - metadata_pane: kv values overflow instead of wrapping (long paths stay on one line; the pane scrolls horizontally). - e2e harness: _test_app /main wires Phase 4 selection + register_theme so the popover/selection demo mirrors production (a thin Phase 6 slice). Unit gate green: ruff + ruff format + mypy clean; tests/unit/ui 603 passed. e2e not yet updated for the popover/theme (Phase 6 follow-up). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design for replacing the single hardcoded --add-test-samples equipment entry with a list-driven generator that seeds a full equipment/project/run tree, each folder carrying real metadata via the production file producers. Captures the resolved decisions: code-embedded Pydantic SAMPLES list, shared metadata-assembly refactor of creation.py, dev-command wipe vs tray first-launch-only seeding, and a dataset spanning sync-status badges and payload files for UI coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…croll The Explorer / Files / metadata panes sized to their content and grew the page (NiceGUI's q-layout / q-page chain is min-height-driven, so nothing was pinned to the viewport). Give that chain a definite viewport height and flex-fill down to the splitter -- page-scoped ui.query overrides on .q-page-container / .q-page / .nicegui-content, and the splitter switched from h-full to flex:1 / min-height:0. The three panes now fill the height between the fixed header and footer and scroll within their own overflow:auto bodies instead of the whole page scrolling. Verified live: page no longer scrolls; the Explorer pane scrolls internally (607px of tree inside a 454px body). Unit gate green (603 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- fixed graphify outputs being commited
Captures what shipped (Phases 1-4 + the post-demo popover/theme/full-height batch, with commit hashes), the two deliberate divergences from the approved design spec (metadata popover instead of the docked Option B; register_theme now wired app-wide), and the remaining work: spec reconciliation, Phase 5 polish (reconciled with the popover), Phase 6 e2e + final verification (with the specific DOM break points to fix), cleanup/tech-debt, and risks (notably the app-wide visual regression from finally injecting the theme). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the previously-inert /main search box (OQ-2): the query now reaches TreeFilters.search via chip_state_to_tree_filters, the input carries its value + clearable + a 300ms Quasar debounce, and on_change re-navigates ?q= through the existing URL/navigate model (mount._on_search + the e2e harness mirror). A result-count / no-matches affordance, sourced from the same build_nodes the tree renders via the new pure count_search_results helper, sits under the box while a query is active. Add a shared empty_state(icon, message, testid) placeholder (centred icon + one-line hint) and route the three bare-label empties through it (select-a-folder, empty-folder, select-a-node), preserving each testid. Group the header toolbar with a vertical divider between the creation actions and the utility actions (button order + testids unchanged). Gate: ruff check + ruff format --check + mypy src clean; tests/unit/ui 609 passed (search-count + empty_state unit tests added); code-reviewer pass found no high-confidence issues. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Validator / LIMS / Staging footer segments were hardcoded NORMAL. Add a pure derive_footer_segment_states(problems_count_hard, lims_reachable, staging_pending) helper (Frontend §3.5.5): Validator → WARNING on a hard finding, LIMS → DANGER when the endpoint is unreachable, Staging → WARNING when runs are pending (no cheap cached count today, so the caller passes 0 and it stays NORMAL until a cached signal exists). mount._build_main_state sources problems_count_hard (audit rollup) + deps.lims_reachable and writes the derived states onto three new MainPageState fields the footer reads. Sync already derives live from the operations counts. Gate: ruff + mypy clean; tests/unit/ui 614 passed (5 derivation tests added). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tick the four Phase-5 items shipped this session (commits 01edee7, 1bb3c8d) in the remaining-work spec; split density out of the toolbar item with its open UX question; flag the remaining items (density, unified selection, sync tooltips/legend) as the visual/theme-CSS work that wants a live pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…egend Finishes the remaining Phase-5 items (all live-verified via Playwright DOM inspection on the seeded app): - File-list density (§4.8): a compact/comfortable toggle in the Files header rides ?density=; density_card_class() puts .exlab-density-compact on the Files card and a scoped theme rule tightens only the file-row vertical padding (--sp-1 vs the default --sp-2 from .p-2). framed_pane gains a card_classes param. on_toggle_density wired through mount + the e2e harness. - Unified tree selection (OQ-6): build_tree seeds Quasar's v-model:selected from ?selected= and a theme rule gives the selected node header the same --color-row-selected fill + 3px --color-row-selected-bar inset as a selected file row. Verified: computed bg #dceaff + inset bar #1b75bc on the selected node only. - Sync tooltips: run rows carry a friendly sync_title (`:title`) — "Data on local disk" / "Cleared -- data on NAS only" — mirroring the local/cloud icon. - Sync-status legend: a "?" popover in the Files header lists every state's icon + meaning, sourced from sync_status_icon.sync_legend_entries() (_STATUS_TO_PROPS, single source of truth). Gate: ruff + mypy clean; tests/unit/ui 623 passed (9 new tests). code-reviewer pass; its one flag (legend menu anchoring) verified a false positive — the `with (button, menu):` form nests the menu in the button (count-pill click does not open it; button click does). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mark the final three Phase-5 items done (commit a31b916), record the live Playwright verification of every Phase-5 item, and note Phase 6 (e2e suite + screenshots) + cleanup as the remaining work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…talog The Phase 3-5 DOM changes didn't break any existing flow (testids were preserved), so the e2e suite was already green. Add the coverage the spec calls for: - New flow 28 (test_flow_28_selection_search_density): single-click file-row selection → ?file= + data-selected + metadata-selected-file sub-card; search result-count + no-matches pill; density toggle → ?density=; sync-status legend popover; the selected tree node's .q-tree__node--selected hook. - Catalog the new operator affordances (file-list-row, toggle-right-pane, main-search, files-density-toggle, files-legend) in ux_catalog.py and regenerate docs/UX_INTERACTIONS.md; the testid-exists-in-source and e2e-covered checks pass. Gate: tests/e2e 70 passed / 3 skipped; unit+integration coverage 91.70% (>= 91%); ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Record that the Phase 3-5 DOM changes broke no flow, flow 28 adds the file-selection + search/density/legend coverage, the CI coverage command hits 91.70%, and the app-wide theme spot-check (welcome/settings/problems) shows no regression. Note the collection-order gotcha (run unit before integration) and that only §D cleanup + §A spec reconciliation remain. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hide (not remove) the per-equipment `stage` sync mode and the staging-PC relay. Every equipment now syncs directly to the NAS (sync_mode="nas"); the staging backend stays dormant and tested for reversibility. No config schema change, no feature flag. UI surfaces gated: - Add-Equipment wizard: drop the sync-mode step (identity -> paths -> review); dormant _render_sync_mode_step kept as the reversibility hook. - Settings: rename "Orchestrator Mode" -> "Workstation" (section id kept, so the setup gate / routing are untouched); remove the staging-root input. - mount.py: remove the /staging route + footer clear-verified wiring and the helpers it orphaned (_build_staging_state, _bulk_clear_verified, _render_unavailable); keep the per-run tree context-menu actions (they serve nas runs) and reword their toasts off "staging". - main.py: remove the footer "Staging" segment + "Clear verified runs". Backend left intact and dormant: orchestrator/ (incl. the quiescence poller, the auto-sync engine for BOTH nas and stage runs), api/routers/staging.py, SyncMode.STAGE, orchestrator.staging_* fields. Tests: skip the staging-driven UI flows with reversible markers citing the spec; update wizard/settings/footer/persist tests; drop mount glue-fn tests for the removed helpers. Backend staging tests stay green, proving dormancy. Add a root CLAUDE.md documenting the hidden-but-present state so future work does not "fix" the missing UI or delete the shared poller. Spec: docs/superpowers/specs/2026-05-29-hide-orchestrator-staging-design.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… cleanup Amend the original main-window design spec to record the two shipped divergences as the approved behaviour: a reconciliation banner up top + inline notes at §3.2 (register_theme wired app-wide), §4.1 (metadata is a floating popover, not a docked card), §4.4 (only the container changed), and §5 (rationale). Record the popover as THE direction (popover-only). In the remaining-work doc, tick §A (done) and §D: decided to KEEP the literal var() fallbacks as a documented headless/cron-render safety net; scratch artifacts (RESUME.md, phase*.png, .playwright-mcp/) deleted; register_theme confirmed single-injection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the single hardcoded `--add-test-samples` equipment literal with a
declarative `SAMPLES` list expanded by a generator that seeds a full demo
tree (equipment -> projects -> runs). Every folder's metadata is written
through the real production producers (ReadmeGenerator, CreationWriter,
EquipmentCacheWriter) so it cannot drift from what the wizard writes.
- sample_data/{spec,generator,__init__}: Pydantic SAMPLES + generator +
generate_samples() facade with a guardrailed destructive wipe.
- controller/metadata_assembly: extract build_readme_context /
build_creation_json (+ moved helpers) shared by the controller and the
seeder; creation.py now delegates (behavior-preserving, parity-tested).
- config/test_bootstrap: shared write_starter_test_config /
bootstrap_test_config (light module; no pystray import) used by both the
tray flag and the new dev command.
- dev/seed: `python -m exlab_wizard.dev.seed` (always wipe + regenerate).
- tray/main: delete the inline TESTRIG literal; seed via the shared
bootstrap on first launch only (never wipes an existing sandbox).
Deterministic (fixed clock; README/creation.json/payload bytes identical
across runs; only equipment.json's writer-stamped timestamps differ).
Seeded equipment roots are base paths so run-walking (quiescence poller /
validator) discovers the tree. Destructive wipe is fenced to the -test
sandbox with strict-subpath containment. Full suite green (2362 passed,
3 skipped); ruff + mypy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(sample-data): list-driven sample data generation for --test
`test_cancel_with_discard_files_removes_partial_dir` races the creation pipeline against a mid-flight cancel (it polls with asyncio.sleep and branches on whether the directory landed first). Under load the race can transiently lose, flaking the suite. Add pytest-rerunfailures to the test extra and mark the test `@pytest.mark.flaky(reruns=3, reruns_delay=0.5)` so a transient timing miss retries instead of failing CI. Verified the marker re-runs on failure (fails twice -> passes on the third attempt). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removing the @ui.page("/staging") block left a single blank line before
the "Helpers" divider; ruff format wants two. No behavioural change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Summary
A visual + interaction refresh of the main window (
/main), landed in six gatedphases. 20 commits since
main: the UI refresh itself plus a few smalldocs/chore commits (a sample-data design spec, a
.gitignorefor Playwrightoutputs, a CI-on-every-PR workflow tweak).
The panes become elevated, titled cards on a grey canvas; the centre file
list gets zebra striping + a defined row-state precedence; selecting a file/folder
shows its metadata nested in the current tree context; and a set of low-cost
intuitiveness wins land on top (unified selection, sync tooltips + legend, live
footer segments, empty states, toolbar grouping + density, search).
Design spec:
docs/superpowers/specs/2026-05-29-main-window-ui-refresh-design.md(reconciled for the shipped divergences); phase log:
…-main-window-ui-refresh-remaining-work.md.What's in it, by phase
vars (
--color-highlight/--color-link/--color-bg-subtle) that weresilently dropping declarations.
row_background(Selected > New > Tombstone > Zebra),tolerant
sync_status_props(strict=False), worst-ofsync_rollup.framed_pane(new) wraps Explorer / Files / metadata /footer; panes pin to the viewport height and scroll internally.
(
data-selected) + a metadata sub-card beneath the node content; per-folderrefresh; tolerant per-file status icons. Metadata floats as a popover over
the Files pane (operator-directed; reconciled in the design doc). Also fixes a
latent bug:
register_themewas never injected by the running app — now wiredapp-wide (
shared=True), so design tokens resolve live on every page (the inlinevar(--x, #literal)fallbacks stay as a headless/cron-render safety net).pill; shared empty-state placeholders; toolbar group divider; live footer
segments (Validator → WARNING on hard findings, LIMS → DANGER when
unreachable); file-list density toggle; unified tree selection (selected
node gets the file-row fill + accent bar); sync tooltips + a legend
popover.
added
flow_28(file selection + search / density / legend) and catalogued thenew affordances. Every Phase-5/6 surface was live-verified via Playwright
(DOM inspection + screenshots), incl. the app-wide theme on welcome/settings/
problems.
Verification
pytest tests/unit tests/integration --cov --cov-fail-under=91→ 91.70%, 2338 passed (the CI command — unit first; e2e runs in a subprocess,
excluded from coverage).
pytest tests/e2e→ 70 passed, 3 skipped (skips pre-existing).ruff check+ruff format --check+mypy srcall clean.flow_06/flow_09are intermittently order-flaky under the shared sessionserver (pass in isolation) — pre-existing, worth a follow-up.
Reviewer note
Run the suite as CI does —
pytest tests/unit tests/integration(unit first).Plain
pytest testscollectsintegrationbeforeunitand pollutes NiceGUI'sglobal auto-index render state, failing the in-process UI render tests. It's a
collection-order constraint, not a product bug.
🤖 Generated with Claude Code