Files
brain-of-reese/.agents/phases/todo/156_table_ux_polish/00_phase.md
T
ducoterra 966c4b0540
Build and Push Containers / build-and-push-app (push) Successful in 2m45s
Build and Push Containers / build-and-push-db (push) Successful in 19s
phase: 153_document_viewer_context
That last `--co` (collect-only) run was my probe mistake — it shows 34% because no tests execute under collect-only. The valid coverage run was the real gate command executed earlier: `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (exit 0, all 3544 tests executed). All gates are verified.

## Phase 153 — final verification pass report

**Verified (all tasks 01–04 complete in `complete/`):**
- Modal `fullPageUrl(source, path, back = null)` + `openDocumentModal(…, back = null)` with module-level `currentBack`; 5 chat call sites pass `"/"`, sources.js call site unchanged (byte-identical Sources path); `documentUrl` comment now true
- Copy-path control (shared `renderDocument` core, icon+text, text = accessible name) on both surfaces; per-page clipboard helpers with the fallback-field + status-line contract; `#doc-copy-announcer` live region; styles + 44px touch floor
- Unit pins: `&back=%2F` / no-`back` byte-identity, `currentBack` single-writer guard, copy strings byte-pinned, phase-13 regression pins untouched

**Test / lint / coverage results:**
- `uv run pytest` → **3544 passed**, 0 failed (full unit + integration)
- `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (>90% gate)
- `uv run pytest tests/e2e/test_document_viewer_context.py -v --no-cov` → **8 passed** in 47.33s, in isolation (DB up, mock LLM)
- `uv run ruff check . && uv run pyright` → clean (0 errors/warnings)

**Completion criteria:** all met — (1) chat→modal→Full page: `href="/"` + "Chat"; Sources→modal→Full page: `href="/sources.html"` + "Sources" with `back=` asserted absent; (2) no-`back` URLs byte-identical to pre-phase; (3) Copy path: "Path copied." on success (clipboard readback), prefilled select-on-focus fallback field + "Path ready — copy it from the field." on rejected clipboard, both surfaces parameterized; (4) copied string exactly `docs/homelab/kubernetes.md`; (5) full suite green, E2E isolated green, lint clean, coverage 99%; (6) commit by harness.

**Documentation:** README.md reviewed in full — phase-153 feature already named in the Chat section; routes, env reference, workflows, deployment all consistent — no changes needed. AGENTS.md reviewed in full — rules, gates, and quick-reference commands verified against the repo (incl. `test_smoke.py`, `.env.example`, gitignore entries) — consistent, no changes needed.

**Notable:** pytest 9.1.1 quirk — a doubled `-q` (pyproject `addopts` + explicit `-q`) swallows the final "N passed" summary line while still exiting 0; the plain `uv run pytest` gate command prints it. No defects found in prior work.

**Next pending phase:** `154_sync_status_freshness`.
2026-10-04 09:23:54 -04:00

7.7 KiB
Raw Blame History

Phase 156 — tables: sticky headers + ellipsis on clipped labels

Source: owner UX audit 2026-10-03 (live interactive-browser pass): "sticky table headers (RAG/Sources/History — the tables are long; the header scrolls away)" + "At ~1,009px window width the rightmost table columns start clipped with no ellipsis hint on the 'Hidden'/'Edit' labels (the wrapper scrolls fine — just subtle)". Story: n/a (audit-derived phase; the table machinery is phase 97's 97_kb_tree_catalog + phase 86's 86_history_page_width, both complete) Context: styles.css:: .table-wrap (L2528–2534: position: relative; background; border; radius; box-shadow; overflow-x: auto) wraps .docs-table (width: 100%; min-width: 640px) — the RAG folder table (the kb-folders-table class), the Sources table (#git-sources-table-wrap, L3390), and the History table (the phase-50/86 width contract, L5583). The thead already has a TINTED background (L530 comment: "the thead tinted") — the sticky design needs it OPAQUE (rows must not show through on scroll). The clipped cells: the Sources table's Hidden checkbox label + Ignore paths/Remove buttons, the RAG table's Edit button, the History table's Copy/Unshare/Create link/Delete buttons — today a clipped label hard-cuts (no text-overflow: ellipsis). The sticky constraint (the design's load-bearing fact): a position: sticky th sticks to its NEAREST SCROLLING ANCESTOR — with overflow-x: auto, the wrapper's computed overflow-y is auto too, so the wrapper IS the scroll container; but it has no height bound, so it never scrolls vertically — the PAGE does. A sticky header in an unbounded wrapper therefore scrolls away with the page. The header sticks only if the wrapper itself is the vertical scroll region (a bounded max-height) — the standard scrollable-table-with-sticky-header pattern. That is the UX trade this phase makes (ASSUMPTION A1): the tables become bounded internal-scroll panels; the page no longer carries their height.

Objective

On the three catalogue tables (RAG folders, Sources, History): the header row stays pinned while the user scrolls the table's own (now bounded) vertical scroll, and a clipped cell label shows … instead of a hard cut — the full text remains reachable (title/aria, the established idiom).

Dependencies

  • 97_kb_tree_catalog (complete) — the RAG folder table + the .table-wrap card.
  • 86_history_page_width (complete) — the History table's width contract (the sticky design must keep it — the table stays full-width inside the bounded panel).
  • 63_unambiguous_document_listing (complete) — the Sources table's column idiom (the Hidden label + per-row actions).

Design (shared by all tasks — the executor reads this, not the chat)

  • A1 (owner-locked): the bounded-panel design — .table-wrap gains max-height (ASSUMPTION A2: min(70vh, 64rem) — the phase picks this; it must keep a 15-row table scrolling while a 3-row one fits without a scrollbar: the min() of a viewport fraction and a fixed cap does exactly that) + overflow: auto (superseding overflow-x: auto — the horizontal scroll contract is UNCHANGED: the table's min-width: 640px still triggers the wrapper's horizontal scroll at narrow widths; overflow: auto keeps both axes). The th sticky: position: sticky; top: 0; z-index: 1; on .table-wrap table thead th with an OPAQUE background (the existing tint made opaque — background: var(--surface) or the existing tint token, whichever the L530 comment names; check the current value and make it non-transparent; do NOT add a new palette variable — B3: the 17-variable contract is untouched, and an all-default theme stays byte-identical in the served CSS — the rule changes apply to BOTH themes equally).
  • A3 (owner-locked): the ellipsis hint — the clipped cell LABELS get max-width + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; (a .cell-clip class or a direct selector on the action-cell text nodes — the executor's choice; the docs-table's existing cell padding is untouched). Coverage: the Sources Hidden checkbox label + Ignore paths + Remove; the RAG Edit; the History action buttons (Copy/Unshare/Create link/Delete). The FULL text stays reachable: every clipped label's control already carries its accessible name (the Remove button's aria-label names the full source — the phase-89 idiom; CHECK each of the other controls and add a title/aria-label where one is missing — the audit: the RAG Edit button, the History action buttons, the Ignore paths button — add the missing labels in the same task).
  • Scope guard: the CHAT view's .md-table-wrap (L539 — the markdown-rendered tables) is a DIFFERENT class — UNTOUCHED (it has no card, no height bound; the design is for the catalogue tables only). The mobile ≤640px contract (PLAN §7.1) is re-verified in the E2E (the hamburger nav + the tables' horizontal scroll at ≤640px still work — the max-height must not break the mobile scroll model).

Tasks

  1. 01_sticky_header.md — the bounded-panel max-height/overflow: auto + the opaque sticky th (all three catalogue tables share the class — one rule).
  2. 02_ellipsis_hints.md — the clipped-label ellipsis + the missing title/aria audit (the five control families).
  3. 03_unit_pins.md — the CSS contract pins (the existing styles-pinning idiom — the :root-drift test parses styles.css; extend the same suite).
  4. 04_e2e_table_polish.md — the dedicated Playwright suite (narrow viewport: the sticky offset + the computed ellipsis style + the mobile contract).

Testing & Quality

  • Unit: the CSS pins — .table-wrap carries max-height: min(70vh, 64rem) + overflow: auto; .table-wrap table thead th carries position: sticky; top: 0; z-index: 1 + an opaque background (parse the stylesheet — the styles-pinning idiom, the :root-drift test's precedent); the .md-table-wrap rule is byte-UNCHANGED (the scope guard).
  • Integration: none (CSS-only phase — the server pins stay byte-identical as regression guards).
  • Coverage: >90% on modified code (CSS pinned by the source-level suite + the E2E).
  • The phase is "Complete" only when the entire project test suite passes 100% (every test in the project, not just this phase's).
  • E2E: uv run pytest tests/e2e/test_table_ux_polish.py -v --no-cov in isolation (DB up: podman compose up -d db).

Documentation & Consistency

  • The final pass reviews README.md and AGENTS.md in full against the project as it stands after this phase — fixing every inconsistency found, including pre-existing ones not caused by the phase.
  • Tasks update README.md/AGENTS.md immediately when their own changes are user-visible (the bounded-panel scroll is a visible UX change — name it where the pages' layout is described, if anywhere).

Completion Criteria

  • all three catalogue tables: after scrolling the wrapper's vertical scroll, the thead stays at the panel top (the E2E asserts the th's offsetTop ≈ 0 while the wrapper's scrollTop > 0)
  • the horizontal-scroll contract is unchanged (narrow viewport: the table's min-width: 640px still scrolls inside the wrapper — the E2E)
  • a clipped action label shows text-overflow: ellipsis (the E2E asserts the COMPUTED style on a forced-narrow cell — deterministic, no pixel assertions) and its full text is reachable (title/aria present on every clipped control)
  • the mobile ≤640px contract holds (the hamburger nav + the table scroll — the E2E)
  • uv run pytest green; the dedicated E2E green in isolation; uv run ruff check . && uv run pyright clean; app/ coverage >90%
  • one atomic commit per AGENTS.md rule 8 (--no-gpg-sign)