phase: 156_table_ux_polish
Build and Push Containers / build-and-push-app (push) Successful in 2m15s
Build and Push Containers / build-and-push-db (push) Successful in 11s

Everything is verified. All gates green on the current working tree. Final report:

**Phase 156 — final verification pass (all 4 task files present in `complete/` — no outstanding work)**

- **Verified (not re-implemented):** bounded-panel `.table-wrap` (`max-height: min(70vh, 64rem)`, `overflow: auto`, opaque sticky `th` via `var(--brand-soft)`), `.cell-clip` ellipsis rule + the five label families in the three renderers, the added aria-labels, unit pins (incl. `.md-table-wrap` byte-unchanged scope guard, `#git-sources-table-wrap` harmony — no divergent rule), dedicated E2E suite (sticky ×3 tables, horizontal contract, computed-style ellipsis + reachable full text, mobile hamburger + scroll + pin)
- **Tests/lint/coverage:** `uv run pytest --cov=app --cov-report=term-missing` → **3570 passed, 99% on `app/`** (gate >90%); `uv run pytest tests/e2e/test_table_ux_polish.py -v --no-cov` (DB up) → **6/6 passed in isolation**; `uv run ruff check . && uv run pyright` → **clean** (0 errors)
- **Completion criteria:** sticky header all 3 tables (exact-0 offset while `scrollTop > 0`) ✅ · horizontal scroll unchanged ✅ · ellipsis computed style + actual clip + title/aria reachable ✅ · mobile ≤640px contract ✅ · full suite/E2E/lint/coverage ✅ · atomic commit: **harness commits this phase after the pass** (per pipeline protocol — no git history commands run by me)
- **Documentation:** AGENTS.md phase-156 wording (bounded panels, sticky headers, `.cell-clip` + aria-label idiom) matches the implementation exactly — verified consistent; README.md reviewed in full (pages/URLs vs registered routes, all `.env.example` vars documented, Quick Start, eval/llm scripts, compose prod profile, E2E examples) — verified consistent, no changes needed (README describes no table layout/scroll, so no phase-156 mention required)
- **Deviations:** none — all work was already in the working tree; this pass re-verified every gate and the docs
- **Next pending phase:** `157_chat_chip_cap`
This commit is contained in:
2026-10-04 20:46:57 -04:00
parent 742eef955b
commit 1513981c1c
33 changed files with 1790 additions and 58 deletions
+22 -3
View File
@@ -540,7 +540,15 @@ export async function mount(root) {
"aria-label",
`Index hidden folders for ${kindLabel} source: ${value}`,
);
hiddenLabel.append(hiddenBox, document.createTextNode("Hidden"));
/* Phase 156 (task 02, A3): the visible "Hidden" text is the
.cell-clip span — a squeezed cell shows "…" instead of a
hard cut; the full text stays reachable (the checkbox's
aria-label names the full source — the phase-105 idiom,
the label's own title carries the explanation). */
const hiddenClip = document.createElement("span");
hiddenClip.className = "cell-clip";
hiddenClip.textContent = "Hidden"; // never innerHTML (XSS contract)
hiddenLabel.append(hiddenBox, hiddenClip);
hiddenBox.addEventListener("change", () => toggleHidden(s, hiddenBox));
actTd.appendChild(hiddenLabel);
/* Phase 89: the per-row ignore-paths editor trigger (A3: stored
@@ -556,14 +564,25 @@ export async function mount(root) {
"aria-label",
`Edit ignored paths for ${kindLabel} source: ${value}`,
);
ignoreBtn.textContent = "Ignore paths";
/* Phase 156 (task 02, A3): the static text label is the
.cell-clip span — ellipsizes in a squeezed cell (the
aria-label above keeps the full context reachable). */
const ignoreClip = document.createElement("span");
ignoreClip.className = "cell-clip";
ignoreClip.textContent = "Ignore paths"; // never innerHTML (XSS contract)
ignoreBtn.append(ignoreClip);
ignoreBtn.addEventListener("click", () => openIgnoreEditor(s, ignoreBtn));
actTd.appendChild(ignoreBtn);
const btn = document.createElement("button");
btn.type = "button";
btn.className = "git-source-remove";
btn.setAttribute("aria-label", `Remove ${kindLabel} source: ${value}`);
btn.innerHTML = REMOVE_ICON + "<span>Remove</span>";
/* Phase 156 (task 02, A3): the visible text is the .cell-clip
span (a static template — the icon is static SVG, the
established exception to the textContent house rule); a
squeezed cell ellipsizes it (the aria-label above names
the full source). */
btn.innerHTML = REMOVE_ICON + '<span class="cell-clip">Remove</span>';
// Phase 69: opens the confirmation modal (the native confirm()
// retired) — the modal names the source and states the
// full-removal policy; the per-row error span is retired (the