phase: 156_table_ux_polish
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:
+103
-22
@@ -2524,14 +2524,74 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
Tokens view carried the identical defect). Zero-offset positioning
|
||||
changes no layout: the spans stay clipped by their own
|
||||
clip: rect(0 0 0 0) + 1px box, and cards without such spans (the
|
||||
RAG .docs-table, the git-sources table) render unchanged. */
|
||||
RAG .docs-table, the git-sources table) render unchanged.
|
||||
Phase 156 (task 01, owner-locked A1): the card is ALSO the
|
||||
vertical scroll region — max-height: min(70vh, 64rem) bounds it
|
||||
(a 15-row table scrolls, a 3-row one fits without a scrollbar)
|
||||
and overflow: auto (superseding the phase-86 overflow-x pin —
|
||||
the tables' min-width still triggers the in-card horizontal
|
||||
scroll at narrow widths) makes the card the sticky th's
|
||||
scrolling ancestor; an unbounded card never scrolls vertically,
|
||||
so a sticky header in it would scroll away with the page. */
|
||||
.table-wrap {
|
||||
position: relative;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
overflow-x: auto;
|
||||
overflow: auto;
|
||||
max-height: min(70vh, 64rem);
|
||||
}
|
||||
/* Phase 156 (task 01): the ONE sticky-header rule for every
|
||||
catalogue table inside a .table-wrap card (the RAG .docs-table,
|
||||
the .git-sources-table, the .history-table, the .tokens-table —
|
||||
the per-table th rules keep the text styling only). top: 0 pins
|
||||
the header row to the card top while the card's own vertical
|
||||
scroll moves; z-index: 1 keeps the pinned th above the scrolled
|
||||
cells. OPAQUE background: --brand-soft is a solid hex (no alpha),
|
||||
so rows never show through the pinned header (B3: no new palette
|
||||
variable; a themed page's #bor-theme override of the token
|
||||
handles the color — same behavior, B4). */
|
||||
.table-wrap table thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background: var(--brand-soft);
|
||||
}
|
||||
/* Phase 156 (task 02, owner-locked A3): the ellipsis hint on the
|
||||
catalogue tables' CLIPPED action labels. The class sits on the
|
||||
LABEL SPAN, never the cell (the docs-table cell padding is
|
||||
untouched). The five families: the Sources "Hidden" checkbox
|
||||
text + "Ignore paths" + "Remove" (git-sources.js makeRow), the
|
||||
RAG folder table's "Edit" (sources.js makeDescCell), the
|
||||
History "Copy" / "Unshare" / "Create link" / "Delete" (history.js).
|
||||
max-width: 12rem is GENEROUS — every one of the five labels
|
||||
("Edit" ~2.4rem … "Ignore paths" ~6.3rem at 0.78–0.82rem)
|
||||
fits it, so a cell that FITS shows its full text (the clamp
|
||||
engages only in the CLIPPED case — the audit's ~1,009px
|
||||
rightmost columns), where the label shows "…" instead of the
|
||||
card edge's hard cut. Mechanism: the block container's
|
||||
overflow hidden + text-overflow ellipsis + nowrap is the
|
||||
house .sync-label shrinkable-item pattern; the SPAN's
|
||||
min-width: 0 lets the flex item shrink below its text width —
|
||||
but a flex item shrinks only when its FLEX PARENT is
|
||||
constrained, so the hosting controls carry the supporting width
|
||||
rules (the max-width: 100% + min-width: 0 added to each block
|
||||
below): without them the inline-flex buttons keep their content
|
||||
size, overflow the squeezed cell, and the card edge hard-cuts
|
||||
the label again (the pre-phase defect). The FULL text stays
|
||||
reachable — every one of these controls' aria-label names the
|
||||
row's identity (the source's URL/path — the phase-89/105 idiom;
|
||||
the chat title — the phase-66 idiom; the source/folder name —
|
||||
the phase-97 idiom), so the clip is visual only. B5 idiom
|
||||
intact: the labels are text (text + color, never color alone). */
|
||||
.cell-clip {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
max-width: 12rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.docs-table {
|
||||
width: 100%;
|
||||
@@ -2546,13 +2606,10 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
white-space: nowrap;
|
||||
}
|
||||
.docs-table th {
|
||||
background: var(--brand-soft);
|
||||
color: var(--brand-ink);
|
||||
font-size: 0.82rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
.docs-table td:nth-child(2) { font-family: var(--mono); font-size: 0.82rem; max-width: 30rem; overflow: hidden; text-overflow: ellipsis; }
|
||||
.docs-table tbody tr:hover { background: var(--bg); }
|
||||
@@ -2720,6 +2777,14 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
/* Phase 156 (task 02, A3): the width support for the label span's
|
||||
clamp — the flex item is clamped to the cell + shrinkable
|
||||
(min-width: 0), so a squeezed Description cell ellipsizes the
|
||||
"Edit" label instead of overflowing the card edge. The level
|
||||
block's use (the non-flex .kb-level-body) is unaffected: its
|
||||
natural width never reaches the clamp. */
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 24px;
|
||||
padding: 0.15rem 0.7rem;
|
||||
border: 1px solid var(--line);
|
||||
@@ -3384,16 +3449,15 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
.ignore-editor-save:hover:not(:disabled) { background: var(--brand-hover); }
|
||||
|
||||
/* The list: the Sources page's table pattern — full width in the
|
||||
72rem frame, surface card, horizontally scrollable wrapper (the
|
||||
URL column never wraps or ellipsizes: long URLs, credentials
|
||||
included, scroll the wrapper instead of truncating). */
|
||||
#git-sources-table-wrap {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
overflow-x: auto;
|
||||
}
|
||||
72rem frame. The URL column never wraps or ellipsizes: long
|
||||
URLs, credentials included, scroll the wrapper instead of
|
||||
truncating (the .git-sources-table 560px min-width keeps the
|
||||
in-wrapper horizontal scroll). Phase 156 (task 01): the id
|
||||
card's duplicate background/border/radius/shadow/overflow
|
||||
declarations were REMOVED — the wrapper carries
|
||||
class="table-wrap", so it inherits the base card + the
|
||||
bounded-panel max-height + the sticky rule (the one-rule
|
||||
contract: no divergent unbounded override). */
|
||||
.git-sources-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
@@ -3407,13 +3471,10 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
white-space: nowrap;
|
||||
}
|
||||
.git-sources-table th {
|
||||
background: var(--brand-soft);
|
||||
color: var(--brand-ink);
|
||||
font-size: 0.82rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
/* Location cell: mono at the Sources-table size; the <code> is plain
|
||||
(no chip background — the cell IS the mono readout). Phase 38:
|
||||
@@ -3448,6 +3509,10 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.35rem;
|
||||
/* Phase 156 (task 02, A3): the width support for the "Remove"
|
||||
label span's clamp (same rationale as .git-source-hidden
|
||||
above). */
|
||||
max-width: 100%;
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
flex: 0 0 auto;
|
||||
@@ -3476,6 +3541,10 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* Phase 156 (task 02, A3): the width support for the "Ignore
|
||||
paths" label span's clamp (same rationale as .git-source-hidden
|
||||
above). */
|
||||
max-width: 100%;
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
flex: 0 0 auto;
|
||||
@@ -3525,6 +3594,11 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
/* Phase 156 (task 02, A3): the width support for the "Hidden"
|
||||
label span's clamp (the label is an inline-flex child of the
|
||||
cell — clamped to the cell so the span can ellipsize in a
|
||||
squeezed column instead of overflowing the card edge). */
|
||||
max-width: 100%;
|
||||
height: 44px;
|
||||
padding: 0 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
@@ -3678,7 +3752,6 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
vertical-align: middle;
|
||||
}
|
||||
.history-table th {
|
||||
background: var(--brand-soft);
|
||||
color: var(--brand-ink);
|
||||
font-size: 0.82rem;
|
||||
text-transform: uppercase;
|
||||
@@ -3730,9 +3803,17 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
}
|
||||
/* Actions: the Delete ghost button (the tuning row-action language)
|
||||
+ the inline two-step confirm pair (phase 50 task 04). */
|
||||
.history-actions { display: inline-flex; align-items: center; gap: 0.4rem; }
|
||||
/* Phase 156 (task 02, A3): the width support for the action
|
||||
labels' clamp — both wrappers are clamped to the cell (max-width
|
||||
100% of the td's content box), and the flex-item buttons are
|
||||
shrinkable (min-width: 0), so a squeezed Actions/Share column
|
||||
ellipsizes the label instead of the card edge hard-cutting it.
|
||||
Mobile (≤640px) is unaffected in kind: the flex-wrap there wraps
|
||||
the buttons onto their own lines before the clamp engages. */
|
||||
.history-actions { display: inline-flex; align-items: center; gap: 0.4rem; max-width: 100%; }
|
||||
.history-delete {
|
||||
min-height: 44px;
|
||||
min-width: 0;
|
||||
padding: 0.35rem 0.7rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
@@ -3785,12 +3866,13 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
row-action language of .history-delete: --line border, transparent
|
||||
fill, ink-soft (>=5.1:1), ≥44px comfortable target, focus-visible
|
||||
via the global 3px rule; hover takes the brand pair (6.9:1). */
|
||||
.history-share { display: inline-flex; align-items: center; gap: 0.4rem; flex-wrap: nowrap; }
|
||||
.history-share { display: inline-flex; align-items: center; gap: 0.4rem; flex-wrap: nowrap; max-width: 100%; }
|
||||
.history-share-cell { white-space: nowrap; }
|
||||
.history-share-create,
|
||||
.history-share-copy,
|
||||
.history-unshare {
|
||||
min-height: 44px;
|
||||
min-width: 0; /* phase 156 (task 02, A3) — the shrinkable flex item (the clamp support above) */
|
||||
padding: 0.35rem 0.7rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
@@ -3984,7 +4066,6 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
vertical-align: middle;
|
||||
}
|
||||
.tokens-table th {
|
||||
background: var(--brand-soft);
|
||||
color: var(--brand-ink);
|
||||
font-size: 0.82rem;
|
||||
text-transform: uppercase;
|
||||
|
||||
Reference in New Issue
Block a user