phase: 171_admin_settings_layout
Expected state: only `00_phase.md` remains in `todo/` (the harness moves it on success; I must not touch it). All verification is complete. --- **Phase 171 final verification pass — report** - **Verified (all 4 tasks complete, CSS-only diff confirmed byte-exact to contract):** `#settings-content` flex column + `gap: 1.25rem` (tasks 01); `.settings-group-row th` band `--brand-soft` + 700 + `--line` top hairline (02); `#settings-save:disabled` `cursor: default` (03); new phase-dedicated Playwright suite (04). Mobile media queries and `settings.js`/`app/` byte-untouched, as pinned. - **Gates:** `uv run pytest --cov=app --cov-report=term-missing` → **4069 passed, app/ 99%** (>90%); `uv run ruff check .` → clean; `uv run pyright` → **0 errors, 0 warnings**. - **E2E (in isolation):** `test_admin_settings_layout.py` → **5 passed**; `test_admin_settings_panel.py` (phase 168) → **12 passed**; `test_wide_desktop_column.py` → **3 passed**. - **Completion criteria:** search/table no-intersection + gapped action row at 1440/375 px ✅ · distinct group bands (bg ≠ card, weight 700, ≥1px top hairline) ✅ · disabled Save `cursor: default` with §7.4 dim + aria-busy lifecycle intact ✅ · full suite + coverage + lint ✅ · all three E2E suites isolated ✅ · atomic commit + dir move = **harness's job** (task files already in `complete/`; per protocol I left every change uncommitted in the working tree). - **Documentation:** README.md (all 1487 lines) and AGENTS.md (all 80 lines) fully reviewed — every config default cross-checked vs `app/config.py`, all quick-reference commands verified against existing files, AGENTS.md rules/contracts verified vs code — **verified consistent, no changes needed** (phase 171 is CSS-only: no setup/command/env/API impact). - **Notable:** one phase-168 test failed on the first isolated run solely because an unrelated `undercooked` dev server held port 8124 (the suite's `DISABLED_PORT`); re-ran with the suite's built-in `E2E_APP_PORT=8300` override — no code change, foreign process untouched. Post-run DB state clean (`app_settings` 0, `ui_settings` 0, e2e tokens 0). - **Next pending phase:** none — phase 171 was the last `todo/` phase.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
# Phase 171 — Admin settings page: row rhythm, distinct group bands, a properly disabled Save
|
||||
|
||||
**Source:** `TODO.md` L4 — "In the admin settings page: The search settings text box overlaps settings table. The settings category headers are not differentiated from the settings themselves, making them confusing at a glance. The save settings and clear all overrides are smushed right up against the settings table. The save settings shows a loading cursor when moused over rather than just being disabled."
|
||||
**Story:** n/a (TODO-derived, owner roadmap confirmation 2026-10-10)
|
||||
**Context:** `frontend/index.html` — `#view-settings` (L1499), `#settings-content` (L1535, a plain block — its children `#settings-disabled`, `#settings-status`, `#settings-error`, `.settings-search` (L1562), the `.table-wrap` card (L1576, `#settings-tbody` created by settings.js), and `.settings-actions` (L1607: `#settings-save` disabled by default + `#settings-clear-all` + `#settings-dirty`) stack with **zero vertical rhythm** — the root of the "overlap" and the "smushed" complaints), `frontend/assets/settings.js` (637 lines — the group-row render L319–335: `tr.settings-group-row` + `th[colspan=4][scope=colgroup]` with the group name, the client-side search filter, the §7.4 Save lifecycle), `frontend/assets/styles.css` — `.settings-shell` (L4660–4665: `display:flex; flex-direction:column; gap: 1.25rem;`), `.settings-group-row th` (L4684–4690: `background: var(--surface)` — the **same** fill as the `.table-wrap` card (L2825–2831) → the band is effectively invisible, the "not differentiated" complaint), the sticky-th tint rule `.table-wrap table thead th { background: var(--brand-soft); }` (L2841–2848, the solid-tint precedent), `.settings-search` (L4780–4786), `.settings-actions` (L4804–4809), `#settings-save` (L4810–4824, incl. `:disabled { opacity: 0.6; cursor: wait; }` L4824 — the loading-cursor complaint), `.settings-reset:disabled { opacity: 0.5; cursor: default; }` (L4775 — the house idiom), the mobile media queries (L6396–6400: `.settings-search` / `.settings-actions` `flex-direction: column`, `#settings-save` `width: 100%`), `tests/unit/test_settings_frontend_view.py` (the phase-168 static pins, incl. the L561 media-query pin), `tests/e2e/test_admin_settings_panel.py` (the phase-168 suite — the regression set).
|
||||
|
||||
## Objective
|
||||
The four layout complaints on the Settings page — the search box overlapping the table card, the group headers indistinguishable from the rows, the Save / Clear row smushed against the table, and the disabled Save showing a loading cursor — are fixed with **CSS only** (plus the pin updates): one rhythm rule, one band rule, one cursor value. The phase-168 contract (the gate, the catalogue, the §7.4 Save lifecycle, the API, the bounded scroll + sticky header) stays behavior-identical — no `app/` code, no JS logic change.
|
||||
|
||||
## Design (shared by all tasks — the executor reads this, not the chat)
|
||||
- **D1 — the rhythm (task 01).** `#settings-content` becomes `display: flex; flex-direction: column; gap: 1.25rem;` — the **same** token `.settings-shell` already uses (L4664). The children (the disabled banner, the status line, the error line, the search row, the `.table-wrap` card, the `.settings-actions` row) then stack with one uniform gap: the search box clears the table card (the "overlap" dies) and the Save / Clear row is separated from the table (the "smushed" dies) in the same change. The `min-height` rules on `#settings-status` / `#settings-error` (L4849–4862) already reserve their lines — the gap is additive and uniform, no per-element margins are introduced.
|
||||
- **D2 — the band (task 02).** `.settings-group-row th` gets a band that is distinct from the row cells at a glance: `background: var(--brand-soft)` — the **same solid tint** the sticky `thead` rule already uses (L2847; B3: no new palette variable), `font-weight: 700` (the 0.82rem / uppercase / letter-spacing stay), and `border-top: 1px solid var(--line)` so a band never merges with the card's top edge or a previous band. The row cells stay on the card's `--surface`. The group text stays `--ink` on `--brand-soft` — the pairing the sticky-th rule already documents. Static, text-only, no animation (nothing for `prefers-reduced-motion` to still).
|
||||
- **D3 — the cursor (task 03).** `#settings-save:disabled` → `cursor: default` — the `.settings-reset:disabled` idiom (L4775); "just disabled", per the owner. The §7.4 in-flight state is the **dimmed** `:disabled` (`opacity: 0.6` stays) + `aria-busy` — the `cursor: wait` was never part of the contract; it was the "loading cursor on hover" the owner saw.
|
||||
- **NOT touched:** `settings.js` (the render / filter / save / reset / clear-all logic), `app/api/admin_settings.py`, the sign-in gate, the nav reveal, the `.table-wrap` bounded-scroll + sticky-th contract (L2825–2848), the `#settings-status` / `#settings-error` regions, the `restart required` flag, the mobile media queries (L6396–6400 — they compose with the new gap and stay byte-identical), `app/` server code.
|
||||
|
||||
## Dependencies
|
||||
- `168_admin_settings_panel` (in flight — the code + the E2E suite are committed; only its directory move + final commit are pending) — the Settings view this phase polishes. Note: the abandoned `169` settings status card never landed — there is nothing to coordinate with.
|
||||
|
||||
## Tasks
|
||||
1. `01_content_vertical_rhythm.md` — `#settings-content` becomes a flex column with the shell's `1.25rem` gap (fixes the search/table overlap **and** the smushed action row); the pin added.
|
||||
2. `02_group_band_distinction.md` — `.settings-group-row th` gets the `--brand-soft` band + 700 weight + top hairline, so groups read at a glance.
|
||||
3. `03_disabled_save_cursor.md` — `#settings-save:disabled` → `cursor: default` (the house idiom), killing the loading cursor.
|
||||
4. `04_e2e_settings_layout.md` — `tests/e2e/test_admin_settings_layout.py` (the phase's dedicated Playwright suite, AGENTS rule 4), the regression set, the full gates, the atomic commit.
|
||||
|
||||
## Testing & Quality
|
||||
- **Unit:** `tests/unit/test_settings_frontend_view.py` (the phase-168 static pin set, extended in tasks 01/02/03): the `#settings-content` flex-column + gap rule, the band's `--brand-soft` background + weight + top border, `#settings-save:disabled` `cursor: default` (and `cursor: wait` absent from the settings rules), plus the existing pins (the no-CDN pin, the `router.js` mapping, the media-query pin L561) staying green.
|
||||
- **E2E (AGENTS rule 4, task 04):** `tests/e2e/test_admin_settings_layout.py`, run **in isolation** — the four layout contracts, proven in a browser.
|
||||
- **Coverage:** **>90%** on `app/` (this phase changes no `app/` code — the gate must stay green, not drop).
|
||||
- **Lint/types:** `uv run ruff check . && uv run pyright`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] The search box and the table card do not intersect; the Save / Clear row is visibly separated from the table (the E2E bounding-box pins, at 1440 px and 375 px).
|
||||
- [ ] A settings group band is visually distinct from its rows (computed background ≠ the row cells' background; weight 700) and no band merges with the card edge.
|
||||
- [ ] A disabled **Save settings** shows `cursor: default` (not `wait`); the §7.4 dimmed-`aria-busy` in-flight state is intact.
|
||||
- [ ] `uv run pytest` green, `app/` coverage >90% (`uv run pytest --cov=app --cov-report=term-missing`), ruff + pyright clean.
|
||||
- [ ] `uv run pytest tests/e2e/test_admin_settings_layout.py -v --no-cov` green **in isolation**; `test_admin_settings_panel.py` (phase 168) + `test_wide_desktop_column.py` green in isolation.
|
||||
- [ ] One atomic `--no-gpg-sign` Conventional Commit; the phase dir moves to `.agents/phases/complete/`.
|
||||
|
||||
## Locked decisions (owner-confirmed 2026-10-10, roadmap confirmation)
|
||||
- **A3 — the band tint is `--brand-soft`** (the solid tint the sticky `thead` already uses — B3: no new palette variable) + a `--line` top hairline + weight 700; static, no animation.
|
||||
- **A4 — the rhythm reuses the shell's existing `gap: 1.25rem`** — no new spacing value; the existing mobile media queries (L6396–6400) keep working unchanged.
|
||||
|
||||
## Commit
|
||||
```bash
|
||||
git add frontend/ tests/ .agents/phases/ && git commit --no-gpg-sign -m "fix(settings): settings page layout — row rhythm, distinct group bands, a properly disabled Save"
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
# Task 01 — `#settings-content` gets the shell's vertical rhythm (overlap + smushing die)
|
||||
|
||||
**Phase:** `171_admin_settings_layout` · **Source:** `TODO.md:4` — "The search settings text box overlaps settings table. … The save settings and clear all overrides are smushed right up against the settings table."
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
The `#settings-content` block becomes a flex column with the shell's existing `gap: 1.25rem`, so its children — the disabled banner, the status line, the error line, the search row, the `.table-wrap` card, the `.settings-actions` row — stack with one uniform gap. One change kills both complaints: the search box clears the table card (the "overlap") and the Save / Clear row separates from the table (the "smushed").
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/styles.css` — add ONE rule (place it directly after the `.settings-shell` rule, L4660–4665, with a comment citing the phase-171 task + TODO L4):
|
||||
```css
|
||||
#settings-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
```
|
||||
No per-element margins anywhere; the `min-height` rules on `#settings-status` / `#settings-error` (L4849–4862) stay (they reserve the lines; the gap is additive and uniform). Verify by reading, not by adding: `.settings-search` (L4780–4786) and `.settings-actions` (L4804–4809) get **no** new margins — the gap is the only spacing source.
|
||||
2. `tests/unit/test_settings_frontend_view.py` — add the house-style CSS pin: the `#settings-content` rule exists with `display: flex`, `flex-direction: column`, and `gap: 1.25rem` (the same pin family as the L561 media-query pin — parse `styles.css`, assert the three declarations in one rule block).
|
||||
|
||||
- ASSUMPTION: A4 (phase-locked) — the rhythm reuses the shell's existing `gap: 1.25rem` token; no new spacing value; the existing mobile media queries (styles.css L6396–6400: `.settings-search` / `.settings-actions` `flex-direction: column; align-items: stretch;` + `#settings-save { width: 100%; }`) keep working unchanged.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: `tests/unit/test_settings_frontend_view.py` green (`uv run pytest tests/unit/test_settings_frontend_view.py -v --no-cov`).
|
||||
- Coverage: **>90%** on `app/` (unchanged code — the gate must stay green).
|
||||
- Lint/types: `uv run ruff check . && uv run pyright`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] In the live UI (or the task-04 E2E): the search input's box and the `.table-wrap` card's box do not touch — a visible, uniform gap between them, and the same gap above the `.settings-actions` row.
|
||||
- [ ] `uv run pytest tests/unit/test_settings_frontend_view.py -v --no-cov` green; `uv run pytest` green; ruff + pyright clean.
|
||||
- [ ] No per-element margin was added (the diff touches exactly one CSS rule + the pin); the mobile stacking (L6396–6400) is byte-unchanged.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Task 02 — the group bands are distinct from the rows at a glance
|
||||
|
||||
**Phase:** `171_admin_settings_layout` · **Source:** `TODO.md:4` — "The settings category headers are not differentiated from the settings themselves, making them confusing at a glance."
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
`.settings-group-row th` — today `background: var(--surface)`, the **same** fill as the `.table-wrap` card, i.e. invisible — becomes a band that reads as a category: the `--brand-soft` tint (the solid tint the sticky `thead` rule already uses), weight 700, and a `--line` top hairline so a band never merges with the card edge or a previous band.
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/styles.css` — `.settings-group-row th` (L4684–4690), three value changes in the existing rule (no new selector):
|
||||
- `background: var(--surface);` → `background: var(--brand-soft);` (B3: no new palette variable — the exact tint `.table-wrap table thead th` already documents at L2841–2848);
|
||||
- add `font-weight: 700;` (the `font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.04em;` stay);
|
||||
- add `border-top: 1px solid var(--line);` (the rule's existing `border-bottom` from the `.settings-table th` family, L4676–4681, stays — a band is delimited above AND below, and can never merge with the card's top edge or the band before it);
|
||||
- update the contrast comment (L4683): the text stays `--ink` on `--brand-soft` — the pairing the sticky-th rule already documents (the existing "15.3:1, AA" comment applies to `--surface`; recompute / restate the ratio on `--brand-soft` in the comment — the `:root` `--brand-soft` token carries its own documented pairing).
|
||||
2. `tests/unit/test_settings_frontend_view.py` — add the pin: the `.settings-group-row th` rule carries `background: var(--brand-soft)`, `font-weight: 700`, and `border-top: 1px solid var(--line)` (same pin family as task 01's).
|
||||
|
||||
- ASSUMPTION: A3 (phase-locked) — the band = `--brand-soft` (the sticky-th tint, no new palette variable) + `--line` top hairline + weight 700; static, text-only, no animation (nothing for `prefers-reduced-motion` to still).
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: `tests/unit/test_settings_frontend_view.py` green.
|
||||
- Coverage: **>90%** on `app/` (unchanged code).
|
||||
- Lint/types: `uv run ruff check . && uv run pyright`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] In the live UI (or the task-04 E2E): each group band is visibly tinted (`--brand-soft`), bold, and hairline-delimited above; the row cells stay on the card's `--surface` — the two are distinguishable at a glance.
|
||||
- [ ] No band merges with the card's top edge or the band above it (the top hairline is painted).
|
||||
- [ ] `uv run pytest tests/unit/test_settings_frontend_view.py -v --no-cov` green; ruff + pyright clean.
|
||||
- [ ] `settings.js` is byte-unchanged (the band is pure CSS — the group-row render L319–335 is untouched).
|
||||
@@ -0,0 +1,23 @@
|
||||
# Task 03 — a disabled Save settings is just disabled (no loading cursor)
|
||||
|
||||
**Phase:** `171_admin_settings_layout` · **Source:** `TODO.md:4` — "The save settings shows a loading cursor when moused over rather than just being disabled."
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
`#settings-save:disabled` stops painting `cursor: wait` (the loading cursor the owner saw on hover) and takes `cursor: default` — the house idiom `.settings-reset:disabled` already uses. "Just disabled": the §7.4 in-flight state stays the **dimmed** `:disabled` (`opacity: 0.6`) + `aria-busy`; the cursor was never part of the contract.
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/styles.css` L4824 — `#settings-save:disabled { opacity: 0.6; cursor: wait; }` → `#settings-save:disabled { opacity: 0.6; cursor: default; }`. Keep `opacity: 0.6` (the §7.4 dim) and the `#settings-save:hover:not(:disabled)` rule (L4823) byte-unchanged. Update the adjacent comment block (L4809–4810, "the §7.4 in-flight state is the dimmed :disabled + aria-busy") to state the cursor contract explicitly: `cursor: default` when disabled (the `.settings-reset:disabled` L4775 idiom) — the wait cursor was never part of §7.4.
|
||||
2. `tests/unit/test_settings_frontend_view.py` — add the pin: `#settings-save:disabled` carries `cursor: default`, and `cursor: wait` is **absent** from the settings-page rules (a negative grep over the `.settings*` / `#settings-*` rule blocks — the house negative-pin idiom). Note: `.edit-save:disabled` (styles.css L1454) keeps its own `cursor: wait` — it is the chat edit form's in-flight state (phase 159/170), a different control; the negative pin is scoped to the settings rules only, and the test must not fail on the `.edit-save` rule.
|
||||
|
||||
- ASSUMPTION: none — the owner's words fix the target ("just being disabled"); the idiom choice (`default`, mirroring L4775) is the house convention, not a guess.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: `tests/unit/test_settings_frontend_view.py` green.
|
||||
- Coverage: **>90%** on `app/` (unchanged code).
|
||||
- Lint/types: `uv run ruff check . && uv run pyright`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] In the live UI (or the task-04 E2E): an untouched **Save settings** (no dirty rows) is `disabled` and its computed `cursor` is `default` — hovering shows no wait/loading cursor; after a row is edited the button enables and its cursor is `pointer` again (the §7.4 lifecycle, phase 168, intact).
|
||||
- [ ] `uv run pytest tests/unit/test_settings_frontend_view.py -v --no-cov` green; ruff + pyright clean.
|
||||
- [ ] The diff is exactly one CSS value + one comment + the pin; `settings.js` and `app/` are byte-unchanged.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Task 04 — `tests/e2e/test_admin_settings_layout.py`: the four layout contracts, proven in a browser
|
||||
|
||||
**Phase:** `171_admin_settings_layout` · **Source:** `TODO.md:4` — "In the admin settings page: The search settings text box overlaps settings table. The settings category headers are not differentiated … The save settings and clear all overrides are smushed right up against the settings table. The save settings shows a loading cursor when moused over rather than just being disabled."
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
The phase's dedicated Playwright suite (AGENTS rule 4/9): the admin signs in, and the four layout contracts hold in a real browser — the search row clears the table card, the action row is gapped from the table, the group bands are distinct, and a disabled Save is just disabled. Then the regression set, the full gates, and the one atomic commit.
|
||||
|
||||
## Work
|
||||
1. `tests/e2e/test_admin_settings_layout.py` (new) — copy the module-scoped app fixture from `tests/e2e/test_admin_settings_panel.py` verbatim (the mock LLM, `BOR_ENVIRONMENT=e2e`, `BOR_STATIC_DIR`, the E2E admin password/secret, the leak guards, the module's `BOR_SETTINGS_DB=1` opt-in + the `TRUNCATE app_settings` before/after — this suite never writes overrides, so the truncation is belt-and-braces, the same idiom) and change exactly: the module docstring (what the suite proves + the Source line `TODO.md:4` — the four layout contracts) and the test list below.
|
||||
2. Tests (names pinned; `login(page, app_url)` from `tests/e2e/auth_helpers.py`; the suite drives the admin gate exactly like the phase-168 suite):
|
||||
- `test_search_row_is_clear_of_the_table` — at 1440 px and 375 px: `#settings-filter`'s `bounding_box()` and the `.table-wrap` card's box do not intersect (the vertical gap between the search row's bottom and the card's top is > 0), and the card keeps its bounded scroll + sticky `thead` (the computed `position: sticky` on the `thead th` — the phase-156/168 contract intact under the new rhythm).
|
||||
- `test_actions_row_is_gapped_from_the_table` — at 1440 px and 375 px: `.settings-actions`'s box does not intersect the `.table-wrap` card's box (the "smushed" pin — the gap between the card's bottom and the action row's top is > 0); `#settings-save` and `#settings-clear-all` are both reachable (no clipping at either viewport).
|
||||
- `test_group_bands_are_distinct_from_the_rows` — after `login()`: at least the `Retrieval & honesty` and `LLM (aipi)` `tr.settings-group-row` bands render; a band's computed `background-color` ≠ the `.table-wrap` card's computed background (the `--surface` row-cell fill) — the "not differentiated" pin — and the band's computed `font-weight` is ≥ 700; a band's computed `border-top-width` is ≥ 1 px (the hairline — no merge with the card edge or the previous band).
|
||||
- `test_disabled_save_is_just_disabled` — before any edit: `#settings-save` is `disabled` and its computed `cursor` is `default` (NOT `wait` — the loading-cursor pin); edit one row (a `type=search` filter + a value change, the phase-168 dirty-row flow): the button enables (computed `cursor` `pointer`) and dims back to `default` after a **Save settings** settles (the §7.4 lifecycle, phase 168, intact under the new cursor).
|
||||
- `test_settings_page_layout_and_accessibility` — no horizontal page overflow at 375 px and 1440 px (§7.1: the table is ≥80 % of the container, not a skinny list); keyboard tab order still reaches filter → first row input → **Save settings** → **Clear all overrides** (a `page.keyboard` walk asserting focus lands on the real controls — the new rhythm must not have broken the phase-168 keyboard contract); zero external requests during the whole test (`page.on("request")` — rule 6).
|
||||
3. **Regression set, all in isolation** (`--no-cov`): `test_admin_settings_panel.py` (the phase-168 suite — the gate, the save-live contract, the masking, the reset / clear-all, now under the new layout), `test_wide_desktop_column.py` (the 72rem container contract, the Settings view included).
|
||||
4. **Full gates** (AGENTS rule 9, all green before the commit):
|
||||
```bash
|
||||
uv run pytest
|
||||
uv run pytest --cov=app --cov-report=term-missing # >90% on app/
|
||||
uv run pytest tests/e2e/test_admin_settings_layout.py -v --no-cov
|
||||
uv run ruff check . && uv run pyright
|
||||
```
|
||||
5. **Docs & close-out:** confirm `README.md`'s Settings page section (phase 168's addition) needs no layout wording change (it describes the contract, not the pixels — grep `Settings` there); move the phase directory:
|
||||
```bash
|
||||
git mv .agents/phases/todo/171_admin_settings_layout .agents/phases/complete/171_admin_settings_layout
|
||||
```
|
||||
and commit (AGENTS rule 8, `--no-gpg-sign`):
|
||||
```bash
|
||||
git add frontend/ tests/ README.md .agents/phases/ && git commit --no-gpg-sign -m "fix(settings): settings page layout — row rhythm, distinct group bands, a properly disabled Save"
|
||||
```
|
||||
|
||||
## Testing & Quality
|
||||
- The suite passes **in isolation** (`-v --no-cov`) and leaves `app_settings` empty (the `TRUNCATE` idiom — a leftover row is the cross-suite poison phase-168 D6 exists to prevent).
|
||||
- The pins already exist from tasks 01/02/03 — this file adds the browser contract only; if it needs a behavior the CSS lacks, fix the CSS in this task and say so in the commit body.
|
||||
- Coverage: **>90%** on `app/` after everything (the `--cov` runs exclude E2E by the repo's config; the gate is the unit+integration number).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] All four layout pins are green in isolation: search clear of the table, action row gapped, bands distinct (tint + weight + hairline), disabled Save `cursor: default`.
|
||||
- [ ] `app_settings` is empty after the suite and the phase-168 regression suite is green in isolation.
|
||||
- [ ] `uv run pytest` green, `app/` coverage >90%, ruff + pyright clean.
|
||||
- [ ] Phase directory moved to `.agents/phases/complete/171_admin_settings_layout/` and one atomic `--no-gpg-sign` Conventional Commit exists (verify with `git log -1 --stat`).
|
||||
Reference in New Issue
Block a user