chore(agent): phase roadmap from TODO.md (176 mobile settings value width)
The single TODO item (mobile settings Value inputs impossibly small) becomes phase 176_mobile_settings_value_width: 3 tasks (CSS fix in the <=640px block, unit pins, dedicated E2E suite) with owner-locked A1-A3. Protocol B append - PLAN.md and AGENTS.md untouched. LLM-Generated: true
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# Phase 176 — Admin settings page: a usable Value-column width on mobile
|
||||
|
||||
**Source:** `TODO.md` L3 — "On mobile, the settnigs values are impossibly small. This is a horizontally scrollable table so we can have slightly wider input fields." (the TODO's `image-1.png` screenshot: at a 375 px phone viewport the Value-column inputs render only 2–3 characters wide — "Br", "0.", "pr", "Wr", "Pc", "IN" — unreadable and unusable)
|
||||
**Story:** n/a (TODO-derived, owner roadmap confirmation 2026-10-12)
|
||||
**Context:** `frontend/index.html` — `#view-settings` (L1519), the `.table-wrap` card (L1596) holding `table.settings-table` with the static `thead` (L1599–1606: the Setting / Value / Source / Actions `th`s — the Value `th` at L1602 carries no class), `frontend/assets/settings.js` (the row render — `makeRow` builds `td.settings-name-cell` + `td.settings-value-cell` (the `.settings-control` control) + the Source badge cell + the Reset cell; the controls are type-correct per item and carry `width: 100%; min-width: 0`), `frontend/assets/styles.css` — `.settings-table` (L4742–4746: `width: 100%; min-width: 560px;` — the ONLY width floor, no per-column widths — the root cause: at 375 px the table renders at its 560 px floor and the Setting cell's long `white-space: nowrap` labels (L4754: `td { white-space: nowrap }`) devour it, collapsing the `min-width: 0` Value cell), the `.table-wrap` bounded-scroll card (L2836–2842: `overflow: auto; max-height: min(70vh, 64rem)` — horizontal scroll is the established contract; the ≤640px block's own comment: "the table's own horizontal scroll covers the columns"), the ≤640px mobile block (L6180–6529) with the existing phase-168 settings squeeze rules (L6484–6488), `tests/unit/test_settings_frontend_view.py` (the phase-168/171 static pin set, incl. `test_styles_css_settings_block_uses_existing_tokens_only` L516), `tests/e2e/test_admin_settings_layout.py` (phase 171 — the "no horizontal page overflow at 375px / 1440px" pin L482–486) and `tests/e2e/test_admin_settings_panel.py` (phase 168 — the regression set), PLAN.md §7.1 (≥44px touch targets on mobile; lists/tables ≥80–90% of container; the table scrolls INSIDE its card).
|
||||
|
||||
## Objective
|
||||
On a phone, the Settings table's Value inputs are impossibly small (2–3 chars). The owner's direction: the table is already horizontally scrollable, so the inputs should be **slightly wider** — i.e. give the Value column a usable minimum width (and raise the table's scroll floor to match) so every `.settings-control` is readable and editable on a 375 px phone, absorbed by the card's existing horizontal scroll. The fix is **CSS-only, mobile-scoped** (the ≤640px block) — the desktop layout stays byte-identical, no `app/` code, no JS logic change.
|
||||
|
||||
## Design (shared by all tasks — the executor reads this, not the chat)
|
||||
- **D1 — the Value-column floor (task 01, owner-locked A2).** Inside the existing `@media (max-width: 640px)` block (L6180, appended beside the phase-168 settings squeeze rules L6484–6488), two rules:
|
||||
- `.settings-table { min-width: 720px; }` — the mobile scroll floor (was 560 px, which the nowrap Setting labels exhaust). At a 375 px viewport the table renders at 720 px and scrolls horizontally INSIDE the `.table-wrap` card (overflow: auto) — the page itself never overflows (the phase-171 pin holds: the scroll is the card's, not the page's).
|
||||
- `.settings-value-cell, .settings-value-th { min-width: 13rem; }` — the Value column (and its control, `width: 100%`) gets a 13 rem (≈208 px ≈ 14 mono chars at the control's 0.85 rem) floor at the phone. `13rem` is a plain length value — NO new CSS custom property (the house token rule B3 is about palette variables; a bare length literal in the mobile block is the same idiom as the existing `#settings-save { width: 100% }` rules there).
|
||||
- To make the column floor deterministic (auto table layout can otherwise leave the `th` unconstrained), the Value `th` in `frontend/index.html` L1602 gains the class: `<th scope="col" class="settings-value-th">Value</th>` — static HTML (the no-HTML-string house rule binds the JS, not the templates; `settings.js` is untouched).
|
||||
- **D2 — mobile-only scope (owner-locked A1).** Every new rule lives INSIDE the `@media (max-width: 640px)` block only. At ≥641 px (the 1440 px E2E desktop pin, and the 72 rem container ≈ 1152 px) the media query is inert: the table renders at card width as today, the Value column takes its natural auto share, and the desktop bytes of `styles.css`/`index.html` behavior are unchanged.
|
||||
- **D3 — no other change (task 01).** `settings.js` (render / filter / save / reset / clear-all), the `.table-wrap` bounded-scroll + sticky-th contract (L2836–2848), the phase-171 `#settings-content` rhythm, the group bands, the Source badges, the mobile action-row squeeze rules (L6484–6488), `app/` server code — all byte-identical. Nothing animates (nothing for `prefers-reduced-motion` to still).
|
||||
- **NOT touched:** `app/`, `settings.js`, the ≥641 px rules in `styles.css`, the other catalogue tables.
|
||||
|
||||
## Dependencies
|
||||
- `171_admin_settings_layout` (complete) — the Settings page rhythm/band/cursor layout this phase extends, and the ≤640px mobile block's existing settings squeeze rules (L6484–6488) the new rules are appended beside.
|
||||
- `168_admin_settings_panel` (complete) — the panel contract (the gate, the catalogue, the `.table-wrap` scroll card, the §7.4 Save lifecycle) that must stay behavior-identical.
|
||||
|
||||
## Tasks
|
||||
1. `01_mobile_value_column_width.md` — the Value-column `min-width: 13rem` + the table `min-width: 720px` floor, both inside the ≤640px block; the `settings-value-th` class on the Value `th`.
|
||||
2. `02_unit_pins.md` — `tests/unit/test_settings_frontend_view.py`: the static pins for the two mobile rules (present ONLY inside the ≤640px block) + the `settings-value-th` class pin on the static thead; the existing token-only pin stays green.
|
||||
3. `03_e2e_mobile_value_width.md` — `tests/e2e/test_settings_mobile_value_width.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/171 static pin set, extended in task 02): the two new rules parse out of the ≤640px block with the exact values, the `settings-value-th` class is present on the settings `thead`'s Value `th`, and `test_styles_css_settings_block_uses_existing_tokens_only` stays green (no new palette variable — a bare length is not a token).
|
||||
- **E2E (AGENTS rule 4, task 03):** `tests/e2e/test_settings_mobile_value_width.py`, run **in isolation** — the width contract, proven in a browser at 375 px and 1440 px.
|
||||
- **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
|
||||
- [ ] At a 375 px viewport, every Settings `.settings-control` (the Value inputs) is ≥ 12 rem (≈192 px, the A3 pin threshold) wide — readable and editable (the E2E width pin).
|
||||
- [ ] At 375 px the `.table-wrap` card scrolls horizontally (`scrollWidth > clientWidth`) while the PAGE does not overflow horizontally (the phase-171 pin at 375 px and 1440 px stays green).
|
||||
- [ ] At a 1440 px viewport the Value input width is unchanged vs. pre-phase (the mobile rules are inert — no desktop regression; the phase-171 E2E suite stays green in isolation).
|
||||
- [ ] The keyboard tab order still reaches filter → first row input → **Save settings** → **Clear all overrides** at 375 px (the phase-168/171 contract under the new column floor); the whole test issues ZERO external requests (rule 6).
|
||||
- [ ] `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_settings_mobile_value_width.py -v --no-cov` green **in isolation**; `test_admin_settings_layout.py` (phase 171) + `test_admin_settings_panel.py` (phase 168) green in isolation.
|
||||
- [ ] One atomic `--no-gpg-sign` Conventional Commit; the phase dir moves to `.agents/phases/complete/`.
|
||||
|
||||
## Locked decisions (owner-confirmed 2026-10-12, roadmap confirmation)
|
||||
- **A1 — mobile-only scope.** The new width rules live ONLY in the existing `@media (max-width: 640px)` block; the desktop (≥641 px) layout stays byte-identical — the TODO is mobile-scoped.
|
||||
- **A2 — the width values.** Table floor **720 px** (was 560 px — the phone scrolls the card horizontally, the established contract); Value column **min-width 13rem** → the input ≈ 208 px ≈ 14 mono characters, i.e. "slightly wider input fields". No new CSS custom property, no JS change.
|
||||
- **A3 — the E2E pin threshold is 12 rem** (192 px) — a deliberate margin below the 13 rem (208 px) rule so the pin doesn't become brittle to sub-pixel rounding.
|
||||
|
||||
## Commit
|
||||
```bash
|
||||
git add frontend/ tests/ .agents/phases/ && git commit --no-gpg-sign -m "fix(settings): mobile settings table — a usable Value-column width (the card's horizontal scroll absorbs it)"
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
# Task 01 — the mobile Value column gets a usable width (the card's horizontal scroll absorbs it)
|
||||
|
||||
**Phase:** `176_mobile_settings_value_width` · **Source:** `TODO.md:3` — "On mobile, the settnigs values are impossibly small. This is a horizontally scrollable table so we can have slightly wider input fields."
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
At a 375 px phone viewport the Settings table's Value inputs render 2–3 characters wide (the Setting cell's long `white-space: nowrap` labels exhaust the table's 560 px floor). Give the Value column a **13 rem floor** and raise the table's mobile scroll floor to **720 px** — both inside the existing `@media (max-width: 640px)` block, so the phone scrolls the card horizontally (the established contract) while every `.settings-control` is readable and editable. CSS only, plus the one `th` class that makes the column floor deterministic; the desktop (≥641 px) stays byte-identical (owner-locked A1).
|
||||
|
||||
## Work
|
||||
1. `frontend/index.html` L1602 — the static `thead`'s Value cell: `<th scope="col">Value</th>` → `<th scope="col" class="settings-value-th">Value</th>`. Static HTML only (the no-HTML-string house rule binds the JS — `settings.js` stays byte-unchanged); the class exists so the column floor constrains the header cell, not just the `td`s, under auto table layout.
|
||||
2. `frontend/assets/styles.css` — inside the existing `@media (max-width: 640px)` block (opens L6180), appended directly after the phase-168 settings squeeze rules (L6484–6488: `.settings-search` / `.settings-actions` stack + `#settings-save` / `#settings-clear-all` full width + `.settings-actions-cell` wrap), add ONE comment + two rules (the phase-176 header comment, stating the TODO L3 contract: the Value column's 13 rem floor makes the input ≈208 px ≈ 14 mono chars at the phone; the 720 px table floor + the card's `overflow: auto` (L2836) mean the phone scrolls the CARD, never the page):
|
||||
- `.settings-table { min-width: 720px; }`
|
||||
- `.settings-value-cell, .settings-value-th { min-width: 13rem; }`
|
||||
No other rule moves. `13rem` / `720px` are bare length literals — NO new CSS custom property (B3 binds palette variables; a length in the mobile block is the idiom the neighboring `width: 100%` rules already use).
|
||||
3. `frontend/assets/styles.css` L4742–4746 — update the `.settings-table` comment to note the mobile block (≤640px) raises the floor to 720 px for the Value column (one line; the rule itself stays here at 560 px for ≥641 px viewports).
|
||||
|
||||
- ASSUMPTION: A1 (mobile-only scope — the rules live ONLY in the ≤640px block; desktop byte-identical), A2 (the width values — 720 px table floor, 13rem Value floor) — both owner-locked 2026-10-12 in the phase-176 roadmap confirmation. No runtime guesses.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: `tests/unit/test_settings_frontend_view.py` — the existing pins (incl. `test_styles_css_settings_block_uses_existing_tokens_only` L516) must stay green; the task-02 pins cover the new rules.
|
||||
- Coverage: **>90%** on `app/` (unchanged code — the gate must stay green, not drop).
|
||||
- Lint/types: `uv run ruff check . && uv run pyright`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] In the ≤640px block of `styles.css`, the two new rules are present with the exact values (720 px / 13 rem) and NO `settings-value` rule appears outside that block (grep: the selectors occur only once, inside `@media (max-width: 640px) … }`).
|
||||
- [ ] `frontend/index.html` L1602 carries `class="settings-value-th"`; `settings.js` is byte-unchanged (`git diff --stat` shows only `frontend/index.html` + `frontend/assets/styles.css` + the test pin).
|
||||
- [ ] `uv run pytest tests/unit/test_settings_frontend_view.py -v --no-cov` green; ruff + pyright clean.
|
||||
- [ ] No `app/` change; the ≥641 px rules are byte-unchanged.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Task 02 — unit pins: the two mobile width rules + the `settings-value-th` class
|
||||
|
||||
**Phase:** `176_mobile_settings_value_width` · **Source:** `TODO.md:3` — "On mobile, the settnigs values are impossibly small. This is a horizontally scrollable table so we can have slightly wider input fields."
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
Pin task 01's CSS in `tests/unit/test_settings_frontend_view.py` the house way (the phase-168/171 static-pin idiom — `re.search` the rule body, assert the declarations): the two new rules exist with their exact values **inside the ≤640px block only**, and the `settings-value-th` class is on the settings `thead`'s Value `th`. The existing pins — incl. `test_styles_css_settings_block_uses_existing_tokens_only` (L516) — stay green: the new rules add a bare length, no `:root` custom property (B3 holds).
|
||||
|
||||
## Work
|
||||
1. `tests/unit/test_settings_frontend_view.py` — a new section `# ---------- phase 176 (task 01): the mobile Value-column floor ----------` after the phase-171 section (after `test_settings_save_disabled_is_just_disabled_no_wait_cursor`, L714), with ONE test function `test_settings_mobile_value_column_floor` (the house one-test-per-contract idiom; the function docstring cites `TODO.md` L3 + owner-locked A1/A2, the way the phase-171 docstrings cite their TODO line):
|
||||
- Extract the ≤640px block: `mobile = re.search(r"@media \(max-width: 640px\) \{([\s\S]*?)\n\}\n\n/\* Phase 46", css)` is too brittle — instead pin the rules by SELECTOR SCOPING: assert `css.count(".settings-value-cell, .settings-value-th")` is **exactly 1** and that the occurrence sits INSIDE the ≤640px block (locate the `@media (max-width: 640px)` opening at L6180 and the block's closing `}` at the end of the media region — the test may slice `css[mobile_open:media_close]` with the open/close markers; the phase-171 pin `test_settings_content_is_a_flex_column_with_the_shell_gap` (L589) already sets the rule-extraction idiom to follow).
|
||||
- In the mobile block: the `.settings-table` rule declares `min-width: 720px` (the mobile floor — the base L4742 rule keeps `min-width: 560px` for ≥641 px; pin BOTH: the base `.settings-table` rule body contains `min-width: 560px` and NO `720px`, the mobile-block `.settings-table` rule body contains `min-width: 720px`).
|
||||
- In the mobile block: the Value rule declares `min-width: 13rem` on both `.settings-value-cell` and `.settings-value-th`.
|
||||
- `index.html` (via the file read the settings-view pins already use, `_read(FRONTEND / "index.html")`): the settings `thead` (the `table.settings-table` region — the `#settings-tbody` anchor) contains `<th scope="col" class="settings-value-th">Value</th>`.
|
||||
2. `tests/unit/test_settings_frontend_view.py` — verify `test_styles_css_settings_block_uses_existing_tokens_only` (L516) still passes UNCHANGED (the new rules are inside the media block, not the settings block's `:root` scan scope; if its no-new-custom-property scan is a global grep for `--settings-` or similar, confirm it does not match `13rem`/`720px` — it cannot, they are bare lengths; run it and record the pass).
|
||||
|
||||
- ASSUMPTION: the media-block extraction uses open/closing markers (not a hardcoded line range) so the pin survives the two-line insertion above it — the phase-171 docstring's "the 640px mobile stacking composes with it" contract is the same robustness bar. (Not an owner decision: test-internal mechanics.)
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: `tests/unit/test_settings_frontend_view.py` — the new pin + all existing settings pins green.
|
||||
- Coverage: **>90%** on `app/` (unchanged code).
|
||||
- Lint/types: `uv run ruff check . && uv run pyright`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `uv run pytest tests/unit/test_settings_frontend_view.py -v --no-cov` green, incl. the new `test_settings_mobile_value_column_floor` and the untouched `test_styles_css_settings_block_uses_existing_tokens_only`.
|
||||
- [ ] The pin fails if either mobile rule is removed or moved out of the ≤640px block (mutation check: temporarily drop the rule → red → restore).
|
||||
- [ ] `frontend/` and `app/` are byte-unchanged by this task (the diff is `tests/unit/test_settings_frontend_view.py` only).
|
||||
- [ ] ruff + pyright clean.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Task 03 — E2E: the mobile Value-column width contract + regression + gates + commit
|
||||
|
||||
**Phase:** `176_mobile_settings_value_width` · **Source:** `TODO.md:3` — "On mobile, the settnigs values are impossibly small. This is a horizontally scrollable table so we can have slightly wider input fields."
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
Prove the width contract in a real browser (AGENTS rule 4 — the phase's dedicated Playwright suite, run **in isolation**), prove the desktop is unchanged, run the regression set + full gates, and commit the phase atomically.
|
||||
|
||||
## Work
|
||||
1. `tests/e2e/test_settings_mobile_value_width.py` — the phase suite, structured on `tests/e2e/test_admin_settings_layout.py` (phase 171) VERBATIM where the contract allows (module docstring citing `TODO.md` L3 + the test→contract mapping; the `_boot_app` / `app_server` fixture with `env["BOR_SETTINGS_DB"] = "1"`; the per-test `_clean_settings_state` truncation of `app_settings` / `ui_settings` + the `e2e-`-labeled token cleanup — never a TRUNCATE on `api_tokens`; the module app env block; `login` from `e2e.auth_helpers`; `expect` from `playwright.sync_api`). Tests (each at the stated viewport via `page.set_viewport_size({"width": …, "height": 900})`):
|
||||
- **`test_mobile_value_inputs_are_usable_width`** — at **375 px**: the admin arrives at the settings view (the phase-168 gate flow); for a representative spread of rows (at least one `type="search"`/text input, one `select`, one textarea — the catalogue is 75 rows; pick three distinct control types), the `.settings-control` bounding-box width is **≥ 192 px** (12 rem — owner-locked A3, the deliberate margin under the 13 rem rule) AND ≥ 200 px wide is NOT asserted (the 12 rem pin is the contract; 13 rem ≈ 208 px is the rule). Also the `.settings-table`'s own width (`scrollWidth`) **exceeds** the `.table-wrap` card's `clientWidth` (the horizontal scroll is active on the phone — the TODO's own premise) while `document.documentElement.scrollWidth ≤` the 375 px viewport (the PAGE does not overflow — the phase-171 pin, the scroll is the card's).
|
||||
- **`test_mobile_table_still_scrolls_and_page_does_not_overflow`** — at **375 px**: `.table-wrap` computed `overflow` is `auto`; the card's `scrollWidth > clientWidth`; the page-level `documentElement.scrollWidth` ≤ 375 px + 1 px rounding; the sticky `thead` contract survives (the first row's group band/`thead` is reachable — scroll the card and the `thead` `th`s stay pinned, computed `position: sticky`).
|
||||
- **`test_desktop_value_width_is_unchanged`** — at **1440 px**: a fixed probe row (the same first `type="search"` control) has a width STRICTLY GREATER than at 375 px is the wrong shape — the contract is the media query is INERT: the 1440 px `.settings-control` width equals the pre-phase natural layout; pinned as: the mobile rules do not apply (computed `min-width` of `.settings-value-cell` at 1440 px is the UA `0px`/auto — i.e. `getComputedStyle` returns `0px` or `auto`, never `208px`/`208.0px`) and the `.settings-table` `scrollWidth` ≤ the card's `clientWidth` (no forced scroll at desktop — the 720 px floor binds only inside the media query).
|
||||
- **`test_keyboard_order_and_no_external_requests`** — at **375 px**: the tab order reaches `#settings-filter` → the first row's Value control → `#settings-save` → `#settings-clear-all` (the phase-168/171 contract under the new column floor); the whole test's `page.on("request")` log contains ZERO external (non-127.0.0.1) requests (rule 6).
|
||||
- **The DB-isolation docstring + module teardown** (the phase-171 idiom): the suite's only write is none (no save is issued — the width contract needs no dirty row); `app_settings` pinned empty at teardown.
|
||||
2. Run the **regression set** in isolation (each its own run, per the phase-171 completion criteria):
|
||||
- `uv run pytest tests/e2e/test_settings_mobile_value_width.py -v --no-cov` (THIS phase — must be green in isolation),
|
||||
- `uv run pytest tests/e2e/test_admin_settings_layout.py -v --no-cov` (phase 171 — the rhythm/band/cursor + the 375/1440 no-page-overflow pins),
|
||||
- `uv run pytest tests/e2e/test_admin_settings_panel.py -v --no-cov` (phase 168 — the panel contract).
|
||||
3. Run the **full gates**: `uv run pytest` (green), `uv run pytest --cov=app --cov-report=term-missing` (**>90%** on `app/` — this phase changes no `app/` code: the gate stays green, not dropped), `uv run ruff check . && uv run pyright` (clean).
|
||||
4. **Commit** the phase atomically (AGENTS rule 8):
|
||||
```bash
|
||||
git add frontend/ tests/ .agents/phases/ && git commit --no-gpg-sign -m "fix(settings): mobile settings table — a usable Value-column width (the card's horizontal scroll absorbs it)"
|
||||
```
|
||||
then move the phase dir: `mv .agents/phases/todo/176_mobile_settings_value_width .agents/phases/complete/176_mobile_settings_value_width` (the dir move is part of the same phase completion — commit it with `git add .agents/ && git commit --no-gpg-sign -m "chore(agent): complete phase 176 mobile settings value width"` ONLY if the repo's convention is a separate commit; the phase-171 precedent shows a single commit — follow it: the dir move is folded into the atomic commit by `git add .agents/phases/` AFTER the `mv`).
|
||||
|
||||
- ASSUMPTION: A3 (the 12 rem / 192 px E2E pin threshold, the deliberate margin under the 13 rem rule) — owner-locked 2026-10-12. The probe-row choice (one text input, one select, one textarea) is executor mechanics within that lock: the three control types must exist among the 75 rows (the phase-168 catalogue guarantees text / number / select / textarea / password controls).
|
||||
|
||||
## Testing & Quality
|
||||
- E2E (AGENTS rule 4): `tests/e2e/test_settings_mobile_value_width.py` green **in isolation**; phase 171 + phase 168 suites green in isolation (regression).
|
||||
- Coverage: **>90%** on `app/` (unchanged code).
|
||||
- Lint/types: `uv run ruff check . && uv run pyright`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `uv run pytest tests/e2e/test_settings_mobile_value_width.py -v --no-cov` green in isolation (the 375 px input ≥ 192 px, the card scrolls, the page does not; 1440 px inert; keyboard + zero external requests).
|
||||
- [ ] `uv run pytest tests/e2e/test_admin_settings_layout.py -v --no-cov` and `uv run pytest tests/e2e/test_admin_settings_panel.py -v --no-cov` green in isolation.
|
||||
- [ ] `uv run pytest` green; `app/` coverage >90%; ruff + pyright clean.
|
||||
- [ ] One atomic `--no-gpg-sign` Conventional Commit (the phase-171 single-commit precedent, dir move folded in); the phase dir lives in `.agents/phases/complete/176_mobile_settings_value_width/`.
|
||||
Reference in New Issue
Block a user