phase: 176_mobile_settings_value_width
All documentation claims verify against the code. Everything is green — final verification complete.
## Phase 176 — Final Verification Report
- **Verified (all 3 tasks in `complete/`):** the mobile Value-column fix is in the working tree — `frontend/index.html` L1602 carries `class="settings-value-th"`; `frontend/assets/styles.css` has exactly two new rules inside the `@media (max-width: 640px)` block: `.settings-table { min-width: 720px }` and `.settings-value-cell, .settings-value-th { min-width: calc(13rem + 2rem) }`; `settings.js` + `app/` byte-unchanged (diff = index.html + styles.css + unit test only).
- **Documented deviation (flagged in task-03 report, not silent):** spec's `min-width: 13rem` on the border-box cell left the control at 176px (< the 192px A3 pin); executor corrected the cell to `calc(13rem + 2rem)` so the control (100% of content) lands at exactly 13rem ≈ 208px — the A2 lock intent preserved, unit pin + CSS comment updated to match.
- **Tests/lint/coverage (exact commands, all green):** `uv run pytest --cov=app` → **4138 passed**, `app/` coverage **99%** (>90% ✓); `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings.
- **Completion criteria:** 375px controls ≥ 192px — ✅ (E2E `test_mobile_value_inputs_are_usable_width`); card scrolls / page doesn't overflow at 375px — ✅ (`test_mobile_table_still_scrolls_and_page_does_not_overflow`); 1440px media query inert — ✅ (`test_desktop_value_width_is_unchanged`); tab order + zero external requests — ✅; phase E2E isolated — ✅ 4/4; phase 171 isolated — ✅ 5/5; phase 168 isolated — ✅ 12/12; unit pins — ✅ 17/17; commit/move — the harness commits; phase dir already in `complete/`.
- **Documentation:** README.md and AGENTS.md reviewed in full — every claim spot-checked against code (E2E ports 8123/8901, defaults `BOR_COMPRESS_MIN_BYTES=860` / `BOR_LOG_LARGE_RESPONSE_BYTES=65536`, audit budget 262144, `.cell-clip`, `.bubble:empty`, `min(70vh, 64rem)`, aria-pressed toggle, CDN integration test, `.gitignore` entries, all referenced scripts/routes) — verified consistent, no changes needed.
- **Notable:** `image-1.png` (owner's TODO screenshot, referenced by the phase overview) sits untracked in the tree — left alone; not part of the phase's commit scope.
- **Next pending phase:** `177_themed_favicon`.
This commit is contained in:
@@ -224,11 +224,15 @@ def test_settings_content_scaffold_and_feedback_regions() -> None:
|
||||
assert '<caption class="visually-hidden">' in thead, (
|
||||
"the caption is visually-hidden (the phase-86 table pattern)"
|
||||
)
|
||||
assert thead.count('<th scope="col">') == 4, (
|
||||
assert thead.count('<th scope="col"') == 4, (
|
||||
"the table is four columns (Setting | Value | Source | Actions)"
|
||||
)
|
||||
assert '<th scope="col">Setting</th>' in thead
|
||||
assert '<th scope="col">Value</th>' in thead
|
||||
assert '<th scope="col" class="settings-value-th">Value</th>' in thead, (
|
||||
"phase 176: the Value th carries the .settings-value-th class "
|
||||
"(the mobile 13rem column floor constrains the header cell "
|
||||
"under auto table layout, not just the td's)"
|
||||
)
|
||||
assert '<th scope="col">Source</th>' in thead
|
||||
assert (
|
||||
'<th scope="col"><span class="visually-hidden">Actions</span></th>'
|
||||
@@ -764,6 +768,121 @@ def test_settings_save_disabled_is_just_disabled_no_wait_cursor() -> None:
|
||||
)
|
||||
|
||||
|
||||
# ---------- phase 176 (task 01): the mobile Value-column floor ----------
|
||||
|
||||
|
||||
def test_settings_mobile_value_column_floor() -> None:
|
||||
"""Phase 176 (task 01; TODO.md L3, owner-locked A1/A2): on a
|
||||
phone (≤640px) the Settings table's Value inputs rendered 2–3
|
||||
characters wide — the Setting cells' nowrap labels (the
|
||||
``.settings-table td`` rule) exhausted the table's 560px floor
|
||||
and collapsed the ``min-width: 0`` Value cells. The owner's
|
||||
direction: "the table is horizontally scrollable, so we can have
|
||||
slightly wider input fields." Pinned here (static, no browser):
|
||||
|
||||
* the Value column — ``.settings-value-cell`` (the td) and
|
||||
``.settings-value-th`` (the th; task 01 added the class so the
|
||||
column floor constrains the header cell too, under auto table
|
||||
layout) — gets a ``min-width: calc(13rem + 2rem)`` floor: the
|
||||
global border-box model (``* { box-sizing: border-box }``)
|
||||
makes a cell's min-width a BORDER-box, so the cell is floored
|
||||
at the locked 13rem of CONTENT (≈208px ≈ 14 mono chars at the
|
||||
controls' 0.85rem) plus the td's own 1rem × 2 padding — the
|
||||
``.settings-control`` (``width: 100%`` of the cell's content)
|
||||
thus lands at the locked 13rem (the A2 intent, "slightly
|
||||
wider input fields", and the A3 pin's 12rem / 192px threshold
|
||||
with a 16px margin); a bare ``13rem`` border-box would only
|
||||
leave 176px of content (the control), under the A3 pin, and
|
||||
* the table's mobile scroll floor is raised to ``720px`` while
|
||||
the base rule keeps ``560px`` (≥641px — the desktop layout
|
||||
stays byte-identical, A1/A2).
|
||||
|
||||
Both rules live ONLY inside the existing ``@media (max-width:
|
||||
640px)`` block (A1 — mobile-only scope): the selectors occur
|
||||
exactly once in the whole file, and that one occurrence sits
|
||||
inside the media block (the slice is by open/close markers —
|
||||
the inner rules are 2-space-indented, so the first column-zero
|
||||
``}`` after the open is the block's close; no hardcoded line
|
||||
range, so the pin survives an insertion above it). The 720px
|
||||
floor + the ``.table-wrap`` card's ``overflow: auto`` mean the
|
||||
phone scrolls the CARD, never the page (the phase-171 "no
|
||||
horizontal page overflow" E2E pin holds). 720px is a bare
|
||||
length literal and the cell floor is the locked 13rem of content
|
||||
+ the cell's 2rem of padding — NO new CSS custom property (B3
|
||||
binds palette variables; a length in the mobile block is the
|
||||
idiom the neighbouring ``width: 100%`` rules already use), so
|
||||
``test_styles_css_settings_block_uses_existing_tokens_only``
|
||||
stays green unchanged."""
|
||||
css = _read(ASSETS / "styles.css")
|
||||
# --- the ≤640px block, sliced by its open/close markers.
|
||||
open_marker = "@media (max-width: 640px) {"
|
||||
mobile_open = css.index(open_marker)
|
||||
mobile_close = css.index("\n}", mobile_open + len(open_marker))
|
||||
mobile = css[mobile_open:mobile_close + 2]
|
||||
# --- the Value rule: exactly ONCE in the whole file, and that
|
||||
# one occurrence sits INSIDE the ≤640px block (A1: no desktop
|
||||
# copy, no stray rule elsewhere).
|
||||
value_selector = ".settings-value-cell, .settings-value-th"
|
||||
assert css.count(value_selector) == 1, (
|
||||
"the Value rule must occur exactly once in styles.css"
|
||||
)
|
||||
assert mobile.count(value_selector) == 1, (
|
||||
"the Value rule must sit inside the ≤640px block (A1: "
|
||||
"mobile-only — at ≥641px the rule is inert)"
|
||||
)
|
||||
value_rule = re.search(
|
||||
r"\.settings-value-cell, \.settings-value-th \{([^{}]*)\}", mobile
|
||||
)
|
||||
assert value_rule, "the Value rule must declare the column floor"
|
||||
assert "min-width: calc(13rem + 2rem)" in value_rule.group(1), (
|
||||
"the Value column (th AND td) is floored at 13rem of content "
|
||||
"(≈208px ≈ 14 mono chars — A2: 'slightly wider input fields') "
|
||||
"+ the cell's 2rem of padding (the global border-box model — "
|
||||
"a bare 13rem border-box would leave the control at 176px, "
|
||||
"under the A3 192px pin)"
|
||||
)
|
||||
# --- the table floor: BOTH rules pinned — the base keeps 560px
|
||||
# for ≥641px (and must not know 720px) while the mobile block
|
||||
# raises it to 720px (the phone scrolls the card — the
|
||||
# .table-wrap overflow: auto contract, never the page).
|
||||
base = re.search(r"\.settings-table \{([^{}]*)\}", css)
|
||||
assert base, "styles.css must style .settings-table (the base rule)"
|
||||
assert "min-width: 560px" in base.group(1), (
|
||||
"the base .settings-table floor stays 560px (≥641px — "
|
||||
"unchanged, A1)"
|
||||
)
|
||||
assert "720px" not in base.group(1), (
|
||||
"the base .settings-table rule must not know 720px (the "
|
||||
"720px floor belongs to the mobile block only, A1)"
|
||||
)
|
||||
mtable = re.search(r"\.settings-table \{([^{}]*)\}", mobile)
|
||||
assert mtable, (
|
||||
"the ≤640px block must carry the .settings-table floor rule"
|
||||
)
|
||||
assert "min-width: 720px" in mtable.group(1), (
|
||||
"the mobile table floor is 720px (A2: the phone scrolls "
|
||||
"the card horizontally)"
|
||||
)
|
||||
# The 720px declaration exists exactly once in the whole file —
|
||||
# and it is the mobile block's (no ≥641px copy anywhere).
|
||||
assert css.count("min-width: 720px") == 1 and (
|
||||
"min-width: 720px" in mobile
|
||||
), "the 720px floor must exist exactly once, in the ≤640px block"
|
||||
# --- the index.html static thead: the Value th carries the
|
||||
# .settings-value-th class so the column floor constrains the
|
||||
# header cell too (deterministic under auto table layout —
|
||||
# a plain <th> would otherwise stay unconstrained).
|
||||
html = _html()
|
||||
table_start = html.find('class="settings-table"')
|
||||
assert table_start != -1, "the settings table must exist"
|
||||
thead = html[table_start: html.find("</thead>", table_start)]
|
||||
assert '<th scope="col" class="settings-value-th">Value</th>' in thead, (
|
||||
"the settings thead's Value th carries the settings-value-th "
|
||||
"class (task 01: the column floor is deterministic under "
|
||||
"auto table layout)"
|
||||
)
|
||||
|
||||
|
||||
# ---------- the four Theme-bearing pages carry the Settings link ----------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user