Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs/designs/822-mui-revamp.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ One breakpoint. MUI's default `md` is 900px, the same number as the app's only w

**Amendment (#830):** row 210's 390 column says "the footer stacks its buttons (D3.4)" — that predates §7's own record of the exception. #823 already carried the correction (its own paragraph below says so): the confirmed mockup (`docs/designs/864-visual-language/daily-entry.html`, `grid-template-columns: 1fr 1fr`) keeps Save draft and Submit day side by side at 390, and #823 wrote `.entry-foot .actions { flex-direction: row }` specifically as D3.4's one exemption. #830 keeps that arrangement — its sticky footer (a MUI `Paper elevation={4}`, replacing `.entry-foot`) lays the two saves out `display: grid; grid-template-columns: 1fr 1fr` at every width, unconditionally, because #823's row/column split was itself conditional on the 900px breakpoint and #830's footer never stacks at all. This is not a new decision, only this row catching up to one #823 and #740's own scope line already recorded.

**D3.4 The phone action-button rule (#740).** Below 900px an action row (`.actions`, `DialogActions`) **stacks vertically and each button is full width**: `flexDirection: column`, `alignItems: stretch` (MUI's `DialogActions` root sets `alignItems: center`, `DialogActions.js:39`, which would leave stacked buttons at intrinsic width; `.actions` already stretches by default), `flex: 1 1 100%`, DOM order preserved (the primary is already first, `SalesPage.tsx:1493`). At 1280 buttons are `flex: 0 0 auto`, never `flex: 1`. The pill radius stays; it never resolves to an ellipse because a full-width button at 390 is about 353px wide (390 less `.content`'s 1.15rem side padding, L2830) and one line tall (the #740 measurement `91.5 x 103.2` at 390 becomes about `353 x 46`). This is #740's option 1 and it needs no per-label tuning, so `tl`'s longer strings are covered by construction. **The Daily Entry footer is exempt, not a third member of this stacking set** — see the #830 amendment above: it keeps Save draft and Submit day side by side at every width, `.entry-foot .actions { flex-direction: row }` under #823 and #830's own unconditional grid after.
**D3.4 The phone action-button rule (#740).** Below 900px an action row (`.actions`, `DialogActions`) **stacks vertically and each button is full width**: `flexDirection: column`, `alignItems: stretch` (MUI's `DialogActions` root sets `alignItems: center`, `DialogActions.js:39`, which would leave stacked buttons at intrinsic width; `.actions` already stretches by default), `flex: 1 1 100%`, DOM order preserved (the primary is already first, `SalesPage.tsx:1493`). At 1280 buttons are `flex: 0 0 auto`, never `flex: 1`. The pill radius stays; it never resolves to an ellipse because a full-width button at 390 is about 353px wide (390 less `.content`'s 1.15rem side padding, L2830) and one line tall (the #740 measurement `91.5 x 103.2` at 390 becomes about `353 x 46`). This is #740's option 1 and it needs no per-label tuning, so `tl`'s longer strings are covered by construction. **The Daily Entry footer is exempt, not a third member of this stacking set** — see the #830 amendment above: it keeps Save draft and Submit day side by side at every width, `.entry-foot .actions { flex-direction: row }` under #823 and #830's own unconditional grid after. **CRUD dialog footers (`DialogActions`, the five screens in #832) are a second, later exception to the same stacking rule** — see the #832 amendment below: #896 (merged mid-slice) established that a dialog footer stays row/right-aligned at every width, which is `DialogActions`' own unmodified MUI default rather than a rule this file writes, and #832 both converted the CRUD screens' `.dialog-foot` divs to `DialogActions` with that row layout preserved and deleted a stale pre-#896 `MuiDialogActions` phone override that still forced the stack. The stacking rule above still governs every other action row, `.actions` and any `DialogActions` outside these five screens' CRUD dialogs alike, until each carries its own exemption the way these two do.

**The rule lands in #823, and at #823 it is a CSS change, not a theme one.** Until #827 every dialog foot is `Dialog.tsx`'s raw `<button>`s and until #828 every action is `BusyButton`'s raw `<button>` (`BusyButton.tsx:26`), so `MuiButton`/`MuiDialogActions` overrides reach nothing yet; #823 writes them for #827/#828 to inherit, and closes #740 through `styles.css`: `.actions` (L525-529, phone L2985-2990) and `.dialog .dialog-foot` (L757) get `flex-direction: column` inside the 900px query, and the two `flex: 1` rules (L3072-3075, L3077-3080) go. Sticky when stacked: **no** for `.actions` (L2985-2988's `position: sticky` is removed, because three stacked buttons above the tab bar are about 170px of chrome on an 844px viewport) and **yes** for `.entry-foot`, which stays the field-first capture bar. #740 closes with #823. Three things make that a real close: `phone.spec.ts:198` already asserts "no action control is taller than it is wide" over `/daily-entry` and its comment at L237-247 says to extend the walk to `/sales` when #740 lands and delete the comment, so #823 does exactly that; the mutant `phone-action-label-wrapped` (`src/mutants.ts:910`) is **re-targeted** in the same PR, because a full-width button cannot become taller than wide however long its label, so the current mutant would survive and `mutation-check.sh` would report it: the spec gains "each `.actions button` at 390 spans at least 90% of its container" and the mutant restores `flex-direction: row`, with the new healthy and mutated boxes recorded in `phone.spec.ts` where its measurements live, and the `footBox.y + footBox.height <= barBox.y` assertion re-run after stacking (its recorded margin today is 2.2px). Stacking also reverses a recorded F134 decision: `styles.css:2993-2996` buys back 2.3rem of width so the two daily-entry footer buttons sit side by side within a thumb's reach, so that row is in §7 for the owner; and #740's own scope line asks for a pass over the app at 420px in `es` and `tl`, so #823's PR body lists the 6 `.actions` sites in 5 files (`SalesPage` has two, L1492 and L1612, and L1492 is the #740 reproduction) and the 2 dialog-foot rules it changed. #830 states "closed by #823" and does nothing else about it. The correction #823 carries: #740 is filed as an `es`/`tl` defect and is not one (§1).

Expand Down Expand Up @@ -319,7 +319,7 @@ Order is by dependency, then by what each slice teaches the next. **Deletions ar
| 7 | **#829** Dashboard + shell | D3.3 row 1; D5; pairs 12, 13, 14, 19; delete the sidebar and bottom-nav families L1315-1558 (less the two label rules), `.panel`/`.dash-grid`/`.panel-wide` (L1705-1708, L1733-1742) and the capture tiles and recent-sales list (L1744-1802, L2178-2263); **keep** `.content` (§2.2) and the shared `.card, .panel, .order-panel` rule at L1697-1703, whose `.card` and `.order-panel` consumers convert in #831 to #833; retire `styles.test.ts` L251-374 | the whole app frame: sidebar and Dashboard at 1280; bottom bar, house cards and More sheet at 390 |
| 8 | **#830** Daily entry | D3.3 row 2 (amended above: the footer stays side by side, never stacked); §2.2's `entry-context`/`entry-foot*` (a #830-only scope, re-derived by call-site count rather than the L800-1029/L1094-1151 range this row once named — `.entry-step`, `.entry-cols`, `.entry-pane`, `.entry-row`, `.entry-chip*`, `.entry-take`, `.step-n` and the numfield block all stay, because HistoryPage's adjust dialog mirrors this layout and still renders them) | the two panes at 1280 side by side; at 390 each pane full-width and the footer's two saves side by side |
| 9 | **#831** ledgers (+ Feed, Water, Reports) | D3.3 row 3; pairs 7, 9, 10, 11, 15; delete `.order-panel` (L1728-1731, last consumer Flocks moves to #832 if Flocks converts later) and `tr.discounted .badge-warn` (L1225-1253, Sales only); **keep** `.toolbar` L1168-1193 (seventh consumer is `AuditPage.tsx:432`, #833, and deleting early reopens #653), `table.data` L1623-1695 and `tr.inactive td` L1686 (the five CRUD lists in #832 and Audit in #833 still render them), `.page-head` L1571, and `.muted`/`.error`/`.success`/`.warn` (L1195-1224; `.muted` alone has 103 sites in 24 files including `ErrorBoundary`, `EmptyState` and `AppLayout`, and maps to `Typography color="text.secondary"` screen by screen under pair 10); one PR per screen when the diff is large (Sales alone has 4,110 test lines) | each screen's table and filter bar at both widths; Sales with a drill-down open |
| 10 | **#832** CRUD lists | D3.3 row 4; Flocks' `.order-panel` | each list and one open form dialog at both widths; Users' RBAC behaviour is fixed (3,872 test lines) |
| 10 | **#832** CRUD lists | D3.3 row 4; Flocks' `.order-panel` | each list and one open form dialog at both widths; Users' RBAC behaviour is fixed (~3,900 test lines) |
| 11 | **#833** tail (+ Account, SetPassword) | D3.3 row 5; pairs 16, 20; delete the login and help blocks L2264-2796 (including `.glossary-entry:target`), the farm-settings block L3090-3278 (`.update-banner` L3279-3322 went with #828), the L316-534 remainder, and as last consumer: `.toolbar` L1168-1193, `table.data` L1623-1695, `.page-head` and `.content` L1559-1622, the badge rules L1275-1296, the semantic-text rules L1195-1224, and the shared `.card` rule; retire the last CSS guards | Login in both themes, Help, Settings, Audit at both widths |
| 12 | **#834** link colour | D7.1 (superseded by the owner's DIRECTION.md decision, 2026-09-16 — see the amendment on D7.1) | Dashboard, a list screen and Login in both themes, all four palettes (`playwright.palettes.config.ts` already captures this set) |
| 13 | **#835** opsz | D7.2 | a page title and a figure at 1280 before and after, 1:1 |
Expand All @@ -328,6 +328,15 @@ Order is by dependency, then by what each slice teaches the next. **Deletions ar

#666 and #667 are closed (verified via `gh issue view`); the revamp inherits their `.toolbar` through pair 7 and absorbs nothing.

**Amendment (#832, landed 2026-09-17, out of the order this table names).** #832 shipped before #831 (real landing order tracks a coordinator's own sequencing, not this table's dependency order), which changes four things this section assumed:

- **`.order-panel` was NOT deleted.** Row 9's "last consumer Flocks moves to #832 if Flocks converts later" resolved to false: at #832's head, Sales, Inventory and Expenses (#831's own scope) still render `className="order-panel"` (verified by whole-repo `git grep` before and after the edit), so the rule — and the shared `.card, .order-panel` background/border/radius rule it partly grouped with — stays in `styles.css`. Flocks' own drill-down converts to pair 15's ruled region and simply stops using the class; #831 is still the slice that gets to delete it, when it makes Sales/Inventory/Expenses the last consumers.
- **Table, form and shared-component conversion happened before ledger conversion, reversing this row's own dependency order for pair 9.** #832 is therefore the first slice to mount a real MUI `Table`/`TableContainer` anywhere in the app, not #831. Two consequences #831 and #833 inherit for free: `FarmThemeProvider.tsx` gained `MuiTableCell` (row-scale font size/line-height, `fontVariantNumeric: "tabular-nums"`, replacing the "KNOWN GAP" comment #882's review left) and `MuiTableContainer` (`contain: "layout"` under the phone breakpoint — a real #441 repro, found by `phone.spec.ts`'s own overflow walk against a live build of `/flocks`: `TableContainer`'s default `overflow-x: auto` scrolls a wide table within itself but does not stop a mobile browser's initial layout-viewport sizing from measuring the table's raw content width, exactly the defect `styles.css`'s `table.data` phone rule already carries `contain: layout` to close). `ProvenanceCell` (shared by Grades/Flocks here and by Sales/Expenses/History in #831) converted to a `TableCell` with its two retired classes (`nowrap`, `provenance-cell`) as explicit `sx`, with the padding pinned to `table.data td`'s exact legacy values rather than left to density context — a deliberate bridge so the three #831 screens, still on a plain `<table className="data">`, render pixel-identical until their own turn.
- **Scope was narrower than pairs 9/10/11/17 suggest.** The brief cutting this slice limited it to pair 9 (tables), pair 11 (dialog forms) and pair 15 (Flocks only), explicitly leaving pair 10 (`.page-head` → `Stack`/`Typography`), pair 17 (`p.error`/`p.warn` → `Alert`) and pair 8 (`BusyButton`/`button.link` → `Button`) untouched — row 9's own text above ("maps to `Typography color=\"text.secondary\"` screen by screen under pair 10") assumed #832 would carry those too. It does not: `.page-head`, `.muted`, `.error`, `.success`, `button.link`, `StatusBadge` and `BusyButton` are unchanged in all five screens, confirmed still shared with a dozen unconverted screens by whole-repo grep, and are #828's/#831's/#833's to convert.
- **The CRUD dialogs' footers moved to `DialogActions` in a follow-up commit inside the same PR**, prompted by #896 (merged to `main` mid-slice): a dialog footer now stays row/right-aligned at phone width rather than stacking, which is exactly `DialogActions`' own unmodified default, so the 17 `.dialog-foot` divs across the five screens became `DialogActions` with their content untouched. That also surfaced a stale `MuiDialogActions` phone override (pre-dating #896, never updated for it) that still stacked — deleted, with its two now-vacuous tests retired as a named, deliberate coverage reduction.

Only one CSS deletion actually landed with #832: `.dialog .confirm-body` / `.dialog .confirm-body strong` (UsersPage's disable-warning paragraph was its last consumer anywhere in the repo; `useConfirm.tsx` had already dropped it in #827). Every other class family the five screens used — `table.data`, `.page-head`, `.form-grid`/`.inline-form`, `.actions`, `tr.inactive`, `.dialog-foot`, `.numfield-field`, `.named-picker-trigger`, `.hint`, `.check`, `.cell`, `td.nowrap` — is still shared with an unconverted screen, confirmed by grep before touching each one.

### D9. The precache ceiling (#825). Owner review

Ceiling **1,800 KiB** of precache, enforced in CI by extending `scripts/verify-sw.mjs` (it already extracts the manifest at L150-161; it sums the listed files' sizes from `dist/` and fails above the ceiling). Derivation: MUI plus the realistic kit measured 1632.68 KiB; D7.2 adds 118.9 KiB; that is 1751.6 KiB, and 1,800 leaves 2.7% for the small controls. The alternative is a ceiling below the unoptimised kit measurement (1,600 KiB) on the argument that this epic deletes as it adds and a ceiling above the measurement is not a ceiling. Its cost is stated plainly: #825 installs the gate at order 3, before any deletion slice, and no deletion estimate exists yet (the hand-built controls are TSX, which the precache counts as part of the JS chunk, and nobody has measured what #826 and #827 remove), so a 1,600 ceiling can go red at #828 and stay red until enough hand-built code is gone; it also forces the `opsz` fallback in D7.2. #825 measures the JS delta of deleting `NamedEntityPicker` and `Dialog` on a throwaway branch before the number is chosen, so the choice is decidable. Either way: above the ceiling a slice must retire hand-built code to land, and a slice that adds more than 10 KiB of precache without deleting code names the reason in its PR body. The check is a `pull_request` check in the `web` job, so a documentation-only PR skips it (#782), which is correct: such a PR changes no bundle input. Script-time ceiling per #674: re-measure with the record's method when #826 lands, since `Autocomplete` is the component the record named as needing its own number.
Expand Down
28 changes: 20 additions & 8 deletions tools/simulation/ui/mutation-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ declare -A SPEC_FOR=(
[phone-tabs-inert]="specs/phone.spec.ts"
[phone-action-label-wrapped]="specs/phone.spec.ts"
[phone-entry-foot-stacked]="specs/phone.spec.ts"
[phone-dialog-footer-stacked]="specs/phone.spec.ts"
)

# --- the mutant -> project map ---------------------------------------------
Expand Down Expand Up @@ -151,6 +152,7 @@ declare -A PROJECT_FOR=(
[phone-tabs-inert]="chromium-phone"
[phone-action-label-wrapped]="chromium-phone"
[phone-entry-foot-stacked]="chromium-phone"
[phone-dialog-footer-stacked]="chromium-phone"
)

# The project whose WHOLE suite must still be GREEN under this mutant, checked
Expand All @@ -164,6 +166,7 @@ declare -A MUST_STAY_GREEN_ON=(
[phone-tabs-inert]="chromium"
[phone-action-label-wrapped]="chromium"
[phone-entry-foot-stacked]="chromium"
[phone-dialog-footer-stacked]="chromium"
)

# The third test in a11y-live-regions.spec.ts (recorded browser facts) has no
Expand Down Expand Up @@ -220,6 +223,7 @@ declare -A GREP_FOR=(
[phone-tabs-inert]="the tab bar is the navigation at this width"
[phone-action-label-wrapped]="no action control is taller than it is wide"
[phone-entry-foot-stacked]="no action control is taller than it is wide"
[phone-dialog-footer-stacked]="no action control is taller than it is wide"
)

# Mutants whose RED is known not to prove the guarantee they name. See the header.
Expand Down Expand Up @@ -258,12 +262,20 @@ declare -A FALSE_KILLS=(
# nav gate, and is still counted as a false kill rather than as coverage.
#
# The three phone entries were observed the same way. Two of them carry a custom
# message; `phone-table-overflow-unclipped` declares FOUR lines, one per route it
# breaks, because the softness of that walk is itself the claim — a hard
# assertion would stop at /sales and report a quarter of the damage, so requiring
# all four is what keeps `expect.soft` there honest. /daily-entry and /stock are
# message; `phone-table-overflow-unclipped` declares TWO lines, one per route it
# still breaks, because the softness of that walk is itself the claim — a hard
# assertion would stop at /sales and report half the damage, so requiring both
# is what keeps `expect.soft` there honest. /daily-entry and /stock are
# deliberately absent: neither renders a wide data table, and both stayed at
# exactly 390 under the mutant.
# exactly 390 under the mutant. /customers and /flocks were also on this list
# until #832: the mutant's CSS targets `table.data` specifically, and #832
# moved both routes onto MUI's `TableContainer`, which the mutant's rule does
# not reach — observed directly (`CLUCKWORK_E2E_MUTANT=phone-table-overflow-unclipped`
# against a #832 build): only /sales and /history still overflow. This is a
# real narrowing of what the mutant proves, not a typo; if a later slice moves
# /sales or /history onto MUI too, this mutant stops proving anything at all
# and needs a new CSS target (MUI's `TableContainer`, not `table.data`) or
# retirement, matching #824's "retire only with a named successor" rule.
#
# The two phone action mutants split the walk's rule between them, and each
# declares only what it can actually redden. #823 stacks every action row below
Expand Down Expand Up @@ -318,9 +330,9 @@ SIDE 2 — role=alert no longer carries implicit assertive politeness"
[phone-action-label-wrapped]="in the Sales draft-order panel spans
taller than it is wide, so its pill clamps into an ellipse"
[phone-entry-foot-stacked]="in the daily-entry save bar spans"
[phone-dialog-footer-stacked]="dialog footer's row is not laid out as a row (computed flex-direction: column)
dialog footer's buttons share no common vertical band"
[phone-table-overflow-unclipped]="/sales scrolls sideways at phone width
/customers scrolls sideways at phone width
/flocks scrolls sideways at phone width
/history scrolls sideways at phone width"
)

Expand All @@ -339,7 +351,7 @@ if [ ${#MUTANTS[@]} -eq 0 ]; then
a11y-probe-off-role-dropped
phone-action-bar-under-tabbar phone-tabbar-removed
phone-table-overflow-unclipped phone-action-label-wrapped
phone-entry-foot-stacked
phone-entry-foot-stacked phone-dialog-footer-stacked
phone-tabs-inert)
fi

Expand Down
Loading
Loading