Repository navigation
feat(web): add a Lay rate range switcher and page the Dashboard panels - #940
Conversation
Dashboard, before and after —
|
Recent orders and the Lay rate card, before and afterScrolled to the lower half of the same page. What changed here: the Morning After frames captured at 0b64395. |
#914 — the range switcher (after only; there is no before for a control that did not exist)Scoped by the owner on 2026-09-23: this card draws one bar per day and
The "All flocks" scope button above the control is also the contrast fix this
After frames captured at 0b64395. |
#915 — paging inside the panels (after only)
After frames captured at 0b64395. |
…ouse lists Codex gpt-6-sol review round 1 of ff5d5ee, five findings.
Presets 7 and 14, custom ranges capped at 14 days. Week bucketing and its strings are deleted rather than left unreachable; longer ranges move to #941. Also fixes the brand-on-dark contrast of every text and outlined primary Button through the theme.
…ccent MUI v6 replaced Button's textPrimary/outlinedPrimary slots with variants, so the first override was dropped in silence. A render assertion now reads the colour the DOM actually gets, which is what caught it.
Codex (gpt-6-sol) review round 1 of
|
| palette | dark, before | dark, after | light (unchanged) |
|---|---|---|---|
| aubergine | 1.18:1 | 9.35:1 | 13.78:1 |
| forest | 1.46:1 | 10.69:1 | 11.08:1 |
| slate | 1.42:1 | 10.15:1 | 11.44:1 |
| terracotta | 1.51:1 | 9.44:1 | 10.73:1 |
contained is deliberately untouched: there the brand is the background.
A false start worth recording. My first override used MUI v5's
textPrimary/outlinedPrimary slots. MUI v6 replaced those composites with
variants and separate MuiButton-text / MuiButton-colorPrimary classes, so
the override was dropped in silence — and my first policy assertion read the
override object, so it passed while the app was still broken. A capture caught
it. The assertion now resolves the colour the way MUI does, and
FarmThemeProvider.render.test.tsx renders a real Button and reads the DOM,
which is the check that cannot pass vacuously. Both were mutation-checked,
including a mutation that reinstates the v5 slot names. Fix 473e093.
Verification at 473e093
npm run typecheckclean; full web suite 3452 passed / 141 files (3403 on
origin/main).- Full quick Playwright suite on a rebuilt isolated
cw914astack, both
projects: 96 passed, 1 intentionally skipped, 0 failed. - Document guards (
ImagePin,RealTree): 14 passed. pstack:deslopre-run over the whole branch; the comment that narrated review
rounds now cites the issues instead.- Screenshots recaptured on a freshly seeded stack before any suite ran
against it, and the original comments patched in place. The earlier round's
30-day and 60-day frames are gone with the feature; 7-day, 14-day, a custom
10-day range and the 20-day rejection replace them. - web: Dashboard Lay Rate range switcher (7/15/30 days and a custom range) that collapses long ranges to weeks #914 amended per AGENTS.md, naming what ships here and what moved to web: expand the Lay rate chart into a centred overview + scrollable daily view for ranges beyond 14 days #941.
…e ceiling Codex gpt-6-sol review round 2 of 473e093, four P2s and one P3.
Codex (gpt-6-sol) review round 2 of
|
Codex gpt-6-sol review round 3 of 364ee44: three P2s and two P3s.
Codex (gpt-6-sol) review round 3 of
|
…s own read Codex gpt-6-sol review round 4 of e051bf0, two P2s.
Codex (gpt-6-sol) review round 4 of
|
…swers Codex gpt-6-sol review round 5 of b512a97: one pre-existing P2.
Codex (gpt-6-sol) review round 5 of
|
The drain regression test rendered 501 rows and clicked through 63 pages to reach the last one, 3.6s locally and a timeout under CI's coverage gate. The same property is proved with one rendered row.


















































What this changes
#914 — the Lay rate card's window is chosen, not fixed. A Range control
under the flock scope offers the last 7 or 14 finished days, or a
Custom range… of two plain date fields with Apply, capped at 14 days.
The choice is remembered per device and per farm (#535's account-scoped storage),
re-validated on read rather than trusted, and a remembered window this build can
no longer draw falls back to the default.
The card draws one bar per day and nothing coarser (owner, 2026-09-23). 14
days is what the strip holds at a readable width — 22px slots and 2px gaps in
the phone card's 342px of plot (#912). A longer range is refused in the form
rather than redrawn at another scale. The 30-day preset, custom ranges up to
90 days and the expanded view that could carry them moved to #941, which this
PR does not build. The week bucketing #914 originally specified was built,
reviewed, and then deleted rather than left unreachable behind the cap, so
#941 can choose its own display from a clean slate.
#915 — both busy panels page inside themselves. Morning collection shows
8 houses a page on desktop and 6 on a phone, missing houses still first, so
every house on a 100-house farm is reachable without leaving the Dashboard. Recent
orders shows 5 a page and fetches the next five on demand through the shared paged
list. Neither panel gained an inner scroll region.
Component plan (
docs/designs/822-mui-revamp.md)DayStrip,StockBar, … "kept as they are") covers the strip. web: Dashboard Lay Rate range switcher (7/15/30 days and a custom range) that collapses long ranges to weeks #914extends the data
DayStriprenders; the component's own behaviour — theone-tab-stop arrow-key strip (SPA: dashboard rework — capture-status tiles, 14-day sparkline, stock as a stacked bar #654/Dashboard: Recent sales rows do not form columns, and the trend and stock charts are hard to read #777/Production report cannot distinguish a day with no entry from a day that produced zero eggs #780), the reserved readout row, the
average line, the three-item legend — is unchanged.
usePagedList"Load more" →Button; "the hook … stays; there is nooffset
Paginationto adopt") covers Recent orders: it now goes through thathook rather than a second homegrown loader.
.toolbar→ oneFilterBarcomponent) suppliesFilterDateField,reused verbatim for the custom range's two dates.
Desk) and feat(dashboard): redesign the Operations Desk and show order grade names #912.
Decisions taken, and where they differ from the issues
the strip can draw one bar a day. Longer ranges are web: expand the Lay rate chart into a centred overview + scrollable daily view for ranges beyond 14 days #941's.
still fetched — in the same single request, split client-side by
splitProductionReport— but only feeds the hen-day comparison.the fourteen-day default that is index 7, exactly where it has always been, so
the shipped render is unchanged. A seven-day window has no internal boundary and
draws none; a weekly strip needs none, because every bar is already a week.
counts finished days only (owner decision A, web: redesign Dashboard around the Operations Desk direction #906). Longer than 14 days is
refused in the form, never silently shortened, and a range leaving no room for
its own comparison window is refused too.
/saleslist returns a bare array with no count, so the number of order pages isunknown until a short page arrives; numbered pages there would either invent a
total or grow under the reader's finger. The label says
Orders 1 to 5until thelast page lands and
Orders 11 to 12 of 12after it. Houses, whose total ISknown, read
Houses 1 to 6 of 100throughout. One control serves both panels,and at 390 a numbered pager cannot hold 44px targets inside the card's 342px.
trendPanelTitleis now "Lay rate trend". It was "Last 14 days", which therange control makes false; it survives as the card's accessible name, and the
head's duplicate caption is gone because the control states the window.
Tests
Measured on this branch, not recalled.
web/src/lib/dashboard.test.tsweb/src/lib/dates.test.tsweb/src/lib/layRateRange.test.tsweb/src/routes/Dashboard.test.tsxweb/src/components/DayStrip.test.tsxwebsuitenpm run typecheckclean;npm run test:coveragegreen. The full quick Playwrightsuite at this head, both projects, against an isolated
cw914astack: 97 passed,1 intentionally skipped, 0 failed. Two of those 97 are new (
phone.spec.ts), sothe same suite was 95 before.
Mutation checks
Every new guard was shown failing on a mutation of the code it covers, then green
again. Fourteen mutations, fourteen reds.
trendWindowpreset starts a day latelayRateRange.test.ts(2 cases)customRangeErroraccepts 91 dayslayRateRange.test.ts(2 cases)parseStoredRangetrusts storage without re-validatinglayRateRange.test.tsinclusiveDaysdrops its second enddates.test.ts(3 cases)dashboard.test.ts(2 cases)dashboard.test.ts,Dashboard.test.tsxpanelPagetrusts a stale page numberdashboard.test.tsdashboard.test.tsDashboard.test.tsxDashboard.test.tsx(2 cases)Dashboard.test.tsxDashboard.test.tsx(3 cases)2.2remphone.spec.ts(real browser)phone.spec.ts(real browser)Mutations 13 and 14 were injected into the running app through a stylesheet
override, against the same isolated stack, so both browser guards were observed red
and then green without rebuilding.
Defect found and fixed inside this slice
The first capture round showed a real defect the unit tests could not see: a weekly
readout carries two dates, a day count and two figures, which overflowed the
2.2remrow.tipdockreserves and grew upward over the scale caption. Fixedby shortening the four week strings and giving a bucketed dock the full card width
and a
3.6remreserved row (ba4f40b). Measured afterwards in the real browser at1280 and 390 in all three locales — worst case 14.8px of headroom above the row
and 8px below,
tlincluded — and pinned by the newphone.spec.tsguard.Deletions (grepped across the whole repo,
tools/simulation/uiincluded)git grep -n "<name>" -- ':!graphify-out', after the change:TILE_CAPvisibleTilesmoreFlocks(i18n key, 3 locales)recentCount(dayStripparameter)The only hits anywhere are stale entries inside
graphify-out/graph.json, which isregenerated output.
Documentation
specs/product/GLOSSARY.md, the SPA Help page and the in-app glossary are updatedin en, es and tl, each locale using its own control labels (#688): Range /
Período / Saklaw, Custom range… / Período personalizado… / Sariling saklaw…, From /
Desde / Mula, To / Hasta / Hanggang, Apply / Aplicar / Ilapat. "Capture status" and
the renamed "Lay rate strip scale" entries were rewritten, and "Lay rate range" is a
new entry;
helpGlossary.test.ts's own guard requires every in-app term to exist inthe spec glossary, so the rename moved in lock step.
Known limitation, tracked separately
At the drain ceiling the Morning collection progress bar still renders a
determinate percentage from an incomplete entry list, so it can read lower than
the farm's real position while the notice below it explains why. The owner is
keeping the ceiling as it stands; the bar and a better design for the whole
incomplete-data state are tracked in #942 (low priority).
Review round 1 (Codex gpt-6-sol, of
ff5d5ee)Five findings, all fixed, each with a test that failed first; finding 4 is
settled by the deletion of week bucketing. The owner also asked for the
Dashboard's "All flocks" scope button, which this PR's own captures showed as
brand-on-dark: it was 1.18:1 and is now 9.35:1, fixed in the theme for every
text and outlined primary
Button(fourteen call sites across seven screens),not with a one-off
sx. Detail in the round-1 reply comment.Closes #914
Closes #915