Skip to content

SPA revamp: implement the chosen direction — field-first phone (B), ledger desktop (A), one attention line (C) #674

Description

@mforce

Amendment (2026-09-13). Sequence step 1's open question — "do we adopt external UI
components?"
— is answered: MUI. Tailwind + shadcn/ui is declined, not deferred. The farm
palettes are kept and proven. See
#674 (comment) for what
changed, which claims in the evidence comment were corrected, and the measured bundle cost; the
reasoning lives in docs/decisions/674-ui-component-library.md.

The body below is left as written, for history. The checklist at the end is current.

Tracking issue for the SPA revamp. Owner decision recorded on #656 (2026-09-02): a combination — B's field-first phone, A's ledger desktop, C's attention strip reduced to one line on both. This issue carries the work; #656 stays the home of the three visual-identity questions, which the design doc answers.

Every slice follows docs/designs/822-mui-revamp.md.
It is the component plan, not background reading — it maps each existing custom element to the
MUI component that replaces it, and states the rule: "reused, never rebuilt", and for the
shared picker, "the engine stays whole". Composing MUI primitives into a screen-specific
wrapper is expected (FilterBar, AuthShell and FieldConsole all do). Reimplementing
behaviour a shared component already has is not. #918 built a 216-line flock picker duplicating
NamedEntityPicker's paging, debounce and keyboard handling — with the same constants, 50
and 250 — and its review found three defects in mechanics the shared picker had already
solved. That plan even documents the slots.paper technique for rendering content around an
Autocomplete listbox, which is the problem the bespoke component was built to work around.
Cite this document in every slice brief. It appears as a ticked item below, which reads as
finished work; it is a standing reference for every slice that follows.

What was decided, and why

Three rendered directions were built from the app's own seeded data (four houses, 327/298/341 collected with House C not recorded, 63,122 eggs on hand by grade, the 14-day trend, four real orders), differing in point of view rather than palette:

  • A — the ledger. The screen is the working record. Ruled rows, numerals first, no cards.
  • B — field-first. The hen-house phone is primary; one task per screen, glove-sized targets; desktop is the same parts wider.
  • C — operations console. Status before records; a persistent attention strip; built for the night check.

Chosen: phone = B, desktop = A, both = one attention line. The app has exactly two real contexts — a phone in a shed and a desk in an office — and the renders designed for each were B-phone and A-desktop. (The A-phone and C-phone renders reused desktop tables at 390px and overflowed; they were not fair renders of those directions.)

Sequence

  1. Design doc in docs/designs/ — must answer SPA: visual identity — brand-derived link colour, display optical size, two-colour mark (owner decisions) #656's three questions (link colour derived from the brand, display optical size via the already-installed Inter opsz axis, the two-colour mark) and the two questions nobody has asked: the IA (18-link flat sidebar in 5 groups, bottom tabs + "More" sheet on phone) and the layout system (today: everything is a white card; A has none, B has cards for houses only).
    • Also answer: do we adopt external UI components? There is no rule against them and specs/technical/tech_spec.md §8.1 (KD-6) prescribes them; the hand-built approach is a convention nobody decided. Evidence, candidates and the constraints a candidate must satisfy: #674 (comment). Ends as a decision record either way.
  2. Grill the design doc (contrarian pass) before signoff.
  3. Slices, each filed on this epic, sized so each can be rendered and looked at; one PR per slice; every PR attaches a 1:1 before/after comparison captured from a stack rebuilt at the head under review (the only check in this repo that reads the rendered result; it found the sole real defect in the punch-list).

The three visual-identity questions (folded in from #656, closed 2026-09-13)

#656 was closed during the issue cleanup and its questions moved here, so one decision has one
home. Its comment thread stays the record of how the direction was chosen; the substance is below.
The design doc in sequence step 1 answers all three.

1 — Links derive from the brand. The palette is Slack's, token for token: #4a154b primary,
#1264a3 link blue, #611f69 press (web/DESIGN.md, adopted in #52). Blue links are a second hue
that fights the "chromatic monotheism" the stylesheet header claims, and they ignore the farm accent
palettes (#149) — a forest or terracotta farm still gets Slack-blue links. Proposal:
--link: var(--brand) in light, --stat-accent in night, hover a step lighter/darker. Verify
4.5:1 on --surface for every palette × theme in the styles test
, not by eye.

2 — Optical size for display text. Inter everywhere at one optical size gives headings and stat
figures no voice. font-variation-settings: "opsz" 32 on h1, h2, .stat-value buys tighter
display cuts at zero download cost.

Amended 2026-09-16 (#822 D7.2, #864): the premise below was wrong in the way that matters. The files are in the package, but the app loads @fontsource-variable/inter's default entry, whose index.css carries wght faces only; the opsz faces are a separate entry (opsz.css), and adopting them costs about +119 KiB of precache (+24 KiB on the latin subset a phone fetches). "Zero download cost" above does not hold; #835 owns the swap and is charged against #825's ceiling. Left as written below for history.

Already answered, incidentally (2026-09-02): the installed @fontsource-variable/inter does
carry the opsz axis — web/node_modules/@fontsource-variable/inter/files/ contains
inter-*-opsz-normal.woff2. The issue flagged a risk that the default entry ships wght only.
It does not apply. Do not re-investigate this.

3 — The brand mark. A generic single-stroke egg outline that will not survive favicon and PWA
icon sizes. Wants a two-colour mark that holds up small.

Also inherited from #656: it was deliberately sequenced last among the seven look-and-feel
slices, because "the other six are defects; this one is a point of view." The other six shipped
(#653, #655, #660, #662, #663, #664). That ordering rationale still applies to the design doc —
answer the point-of-view questions deliberately, not as a side effect of a defect fix.

Current owner-approved screen directions

These descriptions supersede earlier visual targets for the named slices only. Implementation remains open.

Slice Approved composition
#906 — Dashboard Operations desk: morning brief, collection progress, grade/count/share stock ledger, aligned recent orders and a 14-day bar chart.
#907 — Daily Entry Count workbench: separate collection counts and sellable-egg grading, explicit reconciliation, mortality as a separate flock event, reachable draft/submit actions.
#908 — CRUD Complete comparison tables above full-width bottom inspectors. Lists scroll independently; Products and Packed units are separate tabs on the same page.
#831 — Ledgers Workflow-specific order desk, grade stock board, inventory movement ledger, production correction sheet, expense daybook, feed ticket, direct/meter water log and raw-detail reports.
#833 — Settings and support Expandable Settings/Account sections and Audit rows; persistent Help contents with active-section highlighting; split Login/Set Password panels; full ZIP backup plus single-dataset CSV selector.

Detailed approved compositions and captures: Dashboard, Daily Entry, CRUD, ledgers, Settings and support.

Login banner placement is visually approved; pre-login access to authenticated farm images remains an explicit implementation decision. Production states, localization, accessibility, tests and rebuilt-stack evidence remain acceptance work. #836's brand-mark design is separate and still open.

Slices on this epic

Formalised as an epic on 2026-09-13 during the issue cleanup — the body already said
"slices, each filed on this epic", but the epic label was missing, so nothing was navigable
from here. Label: epic-674.

Phase 0 — design · phase:0-design · gates everything below

Phase 1 — groundwork · phase:1-groundwork · all three run in parallel, after #822

Phase 2 — controls · phase:2-controls · parallel with each other AND with phase 3

Phase 3 — screens · phase:3-screens · #829 first; it sets the conventions the rest follow

Phase 3 follow-up — owner-directed redesign · phase:3-screens · preserve the completed conversion issues as history; each follow-up designs first, then implements after owner sign-off

Phase 4 — identity · phase:4-identity · near-independent; #835 needs #823's type decision

Unphased — start any time, blocked by nothing

Defects

  • web: long tl/es strings overflow fixed-width controls on narrow screens #740 — the phone action bar cannot hold its own button labels. Added 2026-09-13 (owner)
    after a live reproduction. .actions button is flex: 1 at phone width, which squeezes the button
    narrow enough that its label wraps to three lines; the box then becomes taller than it is wide and
    border-radius: 999px resolves to an ellipse the text falls outside of. Broken in English
    too
    — en at 420px is the worst aspect ratio of the three, so this is width-dependent, not a
    locale bug. It belongs here because the fix is a decision about how action buttons lay out on a
    phone, which is sequence step 1's layout-system question. Full measurements, screenshots at three
    viewports and two candidate fixes are on the issue.

  • Offline data capture (PWA) — tech spec KD-4/§6 #50 — Offline data capture (PWA). (Moved to its own milestone "Offline data capture (PWA)", owner 2026-09-26.) Added to this epic 2026-09-13 (owner). The Installable PWA baseline — manifest, icons, service worker app-shell cache (split from #50) #142 PWA
    baseline shipped; this is the queued-writes, conflict-resolution and sync half. It sits here
    because it captures from the screens this revamp rewrites — built before the revamp lands it
    would be built twice — and because the field-first phone direction (B) is the context that
    makes offline capture worth having at all. Sequence it after the visual slices, not
    alongside them. Tier4 until the design doc lands.

Constraints the slices inherit (from #650–#657, all shipped)

  • Sentence-case labels everywhere; caps survive only on nav group dividers (web/src/styles.caps.test.ts).
  • EmptyState (13 list screens, two variants, emptyStates.guard.test.ts) and .toolbar are reused, never rebuilt; --shadow-card is retired; the elevation guard (styles.elevation.test.ts) allow-lists exactly which selectors may cast a shadow — extend deliberately.
  • Count a selector's call sites before styling it (grep -rn "<class>" web/src --include='*.tsx'); zero is a legitimate, stated answer.
  • Removing a presentational transform makes every string it transformed a caller — read the strings in all three locales.
  • Every web/ change ships Vitest tests in the same PR; style facts jsdom cannot see go in a styles.*.test.ts parsing styles.css with postcss.
  • Coverage is a ratchet with <1 pt headroom on functions — never re-baseline to get green.
  • i18n is not English-first: every new string ships en/es/tl inline, machine-drafted and flagged for native review; Spanish and Tagalog run longer.
  • Docs in sync: GLOSSARY + Help page in the same PR when a concept appears or changes; a pure restyle owes neither and says so.

Touchpoints

Materials

Rendered mockups (desktop 1440×1000 and phone 390×844 for A/B/C), the handoff notes and the decision log live in the driver's records (~/.claude/driver-records/cluckwork-revamp/), not in the repo; the design doc brings whatever the repo needs to keep.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:frontendReact/Vite web clientepicPhase-level tracking issueepic-674SPA revamp — field-first phone, ledger desktop (#674)priority:tier2High value, low risk

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions