T
Sign in

UI pattern — Textile ERP NextJS reference language

UI pattern — Textile ERP NextJS reference language

Every screen should feel like one calm monochrome workspace — white rounded-xl sidebar with zinc active pills, zinc-100 workspace, white 20px cards, near-black primary / neutral CTAs, Inter — while staying 100% daisyUI (Repo rule 4), append-only (mandate 3) and light only (Rule 1). Design tokens, layout and components live in docs/DESIGN.md — this doc is the per-screen pattern guide that implements them.

Canonical examples: src/routes/masters/+page.svelte (list screen), src/routes/orders/new/+page.svelte (form screen), src/routes/guard/+ page.svelte (tap screen). The shell (+layout.svelte) already renders the sidebar; pages render inside it and must NOT render their own navbar or ← Home chrome.

The sidebar — one group at a time, one home per screen (C296)

  • One map — src/lib/nav.ts is the only navigation source (NAV, UTILITY, HIDDEN); +layout.svelte draws it and declares no hrefs.
  • One group open — the labelled sidebar shows Dashboard, Masters and the eight work groups; exactly one group's links are drawn — the group that owns the current screen, or the one the user last tapped. 10 rows at rest, 22 at worst; the pre-fix sidebar drew all 45 always. Phone drawer and desk sidebar use the same accordion; the collapsed desktop rail still shows every icon (a rail is a launcher, not a map). Section headers are buttons with aria-expanded; links carry aria-current="page".
  • Strict segregation — a screen lives under exactly one group. Two rows for one page, or a link named after its own group ("Reports" inside "Reports"), is a fault. Screens opened from a parent screen — sizing, winding, beam-end, inward, fabric-in, pending, queue, search, signin — are declared HIDDEN with their reason instead of being added to the sidebar.
  • Guard — bun scripts/nav-audit.ts (16 checks) fails on a dead row, a duplicated screen or name, an unmapped screen, a stale HIDDEN entry, a sidebar that grew past 24 rows with a group open, an icon the layout's map lacks, or an inline href map returning to +layout.svelte. A new screen adds its row (or its HIDDEN reason) in the same change.

Ergonomics is the most fundamental UI principle (DESIGN.md §1). Every pattern in this doc exists to make the daily job easier — fewer taps, less typing, less hunting, less re-checking. When two patterns compete, choose the one that gets the user to the result fastest; restyle only within this language.

Design for the job. Pages are NOT clones of one template — each is shaped by what it serves (owner directive 2026-09-03; layout idioms follow the textile-erp-nextjs reference — owner 2026-09-05 §93):

  • List screens (masters, beams, orders, bookings, lots…) → compact text-xl font-semibold panel header with the count and the create CTA on the right, a search box (input input-bordered input-sm w-full sm:max-w-xs + Search icon) filtering the list client-side, then a plain bordered table (overflow-x-auto rounded-box border border-base-300 bg-base-100, daisyUI table — not table-sm — gray thead, px-4 py-2 cells, — for nulls, badges on enum columns) with a right-aligned text-link action per row (link link-primary link-hover text-sm). Empty list = centred empty-state card. SmartTable (the shared list component) bakes in the reading ergonomics (PLANNING §134, light-only): sticky header inside a max-h-[75vh] scroll region, semibold first (doc-no) column, tabular-nums right-aligned numerics, ISO timestamps rendered as "08 Sep 2026", long text clamped to 2 lines with full-text tooltip, badge columns through statusWords() — never raw enums or ids.
  • Entry/form screens (/orders/new, /gatepasses/new, checking, dispatch…) → page header + a single clear form card (sections + labels); no empty-state/table scaffolding they don't need.
  • Action/queue screens (drafts, approvals, guard, transporter) → page header + the queue as tap-friendly cards or a table depending on density, with the action buttons front and centre.
  • Flow screens (capture hub, checking, dispatch) → keep the flow's step order and its existing logic untouched; restyle within the language.
  • Dashboard screens (home /, /board/[slug] — PLANNING §50) → the BOARDS-A pattern (docs/BOARDS-A.md): master = header New menu + attention strip + period bar + the one pulse + one-open section rows; component pages = depth-bar crumb ← Main, ⓘ What·Data·Who (collapse), KPI stats, then the SmartTable + dump. See "The dashboard pattern" below.
  • Print routes (/hishob/[id]/print) → leave untouched.

The shared visual language (defaults, adapt as the job needs)

  1. Page wrapper — <div class="min-h-screen bg-base-200 p-4 sm:p-6"> with an inner mx-auto max-w-4xl/5xl/6xl (choose by content width). bg-base-200 is the zinc-100 workspace; the shell caps everything at max-w-7xl.
  2. Page header — <h1 class="text-2xl font-bold tracking-tight">
    • one-line <p class="text-sm text-base-content/60"> subtitle. Top-right action when the page is mostly a gateway to creating: <a class="btn btn-primary btn-sm" href="…">.
  3. Panel header per section — <h2 class="text-lg font-bold tracking-tight"> + desc line + badge badge-outline count.
  4. Tables — white bordered card card overflow-x-auto border-base-300 bg-base-100 → table table-sm, thead bg-base-200/60 text-base-content/80 (global CSS paints it zinc-100), badges for status/category (badge-ghost/badge-secondary/badge-info/ badge-warning), — for nulls, right-aligned numerics (text-right pr-4 tabular-nums), font-medium on primary cells. Row-hover inset bar (zinc-900, §195) comes from app.css automatically.
  5. Empty states — centred card: icon avatar flex h-16 w-16 items-center justify-center rounded-full bg-base-200 text-base-content/50, h3 class="text-lg font-bold">No X yet, a p line, and an outline reveal button (see reveal() below).
  6. Forms — labeled fields everywhere: <label class="form-control w-full"><span class="mb-1 text-xs font-medium">Field *</span><input class="input input-bordered w-full" …></label> (selects: select select-bordered w-full). Long forms: section headings col-span-full text-xs font-semibold uppercase tracking-wider text-base-content/50; submit right-aligned btn btn-primary btn-sm. On list screens, wrap the form in a daisyUI details collapse collapse-arrow (auto-open when the list is empty); on form-first screens, the form card IS the page.
  7. Action buttons — primary actions btn btn-primary btn-sm; row actions tiny POST forms inline-flex gap-1 with btn btn-outline btn-<color> btn-xs; destructive/queue actions keep their semantics (confirm=success, discard=error, repair=warning).
  8. reveal() helper (copy from masters) for empty-state buttons: function reveal(id){const d=document.getElementById(id) as HTMLDetailsElement|null; if(d) d.open=true;}.
  9. Form errors — daisyUI alert alert-error fed by page.form?.error + dismiss (masters pattern), on form-heavy pages.

Readability — every control is a picture + a word (C297)

The reader may read little English. Recognition comes before reading, so the shared surfaces carry a lucide icon and its word — never an icon alone (an unlabelled glyph is a guess), never a raw symbol standing in for a word (⬇, ×, ‹, ›), and never a bare text link where a button says "this goes somewhere". Guarded by bun scripts/readability-audit.ts (24 checks — 16 of them fail on the pre-fix tree), run beside daisy-audit when a shared component changes.

  • List toolbar (ListToolbar.svelte, 19 list screens). A Funnel badge announces the filter group before its selects (each select labelled aria-label="<label> filter"); Clear (X), CSV (Download) and Search (Search) are icon+word buttons; the count line reads "Showing 1–50 of 320" / "No matches"; the pager is ChevronLeft prev · page n of m · next ChevronRight.
  • Row popup (RecordDialog.svelte, every list screen's row tap). Fields render as cards, not dotted rows: a two-column chip grid (rounded-lg border border-base-200 bg-base-200/50) with the label as a 11px uppercase caption over a semibold value, so the eye lands on the value first. The action row has a hierarchy — the first action is btn-primary, every other one btn-outline, and a worded X Close sits last beside the header ×. On phones the actions are min-h-11 (a 44px finger target), tightening to btn-sm from sm up.
  • Plain-table row action (SmartTable.svelte). The default action is a btn btn-outline btn-xs with a ChevronRight — a visible target that points forward — never the old link link-primary text link. Screens that pass their own actions slot are unchanged.
  • Action screens are cards. /pending was the last queue-family screen still drawing two zebra tables; it now uses the queue-card pattern the rest of the family already uses (guard, transporter): one card per parked lot / booking — id + status badge, yarn, supplier/mill, bags · kg spelled out — with the same ?/assignLot / ?/assignBooking form, hidden id, contract picker and use:enhance beneath it. Dense multi-column tables stay where the job is comparing many numbers (C285: the job-work orders table keeps its natural width and scrolls).

Hard rules (non-negotiable)

Scope: the hard rules below apply to pure styling passes (the §45→§48 conversion round) — they freeze markup/behaviour so a restyle can't break wiring. Functionality passes (the work-surface pass, PLANNING §51) are exempt from "never touch +page.server.ts": they may add loads, actions and components, under the repo mandates — docs-first (PLANNING entry + this doc in the same turn), append-only data (no edit/delete), daisyUI-only, light-only, build green (bun run build), deploy only via deploy.sh. All other rules below still bind every pass.

  • Only edit the listed +page.svelte files. Never touch +page.server.ts files, schema, app.css, +layout.svelte, tests, docs, package.json.
  • Never change name attributes, action="?/…" values, method, use:enhance, ids referenced by JS, or <script> logic (you may ADD lucide icon imports). Restyle markup and headers only.
  • Append-only: no edit/delete UI; keep every form wired identically.
  • Light only: never add data-theme, dark classes, or dark: variants.
  • Mobile-first: tables scroll within overflow-x-auto cards; grids collapse to one column; tap targets stay large on queue screens.
  • Icons: lucide-svelte, class="h-4 w-4" (h-8 w-8 in empty states).
  • Do NOT run git / bun / build / test commands. Write files only.
  • Wrap-up report per file: what changed + anything left as-is.

daisyUI only — the app's single kit (C292, PLANNING §572)

daisyUI is the whole UI kit. No second component library, no CSS framework, no icon-free hand-rolled control where daisyUI has one. This is not a taste rule it is greppable: bun scripts/daisy-audit.ts (exit 1 on drift) checks four surfaces and every row/session should leave it green.

Primitive Use Never
Button / action btn btn-sm, btn-ghost, btn-neutral… a hand-rolled filled button
Text / search / number input input-bordered input-sm (or <label class="input"> wrapping a bare <input class="grow">) utility-only border+padding box
Dropdown select select-bordered select-sm a custom list box
Multi-line textarea textarea-bordered styled div + contenteditable
Upload file-input file-input-bordered (or class="hidden" behind a btn label) custom picker chrome
Toggle / choice checkbox, toggle, radio inside join-item btn labels peer/sibling CSS switch
Table table table-sm in overflow-x-auto rounded-box border own stripes/hover CSS
Bar / meter progress progress-primary (or progress-success / -warning / -error) <div style="width:%">
Message alert alert-… (+ alert-soft for hints) tinted div with its own border
Modal modal / modal-box / modal-backdrop custom fixed overlay (the focus-entry wizard sheet is the one spec'd exception — docs/FOCUS-ENTRY.md)
Toast toast + alert (src/lib/components/Toasts.svelte) bespoke popup
Tabs / pills tabs tabs-boxed + tab + tab-active chip row with manual active class
Loading loading loading-spinner CSS spinner

Allowed, on the audit's allow-list, and why:

  • lucide-svelte — icons. daisyUI ships no icon set; lucide draws glyphs only (class="h-4 w-4"), never layout or components.
  • marked, zod, sveltekit-superforms, drizzle-orm, better-auth, @libsql/client — data/validation/server, not UI.
  • Paper sheets (src/routes/**/print/** + PAPER_SHEETS in the audit) — a printed A5/thermal document is not an app screen; daisyUI has no paper component, so a sheet keeps its own .tbl grid and print CSS.
  • Layout buttons — a table row, a card tile, a keypad key, a backdrop dismisser is layout, not a control: it keeps utilities (daisyUI btn has a fixed height/padding and would resize the form). No button may be left unstyled; the one unstyled shape that is correct is daisyUI's own <form method="dialog" class="modal-backdrop"><button>close</button></form>.
  • src/lib/components/NumberWheel.svelte — the typeable slot-machine wheel; daisyUI has no wheel. Named in the audit's exemption list, which fails once the marker disappears.

Adding a field, a screen or a dependency: run bun scripts/daisy-audit.ts before you close. A new UI library is a docs decision (this section + a PLANNING log), not a bun add.

The dashboard pattern (home + /board/[slug])

Canonical: the home / (master) and any /board/[slug] component page.

  • Master dashboard (home) — one hierarchy, top to bottom (C299): page header (title + role chip + period badge + "See all sections" when a role board is showing + the single New typed menu — a <details class="dropdown dropdown-end"> whose <summary> is btn btn-primary btn-sm and whose rows are min-h-11 icon + word links to each type-fixed form, C158) → attention strip (first, because what needs a hand outranks a filter; daisyUI card with a 2px primary border, chips = btn btn-outline btn-sm with a count badge badge-xs — tone map primary/ warning/info/secondary) → period bar (form method=GET action=/ with from/to date inputs; binds dated stats only — stock/beam positions are live and say so) → the one 14-day pulse card → a Boards — tap one to open it caption → nine collapse rows, exactly one open: each section is a native <details class="collapse collapse-arrow" name="dash-sec"> with open on one row only (the browser's exclusive group — the C296 sidebar reading applied to the board rows), the <summary> carries the h2 (the ScrollMap tick), the section badge, {n} red / {n} watch tallies counted from the cards' warn/bad KPIs (bad = 2, warn = 1 picks the section opened at rest) and {n} cards, so a closed row still says whether it wants a look. Inside the open row: on phones a carousel w-full gap-3 with one carousel-item w-[86%] per card — one swipeable row per section, never a 29-card stack (C293) — and from sm up the grid grid gap-4 sm:grid-cols-2 lg:grid-cols-3. One {#snippet dashCard(ca)} renders both surfaces, so a card is written once. Each card: card with title attr = the one-line explainer; KPI chips are rounded-lg bg-base-200/70 divs (value tabular-nums + tone color text-success/warning/error), then open page →. Not-ready cards (builds with later-phase data) show the spec note instead of KPIs and a badge badge-outline phase tag — never a fabricated number.
  • Scroll map (ScrollMap.svelte, C289) — the home dashboard mounts it once, above the content wrapper (never inside fade-in: a transformed ancestor captures fixed). It measures the shell's main scroller and draws one 1px tick per section on a 56vh rail in the right gutter, the reader's section inked, section name on hover, click = smooth jump. Desktop only, hidden while the page fits; the content column reserves the lane (lg:pr-8). The dashboard keeps the og scrollbar (C295): the app-wide skinned bar from app.css (10px, rounded zinc thumb), the same one every other screen and every scroll region inside a page draws — no page styles or hides its own bar. The rail itself is a section map plus an invisible 44px grab lane (drag scrolls the page, C290); it paints no bar, so nothing draws beside the real one.
  • Component page (/board/[slug]) — crumb ← Main / Title → header (emoji + title + component/phase badge + "live position" badge) → ⓘ details collapse (What · Data · Who enters) → not-ready alert alert-info when flagged → KPI stats (overflow to SmartTable below) → detail card: panel header + action links + ⬇ Dump — raw rows (CSV) (/api/dump?slug=…) + the SmartTable + a one-line footer about sort/search/filter and dumps-not-reports.
  • SmartTable (src/lib/components/SmartTable.svelte) is the board table standard (DASHBOARDS §5.1): click-to-sort any column (▲/▼), per-column search inputs, filter selects on enum columns, empty-state distinguishes "nothing here yet" from "no match". Columns are server-defined {key, label, align, badge} — badge columns render daisyUI badge pills.
  • Every dashboard number traces to a query in src/lib/server/board.ts; adding a component = one card builder (KPI) + one componentPage case (info/title/columns/rows/links) + one SECTIONS ids entry.
  • Guard (C299) — bun scripts/dashboard-audit.ts (23 checks) fails if the home page grows a second open section, draws cards outside a collapse row, loses the phone carousel, puts a shortcut back at the bottom of the stack, reorders the page below the attention strip, or lets the engine's nine sections / 29 cards drift.

The work surface (ERP verbs) — PLANNING §51

List screens and record pages are work surfaces, not document viewers. Every rule below keeps append-only (mandate 3), daisyUI (Repo rule 4) and light-only (Rule 1) intact.

  • List screens (orders, beams, lots, tippans, gatepasses, bookings, samples, hishob, approvals, drafts) share one layout above the table: a ListToolbar card — search input (input input-bordered input-sm with a lucide Search icon), one filter select per enum/status column, click-to-sort column headers (SmartTable standard, DASHBOARDS §5.1), pagination (btn btn-sm ‹ prev · page n/m · next ›), a totals footer row (font-semibold tabular-nums sums under the right-aligned numeric columns), and a ⬇ CSV button (btn btn-outline btn-xs, /api/dump?slug= style server-generated export). Default 50 rows/page; empty states keep the existing centred-card pattern.
  • Record pages (/orders/[id], /beams/[id], …): header = number + name/party (h1) + status/category badges + id note; then a crumb ← Back to list; then sections as cards — Terms / Linked docs / Events / Amendments — the last two are read-only history from amendment + evt (append-only display: table table-sm, newest first, text-sm). Verbs live in the header, right-aligned, smallest that fits (btn btn-sm):
    • Amend — opens a daisyUI modal with a textarea + the SAME ?/amend//api/amend fields the record's form used; on success the amendment row appears under Amendments (no reload needed → use:enhance + invalidateAll).
    • Approve — only where owner-gated (reconciliations, job-work hishob, mandate 5): btn btn-primary btn-sm POST to /api/approve; before approval the print button is disabled with a badge badge-warning "awaits owner approval".
    • Print — only at EXTERNAL boundaries (gatepass, challan, beam card, hishob; mandate 4): window.print() on the existing print CSS; internal steps stay approval taps, no print.
    • Status transitions and corrections are appendage only — never overwrite; a "correct" verb that edits fields in place does not exist.
  • Global search — the shell gets a search box (desktop sidebar top, mobile top bar) GETting /search?q=; results page lists matches by domain (orders → beams → lots → tippans → gatepasses → parties → designs → rolls), each row linking to its record page, data.q echoed in the header, no matches → the standard empty state.
  • Entry screens — party/design pickers are comboboxes, not <select>: input + filtered dropdown/menu of code — name matches (≥2 chars), Enter/tap picks, value confirmed visibly before submit. Prefill-from-history stays (never listed for empty fields unless user accepts); a duplicate hint (alert alert-warning "same party+design+date booked as {no} — open it?") appears when a matching order exists; confirming creates a new record (append-only, no merge).
  • Plain entry forms use the KIT-NATIVE pattern (PLANNING §95 schemas + §100 client decision): route-local schema.ts (zod), superValidate in load + action (fail(400, { form }) on invalid, setError for business checks), and on the <form> use:enhance from $app/forms (kit's own) — NOT superforms. Errors render from the action result's Svelte 5 form prop: const errs = $derived(form?.form?.errors) → field errors under the input as text-xs text-error; form-level errors (zod .refine) as the alert alert-error via errs?._errors. The superforms CLIENT runtime is banned on these forms: its client validators hang the submit and its failure display never re-renders in this Svelte 5 bundle (PLANNING §99–§100; pinned by tests/no-client-validators.test.ts). Inputs are plain (values ride the FormData; defaults set with selected); where a preview needs reactivity (bookings per-cone/net, rate-hint Use) bind to local $state. Number fields are z.coerce.number() so junk input is a visible error, never a silent || 0 — and selects that represent flags stay string enums (z.enum(['0','1'])), never z.boolean() (superforms maps the posted '0' → true). Bespoke entry forms (combobox+voice prefill, OCR capture, multi-row) stay on the plain use:enhance pattern. Per-row tap lists submit the row id in the DOM; a failed result is targeted to its row by comparing the returned form's data.id. (Historical: superforms client on plain forms — PLANNING §95 — was the §99–§100 dead-submit sequence; server-side superValidate usage remains the standard.)

Row-click detail dialog — APP-WIDE STANDARD (PLANNING §112, live from orders)

  • Every list screen: tapping any row opens that entry in a modal dialog (src/lib/components/RecordDialog.svelte) — key fields in a 2-up grid + an Open full record action that goes to the record page (where Amend/Approve/Print/forms live). Masters keeps its RICHER dialog (inline edit with the same Add form + amend) — it is the reference for edit-capable variants.
  • Mechanics (SmartTable): screens pass rowClick={(r) => (dialogRow = r)}; the row is focusable/cursor-pointer; taps on the row's own controls (links, buttons, inputs — incl. the actions cell) keep their behavior. The dialog renders under {#if dialogRow} with onclose clearing state — no route round-trip, works offline of navigation, mobile-friendly (bottom-sheet on small screens, internal scroll).
  • Rollout: orders live as the reference (2026-09-06); the other list screens wire the same recipe (field list per screen). New list screens MUST include row-tap → RecordDialog per this standard.
  • Density — list tables stay table-sm; numeric columns text-right pr-4 tabular-nums; a compact toggle (btn btn-ghost btn-xs) may hide the toolbar + descriptions for desk clerks; every KPI/list number is tabular.

Guided entry — APP-WIDE STANDARD (PLANNING §125–129, live on every form)

Reference: src/routes/orders/new/+page.svelte. Shared piece: src/lib/components/ReviewSheet.svelte (import it — do not rebuild it).

  • Slim header — one row: text-lg title + kind/status badges. No subtitle paragraph; its guidance lives in the menu + bottom bar.
  • Section menu cards — 2-col grid of big tappable cards (number badge, plain-word title, live one-line summary, explicit "Open →"); active = border-primary bg-primary/5, done = badge-success ✓. Summaries direct the user ("Needs: buyer name", "Can skip"), never just report state. Steps read "Step N of M".
  • One section open at a time (Next buttons, chevron headers that rotate). Closed sections use class:hidden — NEVER {#if}-unmount inputs or their values silently drop from the POST.
  • Sticky bottom bar — Back + position (n/m) + live facts (party · qty · rate) + ghost Clear (resets typed values) + the Check & … button. Facts keep tallying scroll-free.
  • Prefilled badges — history/design-filled inputs show a muted prefilled tag until typed over (touched-map); computed values sit in a "For checking (auto)" panel, never in shouting inputs.
  • Review sheet before submit — FormData snapshot grouped by section, auto-tagged rows, tap-a-row jumps back to its section, Confirm calls formEl.requestSubmit(); Enter-key submits pass the same gate via onsubmit. Server accepts direct posts unchanged (review is UI-only).
  • UI-only rule: every POST name/action/handler stays byte-identical; +page.server.ts untouched; no value-exports from server files.

Focus entry — APP-WIDE STANDARD (PLANNING §211–226)

The owner's signed mobile-first entry pattern — full replication recipe in docs/FOCUS-ENTRY.md; reference src/routes/leads/new/+page.svelte. Use it for new/entry forms; it supersedes the guided-section form look for those screens (the summary replaces the always-open form, the wizard replaces the section menu). In short:

  • Read-only sectioned summary on the page, styled exactly like the old "Check & save" sheet (h3 text-lg font-bold title, h4 uppercase section labels, bordered divide-y rows, muted label + font-medium value).
  • Tap any row → a centred wizard pop-up (fixed, bg-black/15 backdrop-blur-[2px], max-w-md max-h-[85dvh], progress bar, Back/Next), one field per step, no autofocus.
  • Field steps: searchable picks (results in flow ABOVE the input) + suggestion coins; NumberWheel.svelte for money (typeable, slot-machine tick(), faint sides, mid-scroll sensitive); a ±hold-to-repeat stepper for quantity; textarea for notes.
  • Required rows show a red * + amber "Tap to add"; optional show muted "Optional"; the subtitle counts "{n} required to fill"; Save is disabled until required fields are set.
  • The field list comes from the map, not the screen. Every field static/full-flow.html lists for that form must be on it, with its listed mode — a wizard step when it is an important field, otherwise a summary row that opens its own single-field popup. A missing field is added in the same change, with the column, migration and server read/write it needs; never deferred. The rollout lists field → mode → surface in its PLANNING entry, so a field nobody can name there is visibly missing rather than assumed absent (docs/FOCUS-ENTRY.md §11 rule 6).
  • Save → animated-tick pop-up (success() sound/haptic) → auto-close 1.5s → goto('<list>?created=<id>') → the list highlights the new row.
  • Mobile rules: lock page scroll while the wizard is open, overscroll-y-contain on wizard body + wheel, inputs scrollIntoView on focus, viewport interactive-widget=resizes-content.
  • A wizard step is only for an important field — compulsory to enter AND no default value set (a system/automatic value, a master/today/last-used default, an owner-instructed standing default like other cost 5.5% in the quote form, or a plainly optional field is a summary row that opens its own single-field popup, never a step; a prefill the user must confirm and a suggestion chip still count as important). "The wizard" = the one-field-at-a-time steps only — the sectioned summary and the single-field popups are unchanged, and nothing becomes un-editable. In doubt, ask (FOCUS-ENTRY.md §11 rule 5).
  • Every field static/full-flow.html lists for that form must be on it, with its listed mode — step if important, otherwise a summary row that opens its own single-field popup; a missing field is added in the same change, and the rollout lists field → mode → surface in its PLANNING entry (FOCUS-ENTRY.md §11 rule 6).
  • UI-only: hidden real form keeps POST names/validation byte-identical.

Reverse pickers (PLANNING §138 — every entry form offers upstream)

  • Each form lists the records it continues (quote→order, lead→quote/ order, booking→gatepass, order→dispatch/hishob, beam→checking…) and carries every mappable field forward on pick (empty-only fills, never overwrites typing). Where no link column exists the carry is prefill-only. Skipped-with-reason where nothing real maps (order→booking: 0 fields; samples←lead: fuzzy).

Tap feedback — APP-WIDE STANDARD (PLANNING §132, live on guard + transporter)

Reference: src/routes/guard/+page.svelte. Shared pieces: src/lib/toast.svelte.ts (notify(msg, kind)) + src/lib/components/Toasts.svelte (mounted once in +layout.svelte — do not remount it).

  • Tap actions confirm with a toast, not a page cycle. Single-tap forms (exit, reconfirm, pickup, ticks…) use use:enhance={tapped('Done ✓')} where tapped notifies on result.type === 'success' then await update(). Errors keep their existing inline alert-error (toasts are success-only).
  • Plain words, no ids, no jargon. Toast text names the business outcome ("Vehicle exited ✓", "Pickup saved ✓"). Labels anywhere must never carry bible markers (【T】/(N)/…) or raw ids — those live in code comments, hidden inputs and URLs only.

Chip suggestion — APP-WIDE STANDARD (PLANNING §184)

Reference: the qty example (§180/§184). The term "chip suggestion" is what the owner uses to order tap-instead-of-type corrections.

  • What it is — previous values + contextual suggestions rendered as tappable pills in a horizontal-scroll row under the input, so entry is taps not typing. Chips are daisyUI btn btn-outline btn-sm (they DO something) — never badge (badges report status/counts).
  • Ranking — context match (lot multiples, design default, same-party history) → recent (data.recent, last-N) → frequent/common fallbacks. Max 4 chips; dedupe; drop non-positive. Active chip gets btn-active when it equals the input.
  • Behaviour — tap fills the input + marks it touched (clears the prefilled tag, feeds ReviewSheet facts); never auto-submits. The input keeps its POST name/validation/patterns byte-identical (UI-only).
  • Markup recipe — div.mt-1.flex.gap-1.overflow-x-auto.pb-1 holding button[type=button].btn.btn-outline.btn-sm.shrink-0 per suggestion.
  • Create row (C321) — when the typed/read text matches no master value, the pick list's last row is + <text> (a ghost button, not a badge): it opens that master's add form prefilled (/masters?tab=<master>&new=<text>) and returns the saved entry selected. A value the OCR produced and could not match appears as the same chip in the suggestion row. See FOCUS-ENTRY §20.
  • Rollout — party, rate, city, cut size, rolls, vehicle… same recipe everywhere; only the source list changes per field.