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.tsis the only navigation source (NAV,UTILITY,HIDDEN);+layout.sveltedraws 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 carryaria-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
HIDDENwith 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 staleHIDDENentry, 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 itsHIDDENreason) 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-semiboldpanel 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, daisyUItable— nottable-sm— gray thead,px-4 py-2cells,—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 amax-h-[75vh]scroll region, semibold first (doc-no) column,tabular-numsright-aligned numerics, ISO timestamps rendered as "08 Sep 2026", long text clamped to 2 lines with full-text tooltip, badge columns throughstatusWords()— 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 = headerNewmenu + attention strip + period bar + the one pulse + one-open section rows; component pages = depth-bar crumb ← Main, ⓘ What·Data·Who (collapse), KPIstats, 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)
- Page wrapper —
<div class="min-h-screen bg-base-200 p-4 sm:p-6">with an innermx-auto max-w-4xl/5xl/6xl(choose by content width).bg-base-200is the zinc-100 workspace; the shell caps everything atmax-w-7xl. - 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="…">.
- one-line
- Panel header per section —
<h2 class="text-lg font-bold tracking-tight">+ desc line +badge badge-outlinecount. - Tables — white bordered card
card overflow-x-auto border-base-300 bg-base-100→table table-sm, theadbg-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-mediumon primary cells. Row-hover inset bar (zinc-900, §195) comes from app.css automatically. - 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, apline, and an outline reveal button (see reveal() below). - 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 headingscol-span-full text-xs font-semibold uppercase tracking-wider text-base-content/50; submit right-alignedbtn btn-primary btn-sm. On list screens, wrap the form in a daisyUIdetails collapse collapse-arrow(auto-open when the list is empty); on form-first screens, the form card IS the page. - Action buttons — primary actions
btn btn-primary btn-sm; row actions tiny POST formsinline-flex gap-1withbtn btn-outline btn-<color> btn-xs; destructive/queue actions keep their semantics (confirm=success, discard=error, repair=warning). 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;}.- Form errors — daisyUI
alert alert-errorfed bypage.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). AFunnelbadge announces the filter group before its selects (each select labelledaria-label="<label> filter");Clear(X),CSV(Download) andSearch(Search) are icon+word buttons; the count line reads "Showing 1–50 of 320" / "No matches"; the pager isChevronLeft 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 isbtn-primary, every other onebtn-outline, and a wordedX Closesits last beside the header ×. On phones the actions aremin-h-11(a 44px finger target), tightening tobtn-smfromsmup. - Plain-table row action (
SmartTable.svelte). The default action is abtn btn-outline btn-xswith aChevronRight— a visible target that points forward — never the oldlink link-primarytext link. Screens that pass their ownactionsslot are unchanged. - Action screens are cards.
/pendingwas 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/?/assignBookingform, hidden id, contract picker anduse:enhancebeneath 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.sveltefiles. Never touch+page.server.tsfiles, schema,app.css,+layout.svelte, tests, docs,package.json. - Never change
nameattributes,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, ordark:variants. - Mobile-first: tables scroll within
overflow-x-autocards; 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_SHEETSin the audit) — a printed A5/thermal document is not an app screen; daisyUI has no paper component, so a sheet keeps its own.tblgrid 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
btnhas 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
Newtyped menu — a<details class="dropdown dropdown-end">whose<summary>isbtn btn-primary btn-smand whose rows aremin-h-11icon + 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-smwith a countbadge badge-xs— tone map primary/ warning/info/secondary) → period bar (form method=GET action=/withfrom/todate inputs; binds dated stats only — stock/beam positions are live and say so) → the one 14-day pulse card → aBoards — tap one to open itcaption → nine collapse rows, exactly one open: each section is a native<details class="collapse collapse-arrow" name="dash-sec">withopenon one row only (the browser's exclusive group — the C296 sidebar reading applied to the board rows), the<summary>carries theh2(the ScrollMap tick), the section badge,{n} red/{n} watchtallies counted from the cards'warn/badKPIs (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 acarousel w-full gap-3with onecarousel-item w-[86%]per card — one swipeable row per section, never a 29-card stack (C293) — and fromsmup the gridgrid 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:cardwithtitleattr = the one-line explainer; KPI chips arerounded-lg bg-base-200/70divs (valuetabular-nums+ tone color text-success/warning/error), thenopen page →. Not-ready cards (builds with later-phase data) show the spec note instead of KPIs and abadge badge-outlinephase tag — never a fabricated number. - Scroll map (
ScrollMap.svelte, C289) — the home dashboard mounts it once, above the content wrapper (never insidefade-in: a transformed ancestor capturesfixed). It measures the shell'smainscroller and draws one 1px tick per section on a56vhrail 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 fromapp.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-readyalert alert-infowhen flagged → KPIstats(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 daisyUIbadgepills. - 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
ListToolbarcard — search input (input input-bordered input-smwith a lucideSearchicon), one filterselectper 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-numssums under the right-aligned numeric columns), and a⬇ CSVbutton (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/categorybadges + id note; then a crumb← Back to list; then sections ascards — Terms / Linked docs / Events / Amendments — the last two are read-only history fromamendment+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
modalwith a textarea + the SAME?/amend//api/amendfields 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-smPOST to/api/approve; before approval the print button is disabled with abadge 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.
- Amend — opens a daisyUI
- 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.qechoed in the header, no matches → the standard empty state. - Entry screens — party/design pickers are comboboxes, not
<select>:input+ filtereddropdown/menuofcode — namematches (≥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),superValidatein load + action (fail(400, { form })on invalid,setErrorfor business checks), and on the<form>use:enhancefrom$app/forms(kit's own) — NOT superforms. Errors render from the action result's Svelte 5formprop:const errs = $derived(form?.form?.errors)→ field errors under the input astext-xs text-error; form-level errors (zod.refine) as thealert alert-errorviaerrs?._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 withselected); where a preview needs reactivity (bookings per-cone/net, rate-hint Use) bind to local$state. Number fields arez.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'])), neverz.boolean()(superforms maps the posted'0'→true). Bespoke entry forms (combobox+voice prefill, OCR capture, multi-row) stay on the plainuse:enhancepattern. Per-row tap lists submit the row id in the DOM; a failed result is targeted to its row by comparing the returned form'sdata.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}withoncloseclearing 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 columnstext-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-lgtitle + 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) + theCheck & …button. Facts keep tallying scroll-free. - Prefilled badges — history/design-filled inputs show a muted
prefilledtag 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 viaonsubmit. Server accepts direct posts unchanged (review is UI-only). - UI-only rule: every POST name/action/handler stays byte-identical;
+page.server.tsuntouched; 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-boldtitle,h4uppercase section labels, bordereddivide-yrows, muted label +font-mediumvalue). - 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, noautofocus. - Field steps: searchable picks (results in flow ABOVE the input) + suggestion
coins;
NumberWheel.sveltefor money (typeable, slot-machinetick(), 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.htmllists 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-containon wizard body + wheel, inputsscrollIntoViewon focus, viewportinteractive-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.htmllists 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 ✓')}wheretappednotifies onresult.type === 'success'thenawait update(). Errors keep their existing inlinealert-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) — neverbadge(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 getsbtn-activewhen it equals the input. - Behaviour — tap fills the input + marks it touched (clears the
prefilledtag, 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-1holdingbutton[type=button].btn.btn-outline.btn-sm.shrink-0per 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.