# OWNERSHIP.md - file map
The app is one SvelteKit tree; this is the human-readable map of which files each area touches, kept so a change can see its blast radius at a glance. Read it before editing a shared component, and update it when one splits.
Hot files (wide blast radius — read twice before editing)
| Path | Why |
|---|---|
src/lib/server/schema.ts, src/lib/server/schema-foundation.ts |
all tables; a migration touches everything |
src/lib/server/yarnMath.ts |
costing/conversion engine — owner-sheet trace |
src/lib/server/ops.ts |
ids, doc numbers, events, amendments, approvals |
src/lib/server/queue.ts |
per-role pending board (all roles) |
src/lib/company.ts |
every print head |
src/app.css |
theme pin (Rule 1 guard) |
src/routes/+layout.svelte |
shell / nav for every route |
src/lib/components/** |
shared UI (chips, pickers, popups) |
docs/UIPATTERN.md, docs/FOCUS-ENTRY.md |
signed design language |
drizzle/** |
migration journal |
Areas (target → area key)
An area key is just the first route/engine segment a row touches — a quick way to see which part of the app a row lives in. (No parallelism hangs off this any more; rows run one at a time regardless of area.)
| Lane key | Owns |
|---|---|
leads |
src/routes/leads/** |
quote |
src/routes/quote/** |
orders |
src/routes/orders/** · src/routes/sale/** · src/routes/prod/** (the kind's own list and record; one list component in src/lib/components/orders/OrdersList.svelte, one loader in src/routes/orders/ordersLoad.ts) |
bookings |
src/routes/bookings/** |
gatepass |
src/routes/gatepasses/** |
tippans |
src/routes/tippans/** |
sizing |
src/routes/sizing/** |
winding |
src/routes/winding/** |
weaving |
src/routes/beams/**, src/routes/loomgate/**, src/routes/weft-issue/**, src/routes/planning/** |
checking |
src/routes/checking/** |
dispatch |
src/routes/dispatch/**, src/routes/runner/** |
movements |
src/routes/movements/**, src/routes/inward/**, src/routes/stocktake |
hishob |
src/routes/hishob/**, src/routes/reconciliations/**, src/routes/approvals/** |
masters |
src/routes/masters/** |
samples |
src/routes/samples/** |
engine |
src/lib/server/*.ts (non-hot) |
infra |
scripts/**, systemd/tunnel/Caddy, docs/** only |
C175 beam-card components (split 2026-10-01 — the beam entry is its own file)
src/routes/beams/+page.svelte is a thin shell: the list (tabs, toolbar,
SmartTable + repair-out, out-house ledger) and the beam card entry, which
lives in src/lib/components/beam/BeamCardEntry.svelte. The C6b transfer stays
on the beam record page (src/routes/beams/[id]/+page.svelte) and calls the
shared focus shell directly — same as winding/sizing/movements, and the reason
is the POST: its action, field names and use:enhance are pinned in that page
by tests/statusact-warpweave.test.ts and tests/fwd-warp.test.ts. The
src/lib/components/beam/ directory is the weaving lane's own; a later
correction to beams touches it or the beams routes, never
src/lib/components/focus/FocusEntry.svelte (shared with six other lanes).
C88 quote components (split 2026-09-23 — one concern per file)
The three quote pages are thin shells (new 76 lines, edit 40 lines,
list 72 lines). Page-level state lives in route-local factories
(src/routes/quote/new/quoteNewState.svelte.ts,
src/routes/quote/edit/quoteEditState.svelte.ts — instantiated per visit,
not globals), passed down as a single s prop; POST actions, field names,
share text and print output are unchanged. Source pins travel via
tests/helpers/quoteSrc.ts (combined shell + state + components).
| Component | Concern |
|---|---|
src/lib/components/quote/NewWizard.svelte |
new-record focus wizard (dictate → lead → quality → warp → weft → job → party) |
src/lib/components/quote/NewSummary.svelte |
summary rows (enquiry/yarns/job/construction) + basic-cost + nav |
src/lib/components/quote/NewCalc.svelte |
calc cards (cost/weights/margin) + quote terms + hidden ?/create POST form |
src/lib/components/quote/TermEditor.svelte |
inline quote-term editor under its summary row (selling stepper + margin coins) |
src/lib/components/quote/YarnPopup.svelte |
per-yarn count/rate popup (board list + steppers + Excl/Incl) |
src/lib/components/quote/AdjustPopup.svelte |
construction scalar editor (coins + input + Set) |
src/lib/components/quote/QualityPopup.svelte |
add-quality master form embed |
src/lib/components/quote/NewSavedPopup.svelte |
save tick + details + share picker |
src/lib/components/quote/ConfirmChip.svelte |
C14 inline ✓ source chip (was the cchip snippet) |
src/lib/components/quote/EditQuoteForm.svelte |
?/save form shell (popup chrome, pending banner, Calculate, requote mirrors) |
src/lib/components/quote/EditLeadSection.svelte |
start-from-lead picker |
src/lib/components/quote/EditPartyQuality.svelte |
party search + quality picker + most-ordered chips |
src/lib/components/quote/EditConstruction.svelte |
reed/pick, width/GST, job, sizing scalars |
src/lib/components/quote/EditWarpRows.svelte |
warp rows + whole-name yarn picks |
src/lib/components/quote/EditWeftRows.svelte |
weft rows + legacy single rates |
src/lib/components/quote/EditShrinkage.svelte |
lasa/L2L/waste/other scalars |
src/lib/components/quote/EditSaveTerms.svelte |
final rate/validity/qty/timeline/party + check-&-save bar |
src/lib/components/quote/EditOutputs.svelte |
weights + cost-per-metre panels |
src/lib/components/quote/EditRateBoard.svelte |
yarn-rate board Fill table |
src/lib/components/quote/EditSavedQuotes.svelte |
saved-quotes search/scope/actions table |
src/lib/components/quote/QuotePreview.svelte |
list preview popup (status moves + share/chase/convert) |
src/lib/components/quote/QuoteListTable.svelte |
live filter + toolbar + SmartTable |
src/lib/components/quote/quoteListUtils.ts |
list share/date helpers (no markup) |
C104 booking components (focus entry 2026-09-25 — one concern per file)
/bookings/new keeps its guided sections + C14 review gate in +page.svelte
and composes the focus-entry layer below. Page-level state stays in the page
(the wizard binds the same $state the POST form binds, so POST names/values
stay byte-identical); the components take values + callbacks as props.
| Component | Concern |
|---|---|
src/lib/components/booking/BookingSummary.svelte |
sectioned read-only summary (Contract & product / Supplier & mill / Bags & weights / Rate & terms) + Clear/Save |
src/lib/components/booking/BookingWizard.svelte |
one-field-at-a-time popup wizard (against → yarn → warp/weft → supplier → broker → lot → date → bags → cones → bag wt → rate → pay term → remark; the GST % is no longer a step since C183 — it is prefilled from the yarn/fabric master and edited in its own box on the page) with coins, hold-steppers and NumberWheel |
src/lib/components/booking/BookingSavedPopup.svelte |
save tick + details, auto-close 1.5s → /bookings?created= |
C110 tippan components (focus entry 2026-09-27 — one concern per file)
/tippans/new composes the focus-entry layer below. The page keeps the state,
the last-program / gatepass / design prefills, the C14 confirm gate, the solver
wiring and the hidden POST form (every field NAME, so ?/create validates the
same bytes); the components take values + callbacks as props. The solver, the
record page, the print, the amend and ?/assign are untouched by this split.
| Component | Concern |
|---|---|
src/lib/components/tippan/TippanSummary.svelte |
sectioned read-only summary (Who & quality · Program · Yarn issue · Beams & notes); a row opens the popup for its field; the C14 source chip confirms a prefilled value |
src/lib/components/tippan/TippanWizard.svelte |
one-field-at-a-time popup, 23 steps declared as DATA (STEPS), so a field is one tap from its summary row. Text / number (inputmode + pattern + steppers + NumberWheel) / date / type-ahead list / coins / solver readout / floor-beam picker |
C163/C165 focus-entry rollout (C166/C168/C170/C173/C174 — one shared shell)
Every focus-entry row after the reference composes the ONE signed shell
(docs/FOCUS-ENTRY.md) instead of copying it: page-level state, the hidden POST
form (every field NAME, byte-identical) and the row's own config stay in the
route; the shell takes values + callbacks as props.
| Component | Concern |
|---|---|
src/lib/components/focus/FocusEntry.svelte |
sectioned read-only summary + dynamic "n required to fill" + Clear/Save gate; starts-open one-field-at-a-time wizard (z-40); a single-field popup (z-[60]) opened from a summary row for every NON-important field; success tick (z-[70]) → 1.5s → caller gotos. Modes: text · textarea · number · qty (hold-to-repeat) · money (NumberWheel) · pick (searchable, results above the input, removable chip, suggestion coins) · multi (searchable multi-select, removable chips) · static (auto/system rows, never typed) |
Supersession, stated once: a screen on this shell no longer carries the old
guided-section menu, the sticky Check & save bar, the per-page ReviewSheet
gate or the inline C14 ✓ chips. A legacy pin that asserted that markup is
re-pointed in the SAME change at the row's own acceptance (its hidden POST
names, its actions, its server validation, its auto rows and its untouched
list-side taps stay pinned) — never deleted silently, never left red.
C163 rule: a wizard step is an important field only — entry compulsory AND
no default value set. A defaulted / auto / optional field gets NO step and
opens its own popup from its summary row. C165 rule: every field
static/full-flow.html lists for a form is present in that row's config with
its listed mode (N number pad, T tap-select, X text, S suggestion, P prefill,
A auto, SC scan) — a missing field is added with its column + migration +
server read/write in the same change, never deferred.
C166 — A3 sample request (/samples, full-flow A3)
The ?/create sample-request form is the summary + wizard; the pack / pick /
dispatch / courier / delivered / follow-up / result actions stay list-side taps.
It opens straight into entry (a capture draft, or the first sample of a list
that has none). WIZARD_IDS = partyId (pick, S) · designId (pick, S) ·
qty (number, N) · rate (money, P). Summary rows + own popup: rateSrcStatic
(A — prefilled from the quality's recent order rate, set on onDesign, the row
stays changeable) · chargeChoice (pick, T) · waiverAmt (number, N) ·
purpose (text, X) · leadPick / orderPick (pick, P — the capture utility
prefill) · draftStatic / sampleNoStatic (A). POST names, values, ?/create
validation and every list-side action are unchanged.
C173 — C3 sizing (/sizing, full-flow C3)
One screen, two entry surfaces behind step — ?/receive (step 1, beams-in
gatepass) and ?/reconcile (step 2, full sizing reconciliation) — each a
sectioned summary + its own wizard over its own hidden POST form. ?/windingGate
stays a tap (system-allowed once the final hishob is done).
Step 1 RECEIVE_STEPS = beamIds (multi/SC, guard beam taps) · sizerId
(pick) · mtrsPerBeam (N). Summary rows + own popup: setNo1 (P, prefilled
from the tapped beams) · endsPerBeam (N, stepper) · beamQuality (X, OCR slip
→ confirm) · gpNoStatic (A, auto on save).
Step 2 RECON_STEPS = sizerId2 (pick) · usedKg (N). Summary rows + own
popup: setNo2 (P) · leftoverKg (N) · windingKg (N) · winderId (pick,
gate fires after owner approval); static auto rows carryStatic (P) ·
receivedStatic (A, leftover + winding yarn).
Both steps are OCR-assisted (full-flow C3 ã€OCR → confirm】): the scan: true
rows mtrsPerBeam · beamQuality · usedKg send one slip photo to the existing
POST /api/ocr and the parser below prefills through the page's onfield.
| Module | Concern |
|---|---|
src/lib/sizingOcr.ts |
reads the sizing/hishob slip OCR text into step-1 mtrs/beam · ends · quality and step-2 used kg · leftover kg · winding-yarn kg. Line-based (a field is read off ITS OWN line), a MTRS PER BEAM line outranks a bare MTR n beam count, only a QUALITY-labelled line becomes the quality, and anything it cannot read stays empty for typing — never guessed. Prefill only: the values land through the page's onfield, so no POST name, value or server validation changes. |
C171 — B7 stock take / cone balance (/lots/stocktake, full-flow B7)
The ?/create cone-count form is the sectioned summary + wizard, composed from
the shared shell; the list stays list-side in the route (the differing-counts
card, the unmarked-barcode card and the SmartTable) because its forward taps and
its POST pins are owned by that route's own tests — the same split the C166 /
C168 / C169 / C170 / C173 / C174 lanes use. No src/lib/components/stocktake/
file exists and none is needed: the entry surface IS the shared shell, and the
per-field list is data (SECTIONS), not markup — so a later correction to B7
touches this route's config, never the shell.
WIZARD = countedCones (qty, N) — the only important field on B7: the
server returns 400 without it and nothing defaults it. Summary rows + own popup:
bagBarcode (text + scan: true, SC — a scan target; a barcode on no bag is a
wanted input, it feeds the unmarked list) · yarnId (pick, S — bag-scan prefill,
confirmed or edited) · expectedCones (qty, A — auto reference) · countType
(pick, T — the standing non-zero default, so no step). Read-only automatic
rows: varianceStatic (A, live) · unmarkedStatic (A) · differenceStatic (A).
The C14 confirm gate survives as save(): a scan prefill that was neither
confirmed nor edited names itself instead of being silently saved.
B7 is a labour screen and stays zero free text — selection, scan and number only; the bag barcode is the one scan field.
Proof of the above (PLANNING §342, 2026-10-01): bun run screens -- --only stocktake passes for a signed-in user against a worktree dev server, and a
real ?/create POST answers with the new id and lands on
/lots/stocktake?created=<id> with the row flashing. Run the screen check
against a worktree with ORIGIN set and LIVE_CHECK_BASE pointed at it —
better-auth pins baseURL/trustedOrigins to env.ORIGIN, and without it the
sign-in never returns a cookie (the failure looks like a stale DB; it is not).
C168 — B0 · B5b gatepass (/gatepasses/new, full-flow B0/B0b/B5b/G0/G0b/C2/C3-1/C5)
One route, one page, eight per-kind field sets (KIND_CFG + SECTIONS), so
one row. WIZARD_IDS gives the important-field steps per kind; every other
field of that kind is a summary row + its own popup.
| Kind (flow row) | Wizard steps | Summary rows + own popup |
|---|---|---|
YARN_IN (G0, G0b) |
sourcePartyId (pick, S) · countYarn (pick, S) |
destPlace (pick, T — our godown default) · targetPartyId (pick) · endorseTo (pick, T) · ticket (pick, S) · millLotNo (pick, S) · bags/cones/conesPerBag (qty, N/P) · gross/net (number, N) · sourceOrderId (pick, P) |
YARN_OUT (B5b) |
targetPartyId (pick, S) · lotIds (multi, T/SC — yarn barcodes) |
countYarn · ticket · bags/cones/conesPerBag · gross/net · sourceOrderId |
BEAM_OUT (B0 intimation, C2) |
targetPartyId (pick, S — "To sizer") · beamType (pick, T) · beamCount (qty, N) · beamIds (multi, T/SC — floor flange taps) · expectedDate (date, N) |
sourceOrderId (pick, S) · statics kindStatic (A) · carryStatic (A) · gpNoStatic (A) · statusStatic (A) |
BEAM_IN (C3 step 1) |
sourcePartyId (pick, S — from sizer) · beamIds (multi, T/SC) · beamMtrs (number, OCR) |
setNo (text, P) · beamEnds (qty, N) · beamQuality (text, OCR → confirm) |
BEAM_REPAIR (C2) |
targetPartyId (pick, S) · beamIds (multi, T/SC) |
setNo (P) · sourceOrderId (P) |
BEAM_RETURN (C2) |
targetPartyId (pick, S) · beamIds (multi, T/SC) |
setNo (P) · sourceOrderId (P) |
WINDING (C5 yarn out) |
targetPartyId (pick, S — to winder) · net (number, N) |
setNo (P, sizing/warping set) · bags/cones (qty, N) · gross (number, N) · transferYear (text, P — current FY) |
DIRECT (G0b direct delivery) |
sourcePartyId (pick, S) · expectedDate (date, N) · bags (qty, N) |
destPlace (T) · targetPartyId (pick) · countYarn (pick, S) · millLotNo (pick, S) · conesPerBag (qty, P) · gross/net (number, N) |
Shared on every kind: gpDate (P — today) · bookingPick (P — the booking
prefill, never a column) and its carryStatic provenance row · the B6 weft
receipt text fields partnerGpNo (X) · lotRef (X) on YARN_IN · the photo /
vehicle / transport section (image PH via /upload-file?bin=screenshots ·
vehicle SC — camera OCR of the plate with a typed fallback · transport S).
B0b (beam outward) stays zero entry — guard taps only, the actual date and
transporter are prefilled; it is a scan-and-tap screen, no invented fields.
C169 — B2 yarn inward (/inward, full-flow B2)
The board is unchanged: the ?/markPicked arrival tap (vehicle + bags) and its
POST stay as they were, and the row's Received ✓ tap now opens the B2
inward receipt — the sectioned summary + a one-field wizard over the hidden
?/markDelivered POST form (id · bags · net · photo byte-identical,
millLotNo added by C165 on the gatepass's existing mill_lot_no column — no
migration). B2 is the automatic leg, so INWARD_WIZARD_IDS = ['millLotNo'] is
the lot no alone. Summary rows + own popup: bags (qty, prefilled from the
gatepass) · net (number, prefilled) · photo (optional). Read-only statics
(A): inwardNo · dateStatic · partyStatic · orderStatic ·
warpWeftStatic · barcodeStatic · conesStatic · grossStatic.
| Component | Concern |
|---|---|
src/lib/components/inward/inwardFields.ts |
the B2 field map — the sectioned summary config, the static auto rows and INWARD_WIZARD_IDS (C163/C165), no markup and no POST |
C170 — B5 · B6 movements (/movements, full-flow B5/B6)
The list, the toolbar, the row dialog and the audit card stay list-side; the
?/create entry form is the summary + wizard over the hidden POST form.
kind prefills from the ?kind= list filter (a default → no wizard step,
its own popup); reason is a prefill the user must confirm, so it is a
step. Item rows follow the picked yarn refs (C165: the lot is the row's
identity — the server resolves a bag barcode to its lot), so per-row numbers
are keyed by that ref and the positional lot/bags/cones/kg arrays ride
unchanged. No new field was needed (every B5 flow field already exists, and
the return barcode is minted server-side); the only server change is the
additive created read for ?created=<id>. B6 (weft inward) has no form in
the app and this row does not create one — the gatepass inward path covers
receipts.
| Kind (flow row) | Wizard steps | Summary rows + own popup |
|---|---|---|
party_to_factory / factory_to_party (account flip ã€T】) |
reason (pick, T) · fromPartyId (pick, S) · toPartyId (pick, S) · lots (multi, T/SC) |
kind (pick, T — filter default) · statics docStatic (A) · auditStatic (A) |
contract_to_contract / short (re-mark incl. short ã€S】) |
reason · fromOrderId (pick, S) · toOrderId (pick, S) · lots |
per-row bags/cones (qty, N) · kg (number, N) · itemsStatic (A, totals) · vehicle (text, N) |
weft_out (to weaver ã€S】) |
reason · toPartyId (pick, S — weaver master first) · lots |
per-row loom/shift (pick, T — shift set from the loom) · weft (pick, S + history chips) |
return_to_party |
reason · toPartyId (pick, S) · lots |
the per-row numbers + vehicle as above |
return_from_production (machine source → NEW barcode + weighed kg + cones ã€SC+N】) |
reason · machineSource (pick, SC — loom master + winding-m/c) · lots · every row's kg + cones (compulsory, no default) |
newBarcodeStatic (A, minted on save) · bags per row · vehicle |
C174 — C5 winding in (/winding, full-flow C5)
Wizard steps (compulsory, no default): winderId (pick) · sizerSetNo (text)
· expectedKg (number) · recNet (number). Everything else is a summary row
- its own popup:
gate(sizing-gate carry, pick) ·warpingSetNo(P) ·transferYear(P) ·bags/cones(qty) ·grossKg/netKg/empties/ringWt(number) · static auto rowsgpNo(A) ·setCountStatic(set, A) ·diffStatic(diff net/gr wt, A).setNosstays the POST name and is the sizer-set + warping-set list joined once each.
C167 — A4c job-work outsource contract (the four production forms)
The four /production/* screens stay thin shells over the ONE shared
src/lib/components/production/ProductionOpForm.svelte (splitting that file is
its own decision), and it is NOT on the shared focus/FocusEntry.svelte shell:
its own dictation/OCR boards, its order picker, its multipart contract file and
its ?/complete POST are pinned by C112/C114/C149, so it keeps its component
and takes the pattern in-place — a read-only sectioned summary over the hidden
real POST form, a starts-open ONE-field-at-a-time wizard (z-40) and a single-field
popup (z-[60]) for every non-step field, with the shared animated tick
(SavedPopup, z-[70]) held ~5 s (C151 §7) then
goto('/orders?created=<orderId>').
C167 lane. The single-field pop-up lives in its OWN component,
src/lib/components/production/ProductionFieldPopup.svelte, so a later
correction to a pop-up is file-disjoint from the form. The important-fields
decision (which fields are wizard steps at all) and the full-flow A4c
field → mode → surface list live in src/routes/orders/new/orderLogic.ts as
PRODUCTION_OP_IMPORTANT_FIELDS and PRODUCTION_OP_FIELD_SURFACES; the spec
PRODUCTION_OP_FIELD_SPEC still drives the summary rows.
Wizard steps (compulsory, no default): order (pick) · partyId (pick — only
when the picked order carries no buyer) · note (textarea). Everything else is a
summary row + its own popup: partyContractNo (text) · mode (pick) ·
jobRate (number) · picks (number + ± stepper) · rate (money, NumberWheel —
carried from the order) · cdPct / brokeragePct / passOnPct (number) ·
paymentDays / dueDays / promisedDays (number) · terms (text) ·
tcsEffRate / tcsUncondRate (number) · tcsLiability (text — the flow's
modelled clauses) · loomType / beamsSource (pick) · contractFile (file,
image/*,.pdf) · sourceDraftId (pick) · tolKind (pick) · diffMtr /
diffBeam (number) · deliveryGpId (pick — the delivery/gatepass leg) ·
stockTrigger / stockTarget (text + suggestions) · lotSizeMtrPerRoll /
rolls (number, rolls with a ± stepper) · deliveryForm (pick) · dispatchCity
(text). Read-only auto row: designStatic (the flow's design + alias, CARRIED on
the order, so no column and no entry field). perPieceMtr stays carried-but-
hidden per PRODUCTION_OP_INTENTIONALLY_HIDDEN. POST names, values, the action,
use:enhance and the server validation are unchanged.
Wide changes
A change spanning two area keys (e.g. booking -> gatepass prefill) is fine - it is one change, not a batch. Prefer splitting a wide form change into one change per form so each can be proved on its own screen.