Changelog
Every shipped change to KVICK.BIKE — the clean-room bike-vertical Hub on Cloudflare Workers + D1. Source of truth: CHANGELOG.md in the kvick-bike repo.
Versioning re-baselined 2026-07-27 — positional, auto-generated from git. Tom rolled the runtime VERSION to v0.7.0 (superseding the "absurd v0.6.1000" the previous scheme was heading toward), then formalised the scheme: every code-altering commit is a version, numbered by position (v0.0.NNN, where NNN = count of code-altering commits). VERSION is generated at build time from git log by scripts/gen-changelog.mjs (a wrangler [build] hook) and imported into src/index.ts from a gitignored src/core/changelog.ts build artifact — no more hand-bumped constant. Only commits that touch src/ / migrations/ / public/ count; changelog: regen commits and no-code commits are excluded, so a version maps 1:1 to a pushed code change. Each generated entry carries a technical layer (commit subject + body, Co-Authored-By stripped) and an operator Plain: line pulled from the body (SHA-keyed curated fallback for older commits). At 697 commits → 669 versions when the scheme landed; build-info now equals the card's newest entry by construction.
vX.X.X prefix (rule reversed)An earlier iteration of this doctrine asked commit subjects to carry v0.7.NNN as a self-identifying counter. That rule is retired. The version is derived from commit position by the generator; a prefix on the subject just prints a fake second version on the card. Current convention: subjects are plain prose (imperative mood, no leading version tag). The generator numbers versions from git log --count; the subject stays clean. See memory rule feedback_changelog_commit_convention.md.
The dated entries on this page cover the module-build era through July 2026. Since then the runtime authority is src/core/changelog.ts at build time (generated from git log by scripts/gen-changelog.mjs), and the operator-facing changelog card in the app renders from that generated file. This hand-authored page is a narrative retrospective for the module-build era, not the live changelog — for anything after v0.7.383 read the What's-New card in the app, or run the generator locally against a fresh clone of the repo. The Bible pages that describe module features cite specific commit dates + versions inline; those are the current source of history at the feature level.
The tree was rolled back to v0.6.929, undoing twenty-two versions. The commits from that range are still ancestors of HEAD, so git log reports every one of them as landed while the files say otherwise. Any tool a reader uses to ask "did this ship?" against git will answer yes for those twenty-two versions when the answer is no. Nothing is lost — the branch pre-rollback-v0.6.950 on origin points at d3d5804 (v0.6.951, last commit before rollback) and preserves every affected path. Re-landing any of it is a checkout of specific paths, not a rebuild. See Three Sessions § Part 2 for the full state at v0.6.954, the verified open issues, and the "what to settle first" list.
Earlier drafts of this page reserved v1.0.0 for "full monolith parity, running as Swicked's production". That framing assumed mimic the monolith verbatim was the design method. Tom overruled that method on 2026-07-23 — the modular build's logic is now considered superior in most instances and the monolith is a reference, not a template (see The monolith is a reference, not a template below). What v1.0.0 means going forward — whether it still marks a parity-and-cutover milestone, or something else — is an open question Tom hasn't ruled on. Do not assume either way.
The monolith is a reference, not a template
Changed by Tom, 2026-07-23. The earlier standing rule was mimic the monolith, never invent — it was the finished design, to be mirrored verbatim, and the only permitted transformation was breaking it into modules. That rule is now retired. In Tom's words: the monolith "is not to be replicated, only referenced. Our logic is superior in most instances now."
- Read the monolith to learn what a feature was FOR and how the shop actually used it.
- Do not copy its markup, mechanism or data model on the grounds that it is the spec. It is no longer the spec.
- A divergence from the monolith is not a defect. Under the old rule a parity sweep would find better logic and "correct" it back to the original behaviour — that must not happen again.
Two consequences this does NOT answer, and this documentation does not assume either way — they are Tom's:
- Whether the overrule is forward-only, or reopens decisions already shipped because the monolith said so.
- What v1.0.0 now means, since parity is no longer the method — see the caution admonition above.
Unchanged: the monolith is still never a data source without Tom's explicit yes or no.
The block below records the earlier attempt to continue the monolith's line at v0.6.471+. It's kept here because that framing appears in commit messages and Bible history from 2026-07-22 → 2026-07-27. As of 2026-07-27 the runtime version is generated positionally from git log and the "continue the monolith's line" framing is retired at the runtime layer (the changelog card still lists entries by positional counter).
An earlier attempt started the modular build at v0.7.0. That made the modular build look like a new product line running in parallel with the monolith it was actually replacing — a false framing that also made an unfinished successor read as newer than the finished system. Under the new baseline the modular build continues the monolith's line: same product, re-architected, one continuous chronology from v0.6.001 through the monolith's v0.6.470 and into the modular build at v0.6.471+. There is no v0.7.x line; a brief v0.8.0–0.8.6 excursion mid-stream folds in too. The renumber is applied positionally at render time in scripts/gen-changelog.mjs — the same mechanism that folds the 1.x block — 454 entries with zero gaps.
Git history is not rewritten. Commit subjects on the repo still read v0.7.xxx — that's the chronology of what shipped when, and it's untouched. Bible pages that cite specific historical versions (e.g. "shipped in v0.7.410") are correct citations at that layer and stay as-written. Chronology is the truth; the number is just a label — the label changed, the chronology didn't.
v0.7.x — Products module (2026-06-25)
Re-baselined onto the prior version line. The clean build was briefly started at v1.0.0, but that over-claimed: this is a partial rebuild (Customers, Settings access-control, Products), while the in-production prior system sits at v0.6.470. A 1.x number made the unfinished successor read as newer than the finished system. The line now continues: v0.7.0 = 0.6.470 + 0.1 — same product, re-architected, honestly pre-1.0. The v1.x entries below are kept as history but were a premature reset folded into this baseline. (A second re-baseline in 2026-07 folded the v0.7.x line back into a continuation of v0.6.471+ — see the admonition at the top of this page. Chronology below is unchanged; only the labels moved.)
v0.7.383 — Till reset is "Esc" + Esc key binding (BIKE.L1-0020, 2026-07)
The Till-header reset control on every tab (Sales / Customers / Inventory / Purchases / Service) now reads "Esc" — was the accent-coloured serif "C". The physical Esc key performs that same action on the active tab via core/keys.ts: idle Esc clicks the active module-pane's visible + enabled .till-clear-dot, so the label and the key name the same action. Function unchanged — Sales = Hold, other tabs = clear the till but preserve the data. LIFO stack still runs first (Esc closes an open dialog before it resets). The old Sales-specific #sc-cancel onEscClear default was retired; the onEscClear API stays for explicit per-tab overrides.
v0.7.382 — Settings Audit Trail ledger (BIKE.L2-0135, 2026-07)
Read-only window on the L1 audit spine, pinned to the bottom of Settings. Renders the narrated summary from audit_events (migration 040) verbatim; server-side search + category / severity / outcome filters + date range (kbDatePicker) + click-to-sort (kbSortable) + offset Load older paging so filters / sort act on the whole history. sys.permissions.view (Owner + sys-admin). No schema change, no Core edits. See the Audit Trail ledger on the Settings module page.
v0.7.379 — AIM cutover tool + Sales audit-3 / alpha-v2 sweep + BC PST accessory rule + Park→Hold rename (2026-07)
The 79-patch run that shipped the AIM cutover tool as a real operator surface, closed a whole audit backlog on Sales (audit-3 + alpha-v2), landed the BC PST bundled-accessory rule at Till parity, and did an owner-directed Park → Hold/Held vocabulary sweep.
-
Migration module (BIKE.L1-0016, v0.7.368). New sys-admin-only tab:
src/modules/migration/+migrations/etl/. Backup picker (v0.7.370), localextract.py→ SQLite snapshot →stage.py→ D1aim_raw_*staging → converter (catalog → suppliers → customers → sales → bikes → service → purchasing → finalize) → fidelity gate BEFORE wipe → parity report AFTER convert → commit or D1 Time Travel rollback. Runner pumps one step per POST, cursor inmigration_jobs. New migrations:049_migration_tool.sql(permission +migration_jobs+migration_presets, seeded preset "Fresh: Customers + Inventory + Sales History"). Tier-1 money bugs fixed at the source (T1 #1/#4 deposit signs +deposit_kind, T1 #2total_tax_centsin INSERT, T1 #3 return signs, T1 #5payments.staff_id); Tier-2 shape bugs (retired mfg/categories,supplier_sku, servicecreated_at,service_menutax); thesad_type='S'= SERIALIZED taxonomy fix (v0.7.373, 717 bikes were being loaded as service tickets);location_id=1pin (v0.7.372, 9,744 inventory rows had landed at re-seeded id 3). Static column-name validation on every INSERT now — the migration converter can't ship an INSERT that doesn't match live schema. New Bible page: Migration module. -
Park → Hold / Held rename (v0.7.337, owner directive). Operator-facing labels swap Park → Hold; substrate follows via migration 047 (
parked_sales→held_sales,parked_at→held_at,idx_parked_active→idx_held_active, notes prefixauto_parked:→auto_held:). Code identifiers rename in lock-step (parkActive→holdActive,resumeParked→resumeHeld,scheduleAutopark→scheduleAutohold,/parked→/held,data-parked→data-held). Paused-sale status text stays "Pending" (unified at v0.7.294) so an operator reads one word for the state; hover reads "Held by <cashier>". -
BC PST accessory rule at Till parity (BIKE.L2-0248). v0.7.373 first shipped a per-line "Fit to bike" toggle (migration 050
transaction_lines.fitted_to_bike) that would drop PST on a lock / cage / bell fitted to a bike-in-invoice. v0.7.378 removed the toggle at the Till — attachments are ALWAYS PST + GST at the counter; the fitted-to-bike exemption is granted only via the Service Tab (not built yet).fitted_to_bikecolumn kept but write path removed. v0.7.379 makes the rule DURABLE at the subcategory level —convert-catalog.tssetsproducts.default_tax_category_idby subcategory NAME (LIGHTS/FENDERS → exempt-by-category, COMPUTERS/CAR RACKS → fully taxable, LOCKS/BELLS/BAGS/PANNIER RACKS/WATER BOTTLES/BOTTLE CAGES/PUMPS → context-dependent-accessory). Tax resolver readsCOALESCE(inventory, product.default, category.default). -
Discount model correctness (alpha-v2 #1 BLOCKER). The gotcha named: per-line discounts are INSIDE the subtotal; cart discounts are OUTSIDE.
transactions.total_discount_centsholds both summed. Readingtotal_discount_centswhere you meantcart_discount_centsdouble-counts, over-refunds, and shows phantom balances on layaways. Formula:cart_discount_cents = total_discount_cents − SUM(transaction_discounts WHERE line_id IS NOT NULL).discount_cents. v0.7.359 fixed detail panel + hovercard + return dialog + reprint receipt + server/refund(was over-refunding by cart discount every time). v0.7.360 fixed/deposit(was computingtotal = subtotal + taxwith NO discount logic — phantom balance on every discounted layaway). v0.7.361 fixed/deposit-update-lines(a layaway edit was re-booking at full price). Same double-count bug found in the Python ETL —sal_soldpriceis already NET, converter was subtracting the discount a second time. -
Sales audit-3 sweep (v0.7.336 → v0.7.357):
- #1 money COLLECTED today — header value = net real-money tenders (excludes on_account / store_credit / gift_card redemptions, includes deposits); clickable → tender-breakdown popup grouping by
payments.card_brand(v0.7.345, audit-3 #6). - #2 gift-card issuance is not a sale/revenue — ETA s.181.2 + BC PST Bulletin 311 + ASPE treat it as deferred revenue liability;
/summarydrops variant-less tax-exempt liability lines from count + revenue. - #4 restocking fee is GST-inclusive — ETA s.182:
fee × 5 / 105. - #5 dynamic Take-Payment title — INVOICE / LAYAWAY / SPECIAL ORDER suffix, colour-keyed.
- #9 on-account confirm — shows before / +sale / new balance before you bill.
- #10 per-line tax badge — every cart line shows resolved tax treatment (
GST,PST,GST+PST,EXEMPT). - #11 split-tender brand-specific card legs — option value
card_present:<brand>so tender breakdown can attribute correctly. - #13 GC redemption placeholder — "Scan or type the card number", not the stale text.
- #14 receipt after installment —
/deposit-paymentfires the sameopenReceiptPrompt. - #15 refund count in header — matches the row count on screen.
- #16 discount reason on recalled sale + reason now optional.
- #1 money COLLECTED today — header value = net real-money tenders (excludes on_account / store_credit / gift_card redemptions, includes deposits); clickable → tender-breakdown popup grouping by
-
Sales alpha-v2 sweep (v0.7.359 → v0.7.365):
- #1 layaway honours discount (BLOCKER, above).
- #2/#7 till line floors at zero — 0 removes; closes the negative-cart cash-payout vector (reverses direction on BIKE.L2-0215).
- #3 in-progress PENDING row tracks live till — draft PUT branch calls
loadLedgeron every change. - #4 collected-today refreshes after deposit installment —
loadSummaryfires after every mutation. - #5/#6 discount double-count (above).
- #10 typeahead race guard — stale search responses whose query no longer matches box are dropped.
-
Cart line UI polish (v0.7.347/.348/.374/.375/.376). Each cart line's right column is now a 2×2 grid: row 1 + Discount / unit price, row 2 − Remove / line total (right-aligned under unit price). Per-line + Discount popup takes % / $ + reason; reason lands on
transaction_discountsand prints on receipt + estimate + shows on the recalled sale. -
Take-Payment unification (v0.7.342/.343/.353). Title type-pill (mint INVOICE / cyan LAYAWAY / peach SPECIAL ORDER), purpose-drawn
PAYICONSVGs per tender (no emoji), one Charge popup drives every path including installments (standalone Add-Funds modal purged). Split-tender offers brand-specific card legs. -
Change due BIG on cash sale (v0.7.371, counter spec scenario 1) — 34-px DM Serif Display, colour-shifts green when change > 0.
-
Layaway / Special Order without a customer → auto-attach (v0.7.377). Opens Attach Customer with Keep walk-in hidden;
pendingActionfires the original action on pick. Closes silent walk-in oversell on full-pay-when-out-of-stock. -
Ledger + detail polish. Today's Held/Pending is scoped to today (v0.7.349, local hold-date). Clickable Returned pill in the detail band opens the linked refund (v0.7.350). "(click to edit customer)" hint on the ledger heading (v0.7.338/.339/.340). Header no longer leads "Till open" (v0.7.351) — reads "Cashier X · N sales today · $Y collected · N refunds".
-
Reverse payment on open deposit (v0.7.333, Tier-2 #13).
/payment-reverseextended to open layaways/SOs; ledger shows reverse + refund rows; client reopens detail for re-collect. -
Ad-hoc mid-sale discount (v0.7.334, Tier-2 #14). Manual on-Till + Discount with reason; stored on
transaction_discountswithdiscount_type='manual'. -
No-match create-customer (v0.7.335, Tier-2 #18). Idle bar "+ Create customer" pre-fills name from the search query.
-
Phone + email validation (v0.7.366, audit #8). App-wide.
core/formats.tsmask inferred fromtype=tel/type=email; blur validates + marksaria-invalid;kbFmt.phoneValid/kbFmt.fieldValidexported. -
PIN gate (v0.7.335, .358). Label reads "Enter your PIN to continue";
harden()stampsautocomplete="new-password"on password inputs so Chrome stops replaying a saved admin code (audit v2 #11). -
Till reset "C" back (v0.7.346). Reverts the "K"-in-circle experiment (v0.7.329/.331) to the accent-coloured serif "C" across all tabs.
-
Search hint occlusion fix (v0.7.367, audit v2 #6 / v3 C).
sales.search_barscaffolding hint refuses on non-empty input; open hint retires on first keystroke; migration 048 rewords copy to phase-neutral. -
New migrations.
047_hold_rename.sql,048_search_hint_copy.sql,049_migration_tool.sql,050_fitted_to_bike.sql.
v0.7.327 — Gift cards + Account credit + Receipt engine + Tier-0/1/2 audit sweep (2026-07)
The 91-patch run that took Sales from "Till + Charge popup at parity" to "the shop's ten audit findings closed, gift cards + account credit + misc lines shipped, receipts industrialised, scaffolding tooltips teaching the register."
-
Scaffolding tooltips engine. New Kernel primitive
core/scaffold.ts+ migrations 043 (per-stafftooltip_mode+staff_feature_state) + 044 (feature_registryseeded with 129 hints across every module). Per-staff On / Off / Heuristic control; decay curves byfrequency_class(high 15/30 · medium 30/60 · low 8/120 · rare 4/180); idle past the decay window resetsuse_countto 1 (re-teach after a two-week absence, v0.7.313); keyboard-Tab-within-700 ms gate kills programmatic-focus firing (v0.7.318); pure hovercard, no ⓘ marker (v0.7.305); replaces the control's owntitle(v0.7.306). New Bible page: Scaffolding tooltips. -
Gift cards + Account credit + Misc-line pill. The
GC/Bulk/Miscpill (v0.7.302) opens five kinds of non-catalog line: Misc / open-price item (v0.7.298, also fires from a no-match search); + New inventory item (v0.7.299, cross-mounts Inventory hidden viakbEnsureModule); + Gift Card (bearer, scanned serial, dupe-reject); + Account Credit (retainer,customer_account_ledger); Bulk (realis_bulkSKUs). The−/+stepper is repurposed on GC/AC lines (v0.7.307) — add-another / confirm-remove, never a qty change. Migration 046 (transaction_lines.account_credit_customer_id, gift-card serial reusesserial_number); v0.7.308 fixes estimate → sale convert viareconstructStoredLine+issueSpecialLinesso a card isn't re-taxed or re-issued. Misc-line tax picker reads liveTAX_CATS(v0.7.300). Add-account-credit action on the customer Account modal (v0.7.309). Per-shop tender toggles — migration 045 + f63cae3, owner-only Settings switches gate+ Gift Cardand+ Account Creditat the pill and the customer card. -
Receipt engine — one receipt everywhere. New architecture: Receipt engine. Post-sale prompt (v0.7.252, Print / Email-soon / No receipt, Enter=Print,
localStoragealways-auto-print opt-in); industry-standard retail anatomy (v0.7.253, letterhead + GST/PST# fromshop_config, SKU + qty @ unit, Tendered / Change); "Served by <initials>" from__actor.name(v0.7.255); unified detail-panel Print onto the samereceiptHtmlwith adocLabelswitch — sale / refund / estimate / layaway / SO / invoice (v0.7.256); Estimate prints as a full-page Letter quote with terms + validity (v0.7.257,buildEstimateFromCart); print-prompt on estimate create (v0.7.258); humanized tax field name (v0.7.259). Deposits render a balance-owing line (v0.7.296). Issued gift-card number on the post-sale prompt header (v0.7.326). -
Core keyboard layer. New Kernel primitive
core/keys.ts(v0.7.282). LIFO Escape stack, shortcut registry, per-tabonEscClear,"?"cheat-sheet. Every modal / popup pushes onto the stack; Escape dismisses the topmost one at a time. New Bible page: Core keyboard layer. -
Tier-0 / 1 / 2 shop-owner audit sweep (Sales money-bug backlog closed):
- Tier-0 #2 — stray-click on payment no longer takes money (v0.7.315). 42-px gap between Split and the next tender tile, an "or" divider, confirm-step on every tile.
- Tier-0 #3 — cancelled sales no longer vanish (v0.7.317). Cancelled sales stay inline in Today's Sales, struck-through, no hidden footer (v0.7.319).
- Tier-1 #5 — On-account label + Receivables view (v0.7.324/.325). On-account sales read "On account", not "Invoiced"; own status-filter entry, amber pill. Customers-tab Receivables view (
GET /receivables, red owed pill on header,#recvroute, mutually supersedes YoY). - Tier-1 #6 — Cancel & refund honours original tender (v0.7.322).
deposit-cancelrefund goes back to the tender the deposit came in on; ledger row shows the tender name. - Tier-1 #7 — GC / Account-credit lines tax_category null (v0.7.323).
addLinehardens against the (already-fixed) case where a gift card or account credit was carrying a taxable category. - Tier-1 #8 — Gift-card number on the post-sale prompt (v0.7.326). Slip printed with the actual card number.
- Tier-2 #9 — Off-day rows dated in the single-date view (v0.7.327). Parked-across-days rows show "Jul 12, 9:43 p.m." not just a time.
-
Sales ledger refinement. Special-order lifecycle broken into Awaiting order → Ordered → Order Arrived (gold,
#fde68a) → Ready for pickup (v0.7.268/.270/.273/.274); after arrival becomes payment-aware (v0.7.248). Date-range picker works inside a status view (v0.7.277). Default status chip reads "Today's Sales" (v0.7.281). Today's Sales total is NET of refunds (v0.7.289). Local-timezone ledger dates (v0.7.279). -
Refund + tender defaults.
defaultRefundMethod()(v0.7.291) picks the destination from the original payment (card → card, cash → cash, on-account/store-credit/gift-card → store-credit). Red warning on cash-vs-non-cash mismatch; amber on other mismatches; hidden on match. Estimate button gated to customer-attached (v0.7.280). -
Card brand + last-4 everywhere.
payLabel()(v0.7.292) — brand replaces the generic "card" label;····1234appends when present. Hand-keyed card capture (v0.7.293) — type + last-4 + auth, all optional, ridescard_brand/card_last_four/reference_number. Nothing on the shop side stores a PAN. -
Editable tax on active transactions (v0.7.267).
POST /set-transaction-taxre-prices stored lines, rebuildstransaction_taxes, updates totals, re-derives deposit balance. -
Customer ledger mirrors Sales. Summary column (v0.7.285), Status pills with
STATUS_PILL_PALETTE(v0.7.286), shared row-builder (v0.7.288), Cashier column in single-customer view (v0.7.262). Held / Pending status text unified (v0.7.287 rename → v0.7.294 revert to Pending). -
Product-click → Inventory editor, in place (v0.7.260/.261).
kbEnsureModule('inventory')mounts Inventory hidden if unvisited; Inventory reparents its New-product modal to<body>on mount and exposeswindow.kbOpenProductEditor(vid). Sales fetches variant + opens same editor as overlay. Reused, not copied. v0.7.261 fixes the first-click ensure-mount guard. -
Reassign customer on posted transaction (v0.7.266). Detail-panel customer header opens the Attach popup;
POST /reassign-customeraudits, blocked when on-account or store-credit rows tie the sale to the original customer. -
Take-Payment banner (v0.7.301). Before money moves the modal names "Special Order" or "Layaway" if any line is out-of-stock or on order; receipt flags on-order items.
product_typegate filters services + rentals out of the special-order trigger. -
Tab-accent picker (migration 042, v0.7.263 → .265). Owner-only shop-wide tab colour picker in Settings — rainbow / pastel / colour-wheel, live preview, apply-on-pick.
shop_config.tab_accents_json; every--accentreads through the same helper. -
Till chrome + chrome. Idle-Till New Sale button (v0.7.276). Header reads "Current Sale — <Name>" / "Current Sale — (Empty)" (v0.7.278). Clear × is a bold serif "C" (v0.7.312, DM Serif Display 700). Layaway / SO buttons removed from every status (v0.7.250). Fold account management into Edit-customer modal (v0.7.311). Site-wide autofill disabled via
core/noautofill.ts(v0.7.283). Nav-tab underline retired (v0.7.284). Till column resizer un-bundled + bike-silhouette on the accent rule (v0.7.244–.246, .239). Inventory-adjusted toast colour-keyed to Inventory tab (v0.7.254). "Actual Stock Count" renames OOS stepper label (v0.7.272). -
Audit narrator house-style (v0.7.269).
narrateTerse()+ a full idiom map incore/audit-narrate.ts, cents →$X.XX, arrows and×normalized; ad-hocACTION_PHRASEretired. -
Version-notes popup + generated changelog (v0.7.291, v0.7.316, v0.7.321). New Kernel modules
core/versionmodal.ts+core/changelog.ts— auto-opens once per build vialocalStorage, ESC dismisses viakbKeys, generator renders the full commit body per entry. Refresh-the-card commits (no version bump) are dropped from the modal. -
New migrations.
041_payment_reverse_perm.sql,042_tab_accents.sql,043_tooltip_mode.sql,044_feature_registry.sql(129 seeded hints),045_pos_tender_flags.sql,046_special_line_on_line.sql.
v0.7.239 — Till + Ledger + Charge popup at prior-implementation parity (2026-07)
The 132-patch stretch that took Sales from "cart + tender" to "one Charge popup, in-Till detail panel, backorder-rings-through, audit narrator + ticker" — the substrate the operator now actually lives in. Grouped by cluster, patch counts approximate.
-
Audit narrator + ticker footer (v0.7.182 → v0.7.200). New Kernel modules:
core/audit-narrate.ts(deterministic verb/lexicon library keyed byevent_type) +core/audit.tsgainedrecordAuditEvent(FULL-coverage tamper hash) +recordNarrated+recentAuditEventsreader; migration 040audit_events;/api/audit/recent. The shell footer becomes a 25-px ticker tape below every tab (v0.7.195); toasts retired (v0.7.175); verbose hover peek only, click-to-open removed (v0.7.200) because a glanceable ticker isn't an inbox. Sale / void / refund / deposit-create / add-funds / deposit-cancel all route throughrecordNarrated. Even tersewriteAuditentries are now owner-readable — the narrator resolvesvariant #N/staff #N/transaction #Ninto readable names + actor + verb. New Bible page: Audit narrator + ticker. -
Backorder → Bucket flow (v0.7.190 → v0.7.204). The old "can't add — out of stock" popup is gone. Zero-stock lines ring through — red backorder N on the line; on Charge, the shortfall auto-drops into
inventory_order_queue(the Purchases Bucket), earmarked to the customer if attached (blue chip) or for Stock (neutral) if not. Sales posts as a Special Order; the ledger row tracks Awaiting order → Ordered → Partially received → Ready for pickup downstream. OOS pill reframed as "Adjust actual stock" — an inlinekbStepperthat commits live toinventory.quantity_on_handvia/countwith an "Inventory Adjusted" popup, for the miscount-correction case. Manual/reorderendpoint retired. New Bible page: Backorder → Bucket flow. -
Charge popup unification (v0.7.208 → v0.7.214). ONE
openConvertPay/Charge popup drives active-Till AND detail-panel commits. Footer is Cancel · Estimate · Charge; the transaction type auto-derives from what the operator enters — editable Total (partial → open order or layaway; full → sale; zero → estimate or "place the order — no payment" for $0 special orders, BIKE.L2-0212). Paid-off deposits hide Charge / Layaway (v0.7.208). Take-payment Total is obviously editable (v0.7.214) — solid border, radius, subtle fill, hint. -
Take-payment modal at prior-implementation parity (v0.7.206). 3×3 grid of nine tender tiles with emoji + one-line how; Debit / Mastercard / Visa become colour-keyed brand variants of
card_present(blue / red / indigo); big DM Serif TOTAL DUE; sharedtenderGridHtmldrives every tender surface (sale, deposit, refund-out, corrected-pay) so a change lands everywhere. -
Sales ledger overhaul (v0.7.204 / .205 / .215 / .216 / .218 / .220–.223 / .234–.239). Priciest line + "+N" on multi-item rows (N = physical items, SUM qty). Status filter dropdown from the Status column header; Cancelled hidden by default. "All <Status>" cross-date view + infinite scroll —
/todaygainsstatus + before_at/before_idkeyset pagination. Sticky column header — thead moves out of the scroll region. "All <Status>" heading kept when clearing till from a status view. Customer name on a historic transaction opens Edit Customer, not re-attach.kbDatePicker.restoreLabelpreserves date/range across open + close. Owing amount under Total; red R flag as pill-corner badge for returns. Hover cards — monochrome, unified, cross-referenced (/transactiongainsrelated_refunds+refund_ofso an invoice card lists its refunds and a refund card lists its invoice). Till column resizer un-bundled (v0.7.239). -
Return / Refund maturation (v0.7.225 → v0.7.232). Return button drops off when nothing's returnable; red R flag + stacking return hover card from
/sale-returns(staff + per-refund lines +fully_returned). StandardkbStepperon qty; refund estimate includes tax. Return modal keeps the invoice up behind it. Refund returns the actual tax paid (effTax = total − subtotal + discount), proportions to the header not the line prices (imported sales don't reconcile at line level), and for imports with no tax rows infers GST 5% + PST remainder. Return hover card shows both invoice and refund side. -
Layaway / Special-Order gating (v0.7.192, .203, .208). On a walk-in, Layaway / Special Order / Estimate open the attach-customer popup with Keep walk-in hidden;
pendingActionauto-fires the action once a customer's picked or created./depositand/estimateserver-guard missing customer with 400. Paid-off deposit hides Charge + Layaway (v0.7.208). -
Purchases Bucket owner display (v0.7.191). Every Bucket row now shows "Awaiting order · for Stock" (neutral) or "for <Customer>" (blue).
/bucketreturnscustomer_id+special_order_transaction_id+customers.display_name. Vocabulary: Bucket = "Awaiting order", PO cut = "Ordered". -
New Kernel primitives.
core/stepper.ts— one shared−/+control mounted on Sales cart, Purchases Bucket, new-PO staging, Return panel qty (BIKE.L1-0013).core/customeredit.ts— shared Edit-customer control (v0.7.185).core/router.tsgainedkbGoto(tab)cross-module opener (v0.7.120).core/formats.tsdata-mask="money"primitive (v0.7.210) —type=text inputmode=decimal, shows5,999.99on blur, strips commas on focus so no read-side ever undercounts. Phone-mask primitive alongside (BIKE.L1-0002). -
Till chassis unification (v0.7.108 → v0.7.189, ~50 patches). The substrate on which every cluster above lands: 3-zone sales-3zone split (v0.7.128); transaction detail on the Till chassis — opening a historic transaction swaps the same slot into a detail panel, not a modal (v0.7.130, BIKE.L2-0208); Customer-Till look emulation (v0.7.113); search-bar as sole entry (v0.7.142); phase-aware search (v0.7.145) — the box's behaviour shifts by Till phase; autopark on tab-away / 90s idle (v0.7.152 / .157 / .164, BIKE.L2-0220); Cancel/Park doctrine (v0.7.146 / .149 / .151); Cancel + void revert ledger to Today (v0.7.186 / .187); editable detail lines for non-finalised transactions (v0.7.181); negative-qty till → auto-refund (v0.7.139, BIKE.L2-0215); live multi-user poll (v0.7.140, BIKE.L1-0017); tab-return in-till refresh (v0.7.150, BIKE.L2-0216); bulk items as real SKUs via
is_bulk(v0.7.136 + migration 039, BIKE.L2-0214); "Awaiting order" ledger status (v0.7.132); Till header default flipped from "Customer" to "Walk-in" (v0.7.189); one-Till-layer + customer-history ledger (v0.7.164); tab-accent color-key on the Till header primary (v0.7.162). -
New migrations.
035_shop_config.sql(BIKE.L1-0012, v0.7.108).036_layaway_backbone.sql(BIKE.L1-0014, BIKE.L2-0209, BIKE.L1-0015, v0.7.115).037_special_order_backbone.sql(BIKE.L2-0210 / 0211, v0.7.116).038_aim_full_fidelity.sql.039_variant_bulk_flag.sql(BIKE.L2-0214, v0.7.136).040_audit_events.sql(v0.7.182). -
Small QoL. Customer card Account balance is signed money —
-$Xowing /$Xcredit (v0.7.213)..cd-drow .nmunified across Invoiced + Awaiting-Order detail views (v0.7.207). -
v0.7.107 — Color-coded Sales ledger status pills (BIKE.L2-0163). The flat
.sc-chipstatus cell in the ledger becomes a rounded status→color pill from a 9-pastel palette copied verbatim from the prior implementation. Invoiced=mint, Cancelled=red, Layaway=cyan, Refund=pink, Estimate=purple, Special order=peach, Parked=sky-blue; auto-parked rows carry an invertedAUTOsub-badge. Unknown statuses fall back to mint, never an unstyled chip..sc-chipstill holds the cart / detail / inventory / purchases uses; sorting (L2-0205) + the row hover-preview (L2-0204) untouched. -
v0.7.106 — Click-the-tax-line "Adjust tax" modal (BIKE.L2-0067). The
GSTandPSTrows in the till totals are now clickable — opens a 3-mode Adjust Tax modal at prior-implementation parity: Status Card (PST-exempt via card number), Manual override (enter cents on either tax, reason required, client + server validate), or Default (revert to the computed per-line total). Modes are mutually exclusive; an override badge lands in the totals block while active; Park / Clear / new-sale wipes it. Sits on top of the per-line categories — the override replaces the computed GST / PST total. -
v0.7.105 — AIM employees mirrored into
staff; Tom relocates to id 999 (auth resolves by role). New template migration 034:staff.external_aim_id+start_date+username(+ index). Client data:data/staff_roster_swicked.sqlmirrors the 9 AIM employees (id 1–9: Jordan Fair, James Durand (owner), Rob Pengelly, Les Jank, Chenoa Campbell, Liam Dyck, Khoen Montgomery, Robbie van Boot, Jadyn Steinhauer) into kvick-bike at the SAME ids the imported transactions already reference — no FK rewrite. Tom's bootstrap admin relocates from id 1 → 999 so Jordan Fair (12,457 imported sales) owns id 1 cleanly;core/auth.tsnow resolves the sys-admin bootstrap by role, not by id=1. -
v0.7.104 — Sales ledger row hover-preview card (BIKE.L2-0204). New Kernel control
src/core/hovercard.ts— a shared row hover-preview shell with per-row arm delay, fetch-or-local data hook, viewport-aware positioning at document.body level (escapes ledger cardoverflow:hidden), race guard on late fetches. Sales mounts it first: posted rows fetch/api/m/sales/transaction?id=at 400ms and cache; parked rows render at 350ms from local till data. Card mirrors the prior implementation exactly — voided note, SPECIAL ORDER band + PO status, ref+type header, Rung-by, Customer+acct, scrollable line rows, tax rows, Total. Service / PO / Inventory / Customers reuse the same shell later. -
v0.7.103 — Sales attach-customer popup: prior-implementation parity with inline create (BIKE.L2-0002). Rebuilt
#sc-cust-modalto duplicate the prior picker: live search-as-you-type over name / phone / email / account #, enriched result rows, subtitle hint, walk-in / cancel / swap paths. The main gap filled: an inline + New customer create panel (Name required + Phone / Email side-by-side + retail-default helper note) that POSTs/api/m/customers/createand attaches the new customer in one step. Empty / no-match results double as a "+ New customer" affordance; Enter picks the first match; name prefills the create form from the search text. -
v0.7.102 — Removed the customer spend graph from the Sales till (BIKE.L2-0156). The spend-over-time strip duplicated the identical graph on the Customers card, and re-fetched
/spend-historyon every attach. Deleted#sc-spend-strip+loadCustomerSpend()and its four call sites from Sales. GET/api/m/customers/spend-history+ the Customers-card graph (#cd-spend-graph, Monthly/Weekly/Annual toggle) untouched. -
v0.7.101 — Sales ledger: date in the Time column when not today + year grouping in customer-history mode. The first column shows the DATE for any row that isn't from today, time only for today's rows — a customer's multi-day history reads by day. Customer-history mode groups rows under year section headers, newest year first.
-
v0.7.100 — Sales ledger flips to the attached customer's sales history (BIKE.L2-0025, prior-implementation parity). Attaching a customer flips "Today's Sales" into customer-history mode — heading reads "<Name> — sales history", the ledger shows that customer's whole sales history (newest first) plus their own parked sales; the date picker is disabled while attached. Walk-in / Clear / sale completion restore Today's Sales. Resuming a parked/estimate sale deliberately does NOT flip. Backend: GET
/todaygainscustomer_id(wins over the date window, limit 200). -
v0.7.99 — Consistent ledger headings: title case, no ALL-CAPS. Sales + Service had inherited
.section-headeruppercase + letter-spacing, so "TODAY'S SALES" / "TODAY'S SERVICE ORDERS" shouted while Customers / Inventory read normally. Normalized all five headings to title case: Today's Sales · Today's Service Orders · Today's Visits · Stock Movements · Purchase Orders. -
v0.7.98 / v0.7.97 — Universal ledger date navigator on Sales, Service, Inventory (BIKE.L1-0010). Mounts the shared
kbDatePickeron every module's ledger heading (Customers already had it). Service (/ledger) and Inventory (/movements) endpoints take an optionaldate_from/date_towindow; both ledgers are work/history feeds so today (the default) shows the FULL board (current work-queue / whole movement history) and only a picked past day or range applies a filter. Sales'/todaytakes the same window; parked sales only show on the current day. -
v0.7.96 —
sales-3zonecolumn ratios match updated BIKE.L1-0001. The responsive spec differentiates the two upper breakpoints: tablet 40% / 60%, desktop even 50 / 50 (was single 60/40). Substrate Line canonical split is now 50/50. Verified at all three viewports. -
v0.7.95 — Sales till status flows inline after the title (prior-implementation parity). The
Till open · cashier <name> · N sales today · $Xstatus line was being pushed to the far right of the.page-header. Wrapped title + sub in a baseline flex group so status flows inline right after "Sales" on the left. -
v0.7.94 — Sales till header shows who is signed in (Sales-only). The Sales page-sub now reads "Till open · cashier <name> · N sales today · $X". GET
/api/m/sales/summaryreturnsctx.actor.name/role; the Sales client renders the cashier segment into#sales-sub. Sales-only; other module headers untouched (no clickable switch-cashier pill yet). -
v0.7.93 — Color-keyed accent line under every module header (Foundation). Each module's
.page-headergains a 2px bottom rule in that tab's accent colour (--accent, set per-tab on<body>by the shell + router). One Foundation CSS rule covers every module; Settings was the only tab not using.page-header, restructured into the standard.page-title/.page-subso it inherits. Also v0.7.93 — Family matches got their own visible group in Sales search (later reverted, superseded by v0.7.92 rerank+cap fix). -
v0.7.92 — Sales search actually surfaces family-member matches. Migration 031 already added
family_namestosearch_customers_fts; the trim was hiding legitimate matches. Two fixes:rerank()gives family-name prefix matches a+300tier (real signal, still below the account's own name), and Sales' limit went 8 → 20 so a legitimate 14th-place match isn't silently cut off. Family match rows showFamily: alex, owen, aaron, …so staff see why she matched. -
v0.7.87 → v0.7.91 — Focus-stewardship doctrine across all five module tabs (BIKE.L2-0195/0196). New Kernel control
src/core/focus-steward.ts— a MutationObserver-driven registry that reclaims focus for the active module's steward input after a modal closes, a print dismisses, focus blurs to nothing, or 5 seconds of keyboard idle. Capture-phase scanner-burst interception (≥5 chars, ≤50ms between keys, ending in Enter) redirects into the steward regardless of where focus drifted. Adopted by Sales (v0.7.87#sales-search), Service (v0.7.88#sv-search; also completes the port with the "don't yank from another visible input" guard + 5s idle timer), Customers (v0.7.89#customers-search), Purchases (v0.7.90#pur-search), Inventory (v0.7.91#inv-search). Sales also picks up Enter-opens-first-result / Escape-clears-and-closes / multi-add flash-green-keep-open / cash-tender auto-select / Enter-confirms / Escape-cancels / $5/$10/$20/Exact quick-tender presets (BIKE.L2-0197–0201). New Bible page: Focus stewardship. -
v0.7.86 — Special orders auto-post to the PO Bucket; ledger tracks it live (James S13). A Sales special-order deposit auto-posts a line into the Purchases Cart the moment it's taken; migration 033 links Sales transaction → Bucket line → PO line so the Sales ledger row can render live status: Special order → Ordered → Partially received → Ready for pickup.
-
v0.7.84 — Deposit / balance owing in the live till footer (James S19). The tender modal used to show it, but not the cart footer; now the cart footer surfaces deposit applied + balance owing while the operator is still stacking lines.
-
v0.7.83 — Cancel & refund a layaway / special-order from the ledger (James S11). Row action on a
deposit_layaway/deposit_special_ordertransaction. Refunds the paid amount + releases the reserved stock (special orders) + optional restock (layaway). -
v0.7.82 — Reverse a payment entered with the wrong amount or method (James S9). From transaction detail: reverse a specific
transaction_paymentsrow (creates a mirror-signed entry + audit) so the next payment lands with the corrected method / amount. -
v0.7.81 — Accessories bundled with a bike drop PST; standalone both taxes (James S5). Migration 032 seeds the
is_context_dependent+context_ruleontax_categories; SalescomputePerLineTaxesreads the context at cart time (BC PST Bulletin 204). Ported verbatim from the prior implementation. -
v0.7.80 — Family members are searchable; restocking fee can be a percent (James). Migration 031 adds
family_namestosearch_customers_ftswith triggers on both sides. Restocking fee gains a percent-of-return option next to the fixed-dollar option. -
v0.7.79 — Merge Return and Exchange into one Return, pick destination at completion (James S11). The Return panel now runs the same flow regardless of destination; at Complete, the operator picks refund method (cash / card / store credit / on-account). Removes the "did I click Return or Exchange" decision at the start.
-
v0.7.78 — Resume an open estimate / layaway into the till for editing (James S18). Row click on an open estimate or layaway loads its lines back into an empty cart; edits ride the same estimate / deposit save path (not a fresh sale).
-
v0.7.77 — Layaway / special-order to sale, using deposit credit (James S15). Pickup / completion converts the deposit transaction to a
sale, applies the deposit as a payment, and collects only the balance. -
v0.7.76 — Estimate-to-sale: convert in place with a real charge (James S14 + S16). The Convert button on an estimate opens the tender modal with the estimate's lines; posting rings the sale + closes the estimate in one operation.
-
v0.7.75 — Remove Void as a one-click action on completed invoices (James S6). Void needs a reason +
sales.voidgate; the ledger row's one-click was too easy to hit accidentally. Void now lives inside the transaction detail modal. -
v0.7.74 — Sales ledger real status labels (James S13).
Special order → Ordered → Partially received → Ready for pickupetc., driven by the live substrate rather than a static per-type string. Feeds off migration 033. -
v0.7.72 / v0.7.73 — Tax inheritance chain (James S3) + perm-gate regression fix. Per-line tax category falls back through variant override → product default → category default → tax_categories default so the operator never sees a line without a tax category resolved.
-
v0.7.71 — Sales: clear search bar on any selection (James S2). Attaching a customer, adding a line, or picking a bulk-item clears the search box so the operator can immediately type the next thing.
-
v0.7.70 — Purchases: create a SKU from the Cart (Omega #49 fix, BIKE.L2-0049). The Cart's Add-a-product search gains a + New SKU button that opens the create-product modal; the new SKU is then searchable + addable to the Cart. Closes the same gap the PO-line editor path already had.
-
v0.7.69 — Products split into two modules: Inventory + Purchases. The former two-face module becomes two peers:
src/modules/inventory/(SKU search → variant detail → Product Card editor → movement ledger) andsrc/modules/purchases/(PO ledger + Purchase Cart + Vendors + PO lifecycle). The movement engine is extracted tocore/movements.tsso Sales and Service can decrement stock through the same code path. -
v0.7.68 — Inventory: remove orphaned section-edit chain (Product Card supersedes).
-
v0.7.67 — Products Face A (Inventory) rebuilt to prior-implementation Inventory-tab parity.
-
v0.7.66 — Products Face B (Purchasing) rebuilt to prior-implementation POs-tab parity.
-
v0.7.65 — Sales: split-tender (BIKE.L2-0058). A sale can be paid with several tender rows in one transaction — cash + card, deposit + cash, etc. — with per-row change handling.
-
v0.7.64 — Foundation: field & format primitives —
core/formats.ts(kbFmt) for money-cents / ISO dates / percent / phone formatting, used across every module's read + edit views. -
v0.7.63 — Clear button on Sales + Service tills. Empty the active cart / work-order with
kbConfirmbefore losing state. -
v0.7.62 — Service module (BIKE.L2 service tickets). Workshop work-order screen — search + today's-service-orders ledger grouped by status; drop-off ticket → labour (
service_menu) + parts (variants) → status lifecycle (Start Work / Wait for Parts / Stop·Call / Ready / Notify) → pickup invoices asservice_invoicesale. Migration 030. See the Service module page. -
v0.7.61 — Sales: customer spend strip in the till (BIKE.L2-0156). When a customer is attached to a sale, a compact monthly-spend bar strip + this-year total / YoY% renders below the till header (reusing
/spend-history) — see the spend while ringing the sale, where the Omega looks. The full chart + toggle stay on the Customers card. -
v0.7.60 — Omega fixes. Estimate Convert-to-sale button (BIKE.L2-0071; was read-only). Reorder dashboard always reachable for reorder-capable operators (BIKE.L2-0089; was hidden at 0 low-stock). Library reconciliations: un-marked 0141 (segments never built), corrected 0156 location (Customers tab, not Sales till), 0095 / 0140 narratives (click-to-open modals, the doctrine).
-
v0.7.59 — Products: Print tag (BIKE.L2-0047). Stock Card footer Print tag → preview (product name, price, hand-rolled CODE39 barcode of the SKU, SKU text) → Print window. Single loaded SKU; batch-from-ledger + templates reconciled to a follow-up.
-
v0.7.58 — Customers: spend-over-time graph (BIKE.L2-0156, Sales-unblocked).
/spend-historyreturns per-period total posted-sale spend + fiscal-YTD compare; Spend section renders a bar chart (Monthly / Weekly / Annual toggle, YTD-bordered bars, per-bar tooltip) replacing the placeholder. Category stacking deferred. -
v0.7.57 — Products: + New SKU from the PO line editor (BIKE.L2-0049). Add-a-product search in the PO line popup gains a + New SKU button that opens the create-product modal; the new SKU is then searchable + addable to the PO.
-
v0.7.56 — Products: 7-view analytical ledger (BIKE.L2-0149 + 0145 top sellers).
/movements?view=Xserves movement-shaped views (today/sold_today/last_received) and variant-shaped views (top_sellers/bottom_sellers/out_of_stock/old_stock); the Ledger Pill (Tier 2) selects the view, the ledger<thead>+ rows adapt per shape, variant rows click through to the Stock Card. Replaces the stopgap movement-type filter. -
v0.7.55 — Products: Recent purchasers + click-to-sale (BIKE.L2-0148, Sales-unblocked).
/variantnow returnsrecent_purchasers(who bought this SKU, newest first w/ sale_id); the stock-card section renders the rows; a click opens a read-only sale viewer. -
v0.7.54 — Sales: enforce
discount.apply(was a defined-but-unwired orphan perm)./create403s a discount when the operator lacksdiscount.apply; client hides the discount chips + cart-discount Apply and makes the line price read-only. Completes the Sales permission surface (everysales.*+discount.*gate now enforced). -
v0.7.53 — Re-baseline from v0.8.6: revisions one decimal finer. Keep the 0.7 minor + patch-per-completion (incl. new modules), monolith-0.6.470-style (low minor / high patch); minor + major reserved, 1.0.0 = parity + cutover. The v0.8.0–v0.8.6 Sales bumps map onto the patch line continuing v0.7.46.
The v0.8.x Sales bumps below were folded into the v0.7.x patch line by v0.7.53 — kept as record:
-
v0.8.6 → folded — Sales: transaction-level discount (BIKE.L2-0060) + per-line tax override (BIKE.L2-0068). Cart-target customer discount as a totals chip (Apply / remove), reduces subtotal and GST/PST base proportionally; per-line tax-category picker via
/tax-categories;taxApplies()usesapplies_gst/applies_pst. Sales 21/28 spec-complete. -
v0.8.5 → folded — Sales: store-credit exchange + return panel (BIKE.L2-0009, full HUB spec). Return / Exchange panel — per-line qty + condition (New / Used restock; Damaged does not), restocking fee, header reason. Exchange issues store credit to the original customer (migration 029) and opens a fresh sale with that customer attached; the pay modal pre-applies store credit and collects only the difference.
-
v0.8.4 → folded — Sales: line-discount rule engine (BIKE.L2-0059, full HUB spec). Customer-assigned model:
/discount-suggestresolves the attached customer sales discount + action; per-line chips (percent / dollar / set_price); manager-threshold gate viadiscount.override_manager(clientkbCan+ server-authoritative in/create); applied discounts recorded totransaction_discountswithtransaction_line_id+discount_id(migration 028) andtotal_discount_centson the header. -
v0.8.3 → folded — Sales: the 4 remaining buildable L2 — void (BIKE.L2-0063: reverse stock + ledger, mark voided,
sales.void), line discount (BIKE.L2-0059: editable cart unit price +original_unit_pricerecorded, discount chip), exchange (BIKE.L2-0009: return remaining lines + start fresh sale), print receipt (BIKE.L2-0069: print window from the detail modal). 19/27 Sales L2. -
v0.8.2 → folded — Sales: allow overselling on a sale (movement engine
allowNegativeflag) so a POS sale of a zero / backorder SKU posts, like the prior implementation./create+/from-estimateset it. -
v0.8.1 → folded — Sales module (slice 2): the transactional write path.
/create(post sale + lines + multi-tender + GST/PST + status-card exemption + stock decrement + TRX-YYYY-NNNNNN),/refund(per-line restock),/transactiondetail,/estimate(create / void / resume / from-estimate),/deposit(layaway + special-order + add-payment + cancel),/parkedCRUD. Client: tender flow (7 methods + cash change), estimate, layaway / special-order deposit, park / resume, ledger → receipt → return. -
v0.8.0 → folded — Sales module (slice 1): scaffold + register + schema (migration 027) + read surface. sales-3zone
.till, today's-sales ledger, unified search (product / customer / invoice), client-side cart with live GST/PST preview, bulk-items palette, attach-customer modal. See the Sales module page. -
v0.7.46 — Products / Purchasing: transcribe the prior system's POs-tab Bucket into Face B (Pill UI kept) — search-to-add-to-cart, inline bucket line controls (qty stepper / cost / vendor), bucket cost-override (migration 026).
-
v0.7.45 — HOTFIX. The Products tab had been dead since v0.7.40: an escaped apostrophe in the client template emitted a bare quote → SyntaxError killed the whole module script. Removed it ("Cost cannot be negative").
-
v0.7.44 — Products / Purchasing: receiving depth — cost-override + cost ladder on receive,
supplier_productscost stamp, default-0 / over-receipt, discrepancy roll-up (BIKE.L2-0044 / 0083). -
v0.7.43 — Products / Purchasing: Clear bucket — empty all staged lines across vendors (BIKE.L2-0050).
-
v0.7.42 — Products / Purchasing: New PO from scratch (vendor picker → empty PO → line editor) + PO ledger column sort + vendor filter (BIKE.L2 New-PO / 0041 / 0048).
-
v0.7.41 — Products / Purchasing: rich vendor record — account #, address, website + B2B portal, logo, drop-ship in the supplier popup (BIKE.L2-0051).
-
v0.7.40 — Products / Purchasing: editable Order Card — edit pending PO lines (qty / unit-cost), add + remove products, server-side total recompute (BIKE.L2-0042).
-
v0.7.39 — Products: align PO status lifecycle to the prior system (
pending_order/ordered; migration 025). -
v0.7.38 — Fix:
.module-panedisplay:contentsto restore the module's flex layout under the keep-mounted router. -
v0.7.37 — Foundation: keep-mounted SPA tab navigation (BIKE.L1-0008). Modules mount once and persist across tab switches. New
src/core/router.ts. See Keep-mounted SPA navigation. -
v0.7.36 — Products: Vendor RMA operator UI (BIKE.L2-0091 complete).
-
v0.7.35 — Products: Vendor RMA engine — tables + lifecycle backend (BIKE.L2-0091; migration 024).
-
v0.7.34 — Products: wire the reorder dashboard to
inventory.reorder. -
v0.7.33 — Products: reorder dashboard — low-stock surfacing + PO from thresholds (BIKE.L2-0088 / 0089).
-
v0.7.32 — Products: gate the Inventory / Purchasing switch on
purchasing.view. -
v0.7.31 — Settings: consolidate Products permissions into one matrix area.
-
v0.7.30 — Products: bulk price update (BIKE.L2-0087) — multi-select variants and apply a percentage or fixed change in one popup; one audit row per variant.
-
v0.7.29 — Products: supplier editing from the Order Card.
-
v0.7.28 — Products: clone a product (BIKE.L2-0045).
-
v0.7.27 — Settings: gate the staff-screens override endpoints (authz fix).
-
v0.7.22–v0.7.26 — Products: create-from-scratch flow, conform buttons / pills / modals to the Customers norm and the shared
.kb-modalfamily. -
v0.7.21 — App-wide timezone fix: render timestamps in the shop's tz.
-
v0.7.16–v0.7.20 — Purchasing (Face B) read surface → PO Bucket (add-to-PO grouped by supplier) → build & issue a PO → receive (closes the Bucket → PO → on-hand loop).
-
v0.7.12–v0.7.15 — Inventory-movement engine + Adjust (BIKE.L2-0046) + Cycle count (BIKE.L2-0084) + Write-off (BIKE.L2-0085); filtered ledger views (movement-type chips).
-
v0.7.10–v0.7.11 — Products section-edit popups (process 0147): Pricing & reorder + Tax + Product + Variant.
-
v0.7.4 — Imported the real catalog from the prior system.
-
v0.7.1–v0.7.3 — Products foundation: data model (catalog + inventory ledger + purchasing; migrations 018–020) + two-face module scaffold + the
sales-3zone60/40 layout that every tab inherits. -
v0.7.0 — Re-baseline. The in-app Bible link was removed at the same time (the
BIBLE_URLwas a placeholder; the Bible stays a separate deployable atkvick-bike-bible.pages.dev).
See the Products module page for the worked architecture.
History — v1.x (kept as record; folded into v0.7.0 baseline)
v1.4.2 — Customers Till header is name-first
The customer Till header leads with name + phone/email (PII-gated), dropping the redundant "Customer" label and moving the account number into the Identity section. Empty state reads "No customer attached".
v1.4.1 — Family members refinement (refines BIKE.L2-0157)
Each in-card family-member row is click-to-edit — the popup pre-fills, Save updates via PUT /family-members, Remove moved into the popup. The Relationship field was dropped (the column stays, written NULL); the member's phone now shows inline in the slot the relationship used to hold (Name · Phone · minor).
v1.4.0 — Year-over-year header (BIKE.L2-0158)
The Customers tab now leads with a churn-vs-growth pulse: total active · ▲ spending more · ▼ spending less. New customers_yoy_summary precompute (migration 017) refreshed by a nightly cron (03:17 Vancouver). See Customers module for the full surface.
v1.3.1 — Spend section placeholder (BIKE.L2-0156, partial)
Added a Spend slot in the customer card; the real stacked-bar graph + year-over-year numerals are deferred — they need per-line product categories from Sell / Fix's domain.
v1.3.0 — Editable Permissions Inspector (BIKE.L2-0155)
The role × permission matrix is now fully editable for sys.permissions.edit holders. Custom roles + per-staff overrides (override role + per-permission grant / revoke / inherit) + Reset Owner permissions for sys-admin recovery. Migration 016 added staff.override_role_id and granted Owner sys.permissions.edit. See Settings module → Editable Permissions Inspector.
v1.2.1 — Phone frame scroll fix (BIKE.L1-0001)
The sales-3zone phone layout was viewport-locked. Released the app-shell lock on phone — the window scrolls, the .till is content-height, each pane is a sized scroll box.
v1.2.0 — Foundation responsive layer (BIKE.L1-0001)
The chassis-level responsive primitives that every module inherits.
v1.1.1 — 2026-06-25
- Removed the dead
.helm-editableinline-edit CSS (thecontenteditablechassis was retired pre-v0.6.385; no element used the leftover stylesheet) and corrected the stale "inline-editable card" comment. The customer card has no inline-edit affordance — editing is modal-first per the no-inline-editing doctrine. Reconciles the build with the BIKE.L2-0023 process narrative (modal-first edit;pricing_tierdormant; start-fresh, no A/B/C→Trade migration).
v1.1.0 — 2026-06-25
Customers
- Account linking renamed "Family" → "Linked" — the section links separate customer accounts (personal ↔ business, partners who each keep their own account), not family members. API
/family→/links; auditcustomer.account.link/unlink; thecustomer_family_linkstable is kept as an internal legacy name. (Edits process BIKE.L2-0153.) - New "Family" section — in-card family members: a primary contact plus spouse / children, each with their own name, relationship, and phone (+ minor flag). New
customer_family_memberstable;/family-membersCRUD (migration 015). A different level from account links — members of one account, not other accounts. - Card layout is now Contact → Linked → Family → Marketing → …
v1.0.0 — 2026-06-25
Baseline of the clean-room rebuild (started 2026-06-21), replacing the prior ~90k-line implementation. Everything below ships in 1.0.0; semantic versioning begins here.
Core (L1)
- Tiny kernel: D1 wrapper, DB-session auth + actor, hash-chained audit log, module contract + registry, granular permission seam (
can()over a data catalog). - Cloudflare Workers + D1, strict TypeScript, no framework; HTML / CSS / JS inline in TS template strings.
- Shared Core controls injected once in the shell:
kbDatePicker,kbSortable,kbPager,kbPrint, and the FTS5 search engine (core/search). cache-control: no-storeon shell + API responses (no stale bundles after a deploy).
Customers (L2)
- Full customer card at parity with the prior build: 3-pane work area (search / swappable ledger / read-only Till), modal-first editing per the no-inline norm.
- FTS5 search (BM25 + per-token prefix + synonyms); keyset-paginated interactions ledger (no silent caps).
- Sections: Contact / Linked / Family (bidirectional links + minor flag) / Marketing / Notes / Equipment / Loyalty / Identity / account credit-debit / Merge / Print.
- Real dataset imported from the prior build: 3,099 customers · 2,541 bikes · 20,360 transactions.
Settings (L2)
- Staff & permissions: Employee modal (role, visible tabs, PIN, idle-lockout) + read-only Permissions inspector.
- Granular permissions mirrored verbatim — catalog + roles + role / staff grants.
Discounts (L2)
- Discount builder in Settings: customer discounts that apply to Sales and/or Service, authored in a 3-step builder and assigned per-customer on the customer card (replaces the old A/B/C pricing tier).
- Per-department assignment, department-validated; the Sales / Service tills (when built) read the customer's assigned discount.
See also
CHANGELOG.mdin the kvick-bike repo — the canonical source; this Bible page mirrors it- Three strata — the architectural shape every release operates within
- Customers module — the first module's Bible page
- Settings module — Staff, permissions, discount builder