diff --git a/docs/superpowers/specs/2026-07-16-hq-store-split-design.md b/docs/superpowers/specs/2026-07-16-hq-store-split-design.md new file mode 100644 index 0000000..c93b434 --- /dev/null +++ b/docs/superpowers/specs/2026-07-16-hq-store-split-design.md @@ -0,0 +1,194 @@ +# Split SiMS into two independent projects: HQ and Store + +**Date:** 2026-07-16 +**Status:** Approved, pending implementation plan + +## Why + +`C:/SiMS/Store Software` currently holds two products that are not one product. + +**HQ** (`apps/hq`, `apps/hq-web`) is internal: our client book, their projects, +proforma-invoice billing, call tracking, AMC contracts, AWS cost recovery, +payments and reminders. It is the commercial record of the business. + +**Store** (`apps/pos`, `apps/store-server`, `apps/backoffice`) is the product we +sell: GST retail management deployed on client premises. + +They are independent projects and should be developed, deployed and released as +such: + +- **Different audiences.** HQ is internal-only; Store ships to ~300 clients. +- **Different databases.** Two SQLite schemas with no conceptual overlap. HQ: + `client`, `module`, `document`, `interaction`, `amc_contract`, `aws_usage`, + `payment`, `reminder`, `staff_user`. Store: `item`, `batch`, `bill`, + `purchase`, `stock_movement`, `party`, `counter`, `store`, `tenant`. Four + names collide (`audit_log`, `doc_series`, `setting`, `tax_class`) but they are + different definitions in different DBs — shared in appearance only. +- **Different cadences.** Of 75 commits, 60 touch HQ and 2 touch Store. The repo + is already HQ wearing a Store name. +- **Confidentiality.** A single tree means every Store branch, contractor + handout, or repo copy carries our client book and revenue data in its history. + +## Decisions + +| # | Decision | Choice | +|---|---|---| +| 1 | Split shape | Two separate git repos | +| 2 | Shared code | Fork into each repo, trimmed to what each uses | +| 3 | History | HQ inherits the existing 60 commits; Store starts fresh | +| 4 | Docs | Trim each side to only what that repo needs | +| 5 | Repo name | `C:/SiMS/hq` (keep — matches docs/14, DEPLOY-HQ.md, `@sims/hq`, 60 commits) | +| 6 | Project tracking | Out of scope — split first, build after | + +Rejected: a third shared repo and a private npm registry. Both add versioning +overhead to solve a divergence risk the evidence says does not exist (see below). + +## Target layout + +``` +C:/SiMS/ +├── hq/ ← current repo, 60 commits preserved +│ ├── apps/{hq, hq-web} +│ ├── packages/{domain, auth, billing-engine, ui} ← trimmed, names kept +│ ├── docs/ ← HQ-only, renumbered +│ ├── DEPLOY-HQ.md +│ └── README.md, STATUS.md ← STATUS.md is new +└── store/ ← fresh git init + ├── apps/{pos, store-server, backoffice} + ├── packages/{domain, billing-engine, auth, ui, config, scanning, search-core} + ├── docs/ ← the remaining 15 docs + └── README.md, STATUS.md, BUILDING.md +``` + +Each repo gets its own `package.json` (`sims-hq` / `sims-store`), `tsconfig`, +`vitest.config`, `.gitignore`, and `CLAUDE.md`. Neither references the other at +any point — no workspace links, no relative paths across roots, no doc citing a +file it does not contain. + +## Shared code: fork, trimmed + +HQ's dependency on shared packages is shallow and fully verified — utilities and +a design system, no shared schema or business logic. + +**HQ keeps the package names** (`@sims/domain`, `@sims/auth`, +`@sims/billing-engine`, `@sims/ui`) and removes files underneath. Import +specifiers are unchanged, so **no import statement in HQ's app source changes** — +not one of `apps/hq`'s 75 files or `apps/hq-web`'s 14. What changes is narrow and +enumerable: the four `index.ts` barrels (drop the removed `export *` lines), and +`apps/hq-web`'s `package.json` + `vite.config.ts` (drop the dead `@sims/printing` +dependency and alias). + +| Package | HQ keeps | HQ drops | +|---|---|---| +| `domain` | `ids`, `money`, `business-day`, `doc-series`, `gstin`, `documents` | `tenancy`, `masters`, `batch`, `outbox` | +| `auth` | `pin` | `lockout`, `permissions`, `session` | +| `billing-engine` | `compute`, `tax` | `returns`, `gst-returns` (588 lines) | +| `ui` | all 5 files | — | +| `printing` | — | whole package | + +Store's `packages/` is untouched. + +### Closure verification (done, 2026-07-16) + +Every trim is a clean cut — verified by reading the imports, not by assumption: + +- `compute.ts` imports `./tax` and four **types** from `@sims/domain` + (`BillLine`, `BillTotals`, `Paise`, `IsoDate`) — all in files HQ keeps. +- `tax.ts` imports only `IsoDate`. +- `documents.ts` imports only from `money` and `business-day`. +- `pin.ts` imports only `node:crypto` — no `@sims` deps. +- `ui` has no `@sims` deps at all (`react` + its own `theme`). +- Nothing HQ retains imports `tenancy`, `masters`, `batch`, or `outbox`. The + reverse edges (`batch → business-day`, `masters → money`) are one-way and + drop cleanly. +- `@sims/printing` is declared in `apps/hq-web/package.json` and aliased in its + `vite.config.ts`, but **imported by no source file** — a dead dependency. + +### Why forking is safe + +The divergence risk for forked code is GST computation. It does not apply: +**tax rates are dated DB rows, not code.** `tax_class` rows carry +`effective_from`/`effective_to` and resolve by the bill's business date, with +the Sept-2025 GST 2.0 slab change cited in `tax.ts` as the precedent. HQ reads +its own `tax_class` table; Store reads its own. A rate change is a row insert +per DB, never a code edit. + +What remains in `compute.ts` is stable arithmetic — CGST/SGST/IGST split, +rounding. It has no reason to change often, and the two products are entitled to +diverge: HQ invoices software modules, Store bills retail goods. + +## History + +The current repo becomes `C:/SiMS/hq`: all 60 HQ commits preserved, then one +commit removing Store paths. No `filter-repo` surgery. + +`C:/SiMS/store` gets `git init` and one clean initial commit. It loses only 2 +commits (one titled `wip`) and gains a guarantee: **zero HQ bytes in its history, +permanently.** This is the confidentiality boundary, and it is only real if the +Store history starts empty. + +## Docs + +Trimmed per repo — each doc cut to what that side actually needs. + +**HQ** (renumbered `00`–`04`): + +| New | From | Treatment | +|---|---|---| +| `00-HQ-CONSOLE-SPEC.md` | `14-SPEC-HQ-CONSOLE.md` | As-is | +| `01-ADMIN-SUPPORT-CONSOLE.md` | `11-ADMIN-SUPPORT-CONSOLE.md` | As-is | +| `02-PROJECT-RULES.md` | `16-PROJECT-RULES.md` | Trimmed: keep money, config-over-code, document rules; drop counter/POS rules | +| `03-DECISIONS.md` | `06-DECISIONS.md` | Extract D15 + HQ-only calls | +| `04-DB-AND-CONFIG.md` | `07-DB-AND-CONFIG.md` | Trimmed: D12 portable-repo pattern, dated tax-class config | + +Plus `DEPLOY-HQ.md` as-is, a rewritten `README.md`, and a **new `STATUS.md`**. + +**Store**: the remaining 15 docs, with D15/HQ references pruned from +`06-DECISIONS.md` and the HQ rows removed from `README.md`'s doc table. + +### HQ has no STATUS.md + +The largest app in the repo (75 files, at go-live per `DEPLOY-HQ.md`) has no +build-state doc. `STATUS.md` covers only the Store product. HQ's current state +had to be reconstructed from source and `git log`. Write one during the split, +covering the built surface: clients, modules and price book, documents +(QT/PI/INV/CN) with templates/PDF/share links, interactions, AMC, AWS usage, +payments, recurring plans, reminders with Gmail send and bounce handling, +reports, dashboard, audit, APEX import, scheduler. + +## Verification + +The split is done when, for each repo independently: + +1. `node_modules` removed and `npm install` run fresh +2. `npm run typecheck` passes +3. `npm test` passes +4. No path resolves outside its own root + +Typecheck is the real proof of the fork closure — a missed dependency fails +immediately and loudly. + +Additionally, confirm the confidentiality boundary holds rather than assuming it: +searching `C:/SiMS/store/.git` for client, AMC, and AWS-cost terms must return +nothing. + +## Out of scope + +- **Project tracking.** Listed as a core HQ concept but not built — no `project` + table, no repo module, no UI. It is a feature to design and build, not code to + move. Deferred so the split stays mechanical: pure move-and-verify, with no new + feature to confuse a failure with. +- **Conversion reporting.** `client.status` (`lead`/`active`/`dormant`/`lost`) + exists but has no lead-to-won reporting on it. Wanted alongside the existing + conversation log (`interaction`), but after the split. +- **Oracle 12c adapter** (Store, pending Classic DDL) and the **46 wireframe + back-office screens** (Store) are unaffected by this work. + +## Risks + +| Risk | Mitigation | +|---|---| +| A trimmed file is needed by something unread | Typecheck on a fresh install in each repo catches it immediately | +| Store history retains HQ data | Fresh `git init`, verified by searching `.git` | +| Docs cite files that no longer exist in their repo | Grep each repo's docs for cross-references to removed paths | +| HQ's dead `@sims/printing` alias breaks its Vite build when dropped | Remove from both `package.json` and `vite.config.ts` together; build verifies |