docs(spec): design for splitting HQ and Store into independent projects

Two products share one repo: HQ (internal client book, proforma billing,
call tracking) and Store (the GST retail product shipped to ~300 clients).
Different DBs, different audiences, different cadences — 60 of 75 commits
touch HQ, 2 touch Store.

Design: two repos. HQ inherits the existing history; Store starts fresh so
its history carries zero HQ bytes. Shared packages fork into each side,
trimmed to verified usage — safe because GST rates are dated DB rows, not
code, so the forked arithmetic has no reason to diverge.

Project tracking and conversion reporting are deferred: they don't exist
yet, and mixing new features into a mechanical move hides failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 5 days ago
parent 89d92df672
commit 85adbb553a

@ -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 |
Loading…
Cancel
Save