docs: record the delivered D18 go-live cluster — 344 tests, gaps closed, spec stamped (Phase 10)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 5 days ago
parent f30532b7d1
commit 8064c49465

@ -17,7 +17,7 @@ dashboard, an append-only audit log, an APEX importer, and a scheduler.
| Part | Where | Notes | | Part | Where | Notes |
|---|---|---| |---|---|---|
| Backend | `apps/hq/src` | Express over `better-sqlite3`, DB at `data/hq.db` (WAL) | | Backend | `apps/hq/src` | Express over `better-sqlite3`, DB at `data/hq.db` (WAL) |
| Frontend | `apps/hq-web/src` | React + Vite; pages Dashboard / Pipeline / Clients / ClientDetail / Modules / Reports / NewDocument / DocumentView / Employees (owner-only) / DocumentTemplate (owner-only) | | Frontend | `apps/hq-web/src` | React + Vite; pages Dashboard (My Day) / Pipeline / Reminders / Clients / ClientDetail / Documents / NewDocument / DocumentView / Modules / Reports / Employees (owner-only) / Profile / APEX Import (owner-only) / Settings |
| Repos | `apps/hq/src/repos-*.ts` | one file per domain; plain functions over a `DB` handle | | Repos | `apps/hq/src/repos-*.ts` | one file per domain; plain functions over a `DB` handle |
| Schema | `apps/hq/src/db.ts` + `apps/hq/schema.sql` | `CREATE TABLE IF NOT EXISTS` + additive `migrate()` | | Schema | `apps/hq/src/db.ts` + `apps/hq/schema.sql` | `CREATE TABLE IF NOT EXISTS` + additive `migrate()` |
| Shared packages | `packages/*` | trimmed forks — see below | | Shared packages | `packages/*` | trimmed forks — see below |
@ -43,7 +43,7 @@ distilled to what applies to HQ. A change that breaks one does not land.
| Append-only audit | Every mutation calls `writeAudit(db, userId, action, entity, id, before, after)` in the **same transaction** as the change. The audit log is append-only — never update or delete rows. Previews and the public share route write nothing. | `audit.ts` | | Append-only audit | Every mutation calls `writeAudit(db, userId, action, entity, id, before, after)` in the **same transaction** as the change. The audit log is append-only — never update or delete rows. Previews and the public share route write nothing. | `audit.ts` |
| Client id is the future tenant id | `client.id` is a client-generated UUIDv7 that becomes `tenant_id` when the cloud tier stands up — same rows, promoted once. Never re-key clients. | `repos-clients.ts` | | Client id is the future tenant id | `client.id` is a client-generated UUIDv7 that becomes `tenant_id` when the cloud tier stands up — same rows, promoted once. Never re-key clients. | `repos-clients.ts` |
| No silent caps | Every list that can grow paginates or warns; never silently truncate. | reports, directory queries | | No silent caps | Every list that can grow paginates or warns; never silently truncate. | reports, directory queries |
| Strict TS, green before landing | Strict TypeScript everywhere; `npm run typecheck` and `npm test` both clean before anything lands. Money math is locked by tests — a rounding change without a test change does not land. | `tsc`, vitest (293 tests) | | Strict TS, green before landing | Strict TypeScript everywhere; `npm run typecheck` and `npm test` both clean before anything lands. Money math is locked by tests — a rounding change without a test change does not land. | `tsc`, vitest (344 tests) |
## Running it ## Running it
@ -63,7 +63,7 @@ cd apps/hq-web && npm run dev
- Env: `HQ_DATA_DIR` (DB location), `SIMS_PIN_PEPPER` (set once, never change), `HQ_SECRET_KEY` - Env: `HQ_DATA_DIR` (DB location), `SIMS_PIN_PEPPER` (set once, never change), `HQ_SECRET_KEY`
(before Gmail), `GOOGLE_*` (Gmail), `AWS_*` (Cost Explorer), `HQ_PORT` (default 5182). (before Gmail), `GOOGLE_*` (Gmail), `AWS_*` (Cost Explorer), `HQ_PORT` (default 5182).
- Health check: `GET /api/health``{ ok: true }`. - Health check: `GET /api/health``{ ok: true }`.
- Verified state: `npm install` + typecheck clean, 293 tests pass. Exercise real flows end - Verified state: `npm install` + typecheck clean, 344 tests pass. Exercise real flows end
to end before claiming done. to end before claiming done.
## Docs (kept in `docs/`) ## Docs (kept in `docs/`)

@ -34,7 +34,7 @@ document and conversation we've had with them.
## Build state (start here) ## Build state (start here)
Working codebase, not a plan. `npm install` + typecheck are clean and **293 tests pass**. Working codebase, not a plan. `npm install` + typecheck are clean and **344 tests pass**.
Current state and run-vs-pending detail live in **[STATUS.md](STATUS.md)**; the go-live Current state and run-vs-pending detail live in **[STATUS.md](STATUS.md)**; the go-live
runbook (server, HTTPS, backups, Gmail/AWS/import wiring, the Postgres switch) is in runbook (server, HTTPS, backups, Gmail/AWS/import wiring, the Postgres switch) is in
**[docs/DEPLOY-HQ.md](docs/DEPLOY-HQ.md)**. **[docs/DEPLOY-HQ.md](docs/DEPLOY-HQ.md)**.

@ -21,9 +21,9 @@ reminders driven by the dated `reminder_schedule` table.
| --- | --- | | --- | --- |
| `npm install` | clean | | `npm install` | clean |
| `npm run typecheck` (root + workspaces) | clean, no errors | | `npm run typecheck` (root + workspaces) | clean, no errors |
| `npm test` (`vitest run`) | **293 tests pass across 57 files** | | `npm test` (`vitest run`) | **344 tests pass across 66 files** |
The 293 figure is the live `vitest run` count measured 2026-07-17, after the The 344 figure is the live `vitest run` count measured 2026-07-17, after the
quote-to-close funnel slice and its review fixes. Test files live under quote-to-close funnel slice and its review fixes. Test files live under
`apps/hq/test/` (49 files, including `employees`, `employees-routes`, `apps/hq/test/` (49 files, including `employees`, `employees-routes`,
`client-owner`, `pipeline`, `quote-followup`, and `reminder-schedule`), `client-owner`, `pipeline`, `quote-followup`, and `reminder-schedule`),
@ -269,9 +269,17 @@ rasterizes for the PDF.
- **AWS cost pull is env-gated** — the scheduled and manual pulls run only when - **AWS cost pull is env-gated** — the scheduled and manual pulls run only when
`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` are configured; otherwise usage `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` are configured; otherwise usage
is owner-entered by hand. is owner-entered by hand.
- **APEX import is CLI-only** — no web UI for staging/commit.
- **Reminders live on the Dashboard** — there is no standalone reminders page,
and there is no separate documents-list page (documents are reached from the
client ledger and the dashboard).
- **A few tax/identity defaults are provisional** — the seeded company state code - **A few tax/identity defaults are provisional** — the seeded company state code
(`32`, Kerala) and several SAC codes carry "founder / CA to confirm" notes. (`32`, Kerala) and several SAC codes carry "founder / CA to confirm" notes.
_Closed by the D18 go-live cluster (2026-07-17): APEX import now has an owner-only
web UI (`/import`: stage → per-row problems → verify → confirm-gated commit);
standalone Reminders (`/reminders`) and Documents (`/documents`, honestly paginated)
pages exist; invoices carry **due dates** (terms default + override, stamped at
issue — the overdue chase says "was due on X, N days overdue"); self-service
profile + own-password change; sidebar collapses; the dashboard has a My Day mode
(staff always scoped to their book); clients carry support-access data (AnyDesk,
OS, district/sector filters, an encrypted DB password with audited reveal); the
module roster is fully editable both ways (one audited write path with Client 360);
"Convert & send" is one click from the register and the document view; and a
multi-module quotation prints one titled section per module._

@ -230,3 +230,30 @@ locked with it:
- **UI-only slice.** No backend/schema changes; `@sims/ui` stays generic (no router or - **UI-only slice.** No backend/schema changes; `@sims/ui` stays generic (no router or
icon deps — `lucide-react` lives in `apps/hq-web` only). Known deferred: server-side icon deps — `lucide-react` lives in `apps/hq-web` only). Known deferred: server-side
"outstanding ₹" figure for the Client 360 KPI row (client-side paise math is banned). "outstanding ₹" figure for the Client 360 KPI row (client-side paise math is banned).
## D18 — Go-live cluster: due dates, ops pages, shell & profile, client support data ✅ DELIVERED (2026-07-17)
Founder-approved slice (spec `superpowers/specs/2026-07-17-golive-cluster-design.md`), built
in ten phases the same day. The calls that matter going forward:
- **Invoice due dates = terms default + per-document override.** `billing.payment_terms_days`
(seeded 15) is the operational default; the *dated* fact is the `due_date` stamped on each
invoice at issue (covers manual issue, convert-and-send and recurring generation in one
place). Overdue reminders anchor `COALESCE(due_date, doc_date)`; the chase mail says
"was due on X and is now N day(s) overdue"; legacy invoices keep the old anchor.
- **Client support data (APEX parity):** `anydesk`/`os`/`district`/`sector` on the client;
district+sector filter the book. The DB password is AES-256-GCM at rest (`HQ_SECRET_KEY`,
loud refusal without it), NEVER in payloads (only `hasDbPassword`), write and reveal are
owner/manager-gated with one audit row per reveal.
- **Roster single-write-path:** the module→clients roster edits (kind/edition/renewal/
status, assign, unassign) reuse `updateClientModule`/`assignModule` — the same audited
functions Client 360 uses, so the two views cannot disagree. `ClientModulePatch` gained
kind/edition with allowedKinds validation.
- **Quotation sectioning:** QUOTATION prints one titled section per module (name +
what's-included + price; print page-break per module); INVOICE/PI/CN/RECEIPT keep the
billing grid. Rendered in the ONE `documentHtml`, preserving preview↔PDF fidelity.
- **Self-service profile:** `/me` routes are strictly self-scoped (role/email untouchable);
changing your own password requires the current one and kills every OTHER session.
- **My Day:** staff dashboards are always scoped to their own book server-side;
owner/manager toggle Mine/Everyone (`?mine=1`). Sidebar collapses to an icon rail.
- **APEX import UI:** owner-only `/import` wraps the existing stage/verify/commit —
commit stays locked while any staged row carries a problem.
Suite at 344 tests / 66 files at delivery.

@ -1,6 +1,6 @@
# HQ Console — Go-Live Cluster Spec (Due Dates, Import UI, Reminders/Documents Pages, Shell & Profile, Client Support Data) # HQ Console — Go-Live Cluster Spec (Due Dates, Import UI, Reminders/Documents Pages, Shell & Profile, Client Support Data)
> **STATUS: DRAFT — awaiting founder review.** Successor slice to the delivered D16 funnel > **STATUS: DELIVERED 2026-07-17.** Successor slice to the delivered D16 funnel
> (`2026-07-17-quote-to-close-funnel-design.md`). The Postgres switch (D15) is deliberately > (`2026-07-17-quote-to-close-funnel-design.md`). The Postgres switch (D15) is deliberately
> NOT in this slice — it is the dedicated task queued immediately after it, before the > NOT in this slice — it is the dedicated task queued immediately after it, before the
> real-data import commit. > real-data import commit.

Loading…
Cancel
Save