docs: bring README/STATUS/CLAUDE current (D20-D33)

Corrects the now-wrong at-rest-encryption claims (D31/D32 removed HQ_SECRET_KEY —
credentials are plaintext, login passwords stay scrypt-hashed), updates the test count
(389 -> 416), the schema table count (26 -> 31) + new tables, and adds the screens and
capabilities built since 2026-07-17: tickets, onboarding, module-defined fields, the
live SMS gateway balance pull, Module data directory, Portals, global search, MIS
cockpit, audit viewer, Renewals, Settings->Operations, and the backup script (D33).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 3 days ago
parent 62eb937012
commit b09f545489

@ -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` |
| 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 |
| 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 (389 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 (416 tests) |
## Running it
@ -61,12 +61,19 @@ cd apps/hq-web && npm run dev
- Owner login on first boot: `admin@tecnostac.com` + the one-time password printed in the
server log (change it immediately). The "Gmail disconnected" banner is expected until Gmail is connected.
- Env: `DATABASE_URL` (set → Postgres engine, absent → SQLite at `HQ_DATA_DIR`; D19),
`SIMS_PIN_PEPPER` (set once, never change), `HQ_SECRET_KEY`
(before Gmail), `GOOGLE_*` (Gmail), `AWS_*` (Cost Explorer), `HQ_PORT` (default 5182).
`SIMS_PIN_PEPPER` (set once, never change), `GOOGLE_*` (Gmail), `AWS_*` (Cost Explorer),
`HQ_PORT` (default 5182).
- **Credentials are stored in the clear (D31/D32).** `HQ_SECRET_KEY` was removed — portal /
gateway passwords, module secrets, client DB passwords and the Gmail token are plaintext,
usable keyless (login passwords stay scrypt-hashed). A DB dump exposes them, so protect the
DB file and its backups. Back up with `npm run backup` (D33 → `pg_dump` to `./backups`).
- **SMS gateway** (D28): the SMS module's per-client balance is pulled live from the provider
panel (`sms.balance_api_url`, keyless) — manual "Refresh from gateway" + a daily scheduler
pass; low balances surface on the SMS credits screen, cockpit tile and dashboard alert.
- Postgres integration tests: `HQ_PG_TEST_URL=postgres://hq:hq_dev@localhost:5432/hq_test npm test`
(wipes that database each run — never point it at real data).
- Health check: `GET /api/health` → `{ ok: true }`.
- Verified state: `npm install` + typecheck clean, 389 tests pass. Exercise real flows end
- Verified state: `npm install` + typecheck clean, 416 tests pass. Exercise real flows end
to end before claiming done.
## Docs (kept in `docs/`)

@ -26,15 +26,23 @@ document and conversation we've had with them.
- **Reports**, a **Dashboard**, an **append-only audit log** (every mutation), an **APEX
importer** for the initial 300-client load, and a **scheduler** (runs on boot + every 6h:
reminders, bounce polling, AWS cost pull).
- **A redesigned ops UI (2026-07-17)** — warm light/dark theme with a teal accent, grouped
sidebar with a live reminder badge, a **Ctrl-K command palette** (client search, pages,
actions), and a **Client 360** with tabbed sections and a 12-month **relationship-pulse
ribbon**; phone-usable drawer layout. Spec:
`docs/superpowers/specs/2026-07-17-hq-console-redesign-design.md`.
- **A redesigned ops UI** — light/dark theme with accent + neutral palettes, grouped
sidebar with a live reminder badge, a **Ctrl-K command palette** (clients, documents,
tickets, pages, actions), and a **Client 360** with tabbed sections and a 12-month
**relationship-pulse ribbon**; phone-usable drawer layout.
- **Support tickets** (aging/SLA/assignee workbench, bill-from-ticket), **onboarding**
milestones, and **per-module operational fields** each module declares (config, not code) —
surfaced per client on Client 360, across all clients in the **Module data** directory, and
as a **Portals** quick-list of every stored login. For the **SMS** module those fields
include a **live credit balance** pulled from the provider gateway (manual refresh + a daily
pass), with low balances flagged for a one-click top-up quote.
- **Renewals** command center, an owner **MIS cockpit**, an **audit-log viewer**, and
**database backups** (`npm run backup` → compressed, restorable `pg_dump`). Credentials are
stored in the clear (no `HQ_SECRET_KEY`), so the DB and its backups are the security boundary.
## Build state (start here)
Working codebase, not a plan. `npm install` + typecheck are clean and **389 tests pass**.
Working codebase, not a plan. `npm install` + typecheck are clean and **416 tests pass**.
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
**[docs/DEPLOY-HQ.md](docs/DEPLOY-HQ.md)**.
@ -47,9 +55,10 @@ runbook (server, HTTPS, backups, Gmail/AWS/import wiring, the Postgres switch) i
dev/test engine; Postgres is the locked production engine (see DEPLOY-HQ.md). DB at
**`data/hq.db`**; server on **:5182** (also serves the built web UI and the public
`/share/:token` route).
- **Frontend — `apps/hq-web`**: React + Vite. Pages: Dashboard, Pipeline, Clients,
ClientDetail, Modules, Reports, NewDocument, DocumentView, Employees (owner-only),
DocumentTemplate (owner-only).
- **Frontend — `apps/hq-web`**: React + Vite. Pages: Home, Dashboard, Pipeline, Reminders,
Tickets, Onboarding, Clients, ClientDetail, Documents, NewDocument, DocumentView, Renewals,
SMS credits, Modules, Module data, Portals, Reports, MIS cockpit (owner), Employees (owner),
Profile, APEX Import (owner), Audit log (owner), Settings.
- **Shared packages** (trimmed forks): `@sims/domain` (ids, money, business-day, doc-series,
gstin, documents), `@sims/auth` (pin/scrypt), `@sims/billing-engine` (compute, tax — our
invoices are GST documents, computed to the paisa), `@sims/ui` (design system).

@ -1,7 +1,7 @@
# SiMS HQ — Build Status
_Internal ops console for running our software business. Not shipped to clients._
_Last reviewed: 2026-07-17 · Version 0.1.0_
_Last reviewed: 2026-07-19 · Version 0.1.0_
HQ is the back-office console we use to run ~300 Classic client relationships:
the client book, our software module catalogue and dated price book, quotations /
@ -19,23 +19,36 @@ self-service profiles, My Day, client support-access data, the APEX import web
UI, the editable module roster with one-click Convert & send, and the
section-per-module quotation PDF — then closed all 8 findings from its review.
**Since 2026-07-17 (D20–D33).** Support **tickets** (aging/SLA/assignee workbench,
bill-from-ticket), **onboarding** milestones, client **branches**, and **module-defined
operational fields** each module declares in its `field_spec` (config over code) — shown per
client on Client 360, across all clients in the **Module data** directory, and as a **Portals**
quick-list of every stored login. For **SMS** those fields include a **live credit balance**
pulled from the provider gateway (`sms.balance_api_url`, keyless): a manual "Refresh from
gateway" and a once-a-day scheduler pass, with low balances flagged on the SMS-credits screen,
the owner cockpit tile and the dashboard for a one-click top-up quote. Also added: a **Renewals**
command center, an owner **MIS cockpit**, an **audit-log viewer**, a **global search** (Ctrl-K
now spans clients + documents + tickets), a **Settings → Operations** panel (SMS gateway URL,
low-balance threshold, ticket SLA), Postgres as a first-class engine (`DATABASE_URL`; D19), and
a **database backup** script (`npm run backup`; D33). **D31/D32: credentials are stored in the
clear** — `HQ_SECRET_KEY` was removed, so portal/gateway passwords, module secrets, client DB
passwords and the Gmail token are plaintext and keyless (login passwords stay scrypt-hashed);
a DB dump exposes them, so the DB file and its backups are the security boundary.
## Verification state
| Check | Result |
| --- | --- |
| `npm install` | clean |
| `npm run typecheck` (root + workspaces) | clean, no errors |
| `npm test` (`vitest run`) | **389 tests pass across 74 files** |
| `npm test` (`vitest run`) | **416 tests pass across 85 files** (5 Postgres-gated skipped) |
The 346 figure is the live `vitest run` count measured 2026-07-17, after the
D18 go-live cluster **and its post-delivery review pass** (8 reviewer findings
— convert-and-send click bubbling, importer support-field mapping, Pipeline
convert parity, client-book filters, typed contact roles, due-date anchors on
dashboard/aging, and two atomicity gaps — all confirmed and fixed the same
day). Test files live under
`apps/hq/test/` (49 files, including `employees`, `employees-routes`,
`client-owner`, `pipeline`, `quote-followup`, and `reminder-schedule`),
`apps/hq-web/test/`, and `packages/{domain,auth,billing-engine,ui}/test/`.
The 416 figure is the live `vitest run` count measured 2026-07-19; the suite has
grown from the D18 go-live cluster through the D20–D33 work (tickets, module fields,
renewals, cockpit, audit viewer, SMS gateway, module directory/portals/search, and
the D31/D32 plaintext-credential change with its own migration + review). Test files
live under `apps/hq/test/`, `apps/hq-web/test/`, and
`packages/{domain,auth,billing-engine,ui}/test/`.
## Architecture
@ -60,13 +73,16 @@ day). Test files live under
- Web: `apps/hq-web` — `npm run dev` / `npm run build`.
- CLI: `npm run import` (APEX importer), `scripts/gmail-connect.ts` (one-time
Gmail OAuth).
- Env: `HQ_PORT`, `HQ_DATA_DIR`, `HQ_SECRET_KEY` (64 hex / 32 bytes, for token
encryption), `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET`,
`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`.
- Env: `HQ_PORT`, `HQ_DATA_DIR`, `DATABASE_URL` (Postgres engine; D19),
`SIMS_PIN_PEPPER`, `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET`,
`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`. **`HQ_SECRET_KEY` removed (D32)** —
credentials are stored in the clear, no key needed.
- Backups: `npm run backup` (D33) — `pg_dump` to `./backups`, restorable with
`pg_restore`.
## Database schema (`apps/hq/src/db.ts`)
The schema creates 26 tables on open; `migrate()` additively backfills the
The schema creates 31 tables on open; `migrate()` additively backfills the
post-launch columns on older DBs (`module.quote_content`, `email_log.bounced`,
`document.due_date`, `staff_user.phone`/`title`, `client.owner_id` plus the
D18 support fields `anydesk`/`os`/`district`/`sector`/`db_password_enc`, and
@ -82,7 +98,12 @@ and widens two CHECK constraints via a guarded, transactional table rebuild
| `client` | Client registry — code, name, GSTIN, state, contacts (JSON), status, account owner (`owner_id` → `staff_user`), source |
| `module` | Software module catalogue — SAC, allowed billing kinds, multi-sub flag, quote-content lines |
| `module_price_book` | Dated prices per module/edition/kind (latest effective row wins) |
| `client_module` | A module assigned to a client — lifecycle status, install/complete/train dates, next renewal |
| `client_module` | A module assigned to a client — lifecycle status, install/complete/train dates, next renewal, provider/username/password (plaintext, D32), module-defined `field_values` (D21) |
| `usage_rate_band` | Dated per-unit usage pricing with a minimum order (e.g. SMS ≥ 50,000 units) |
| `client_branch` | Client branches/offices (APEX parity) |
| `project_milestone` | Onboarding checklist items per client_module |
| `ticket` | Support workbench tickets — status, assignee, opened/closed dates, source (`apex`/`hq`) |
| `sms_balance_check` | SMS gateway poll telemetry (status/balance/message/checked_at) — operational, not audited (D28) |
| `tax_class` | GST rate classes (dated); `GST18` seeded at 18% |
| `doc_series` | Per (doc_type, FY) running number + prefix |
| `document` | QT / PROFORMA / INVOICE / RECEIPT / CREDIT_NOTE — totals, status, ref chain, JSON payload |
@ -90,7 +111,7 @@ and widens two CHECK constraints via a guarded, transactional table rebuild
| `document_share` | Opaque public share tokens — expiry, revoked flag |
| `payment` | Money received — mode, reference, amount, TDS |
| `payment_allocation` | How a payment is applied across invoices |
| `email_account` | The single Gmail sending identity — encrypted refresh token, active/dead status |
| `email_account` | The single Gmail sending identity — refresh token (plaintext since D32), active/dead status |
| `email_log` | Append-only send trace — sent/failed, gmail message id, error, bounced flag |
| `setting` | Key/value store — `company.*` identity, `template.*` letterhead text, reminder + AWS config |
| `audit_log` | Append-only before/after JSON for every mutation |
@ -166,7 +187,7 @@ Everything below is implemented and exercised by tests via the Express router
renders the one document inline as PDF; unknown/expired/revoked tokens return a
data-free "link unavailable" page. Tokens are never logged or audited.
- **Email (Gmail)** — `gmail-connect` loopback OAuth (send + readonly scopes)
stores an AES-256-GCM-encrypted refresh token. Sends go over the Gmail REST API
stores the refresh token (plaintext since D32). Sends go over the Gmail REST API
(no SDK): access-token exchange, raw MIME build with PDF attachment, send.
`invalid_grant` flips the account to `dead` and raises the dashboard banner.
Document emails and templated reminder emails share the send path; every
@ -267,6 +288,14 @@ command palette, Gmail status pill, theme/accent switcher and a user menu
| `/documents` | `Documents` | Register of every document, newest first: type chips + status filter, honest server pagination with true totals, one-click confirmed **Convert & send** on issued proformas |
| `/documents/new` | `NewDocument` | Quotation-in-minutes composer: client type-ahead, line editor, server-computed GST, due-date field on invoices (auto-filled from terms, editable), and a live PDF-fidelity preview (desktop split view / mobile sheet) |
| `/documents/:id` | `DocumentView` | Document page: PDF preview, state-driven action bar (issue / send / mark / convert / cancel / credit note / record payment), share & download group (copy link / WhatsApp / native share / revoke), event timeline and email log |
| `/tickets` | `Tickets` | Support workbench: status chips + counts, per-row assignee, **Age** column with SLA overdue flag + Overdue chip, search, one-click **Bill** (New Document pre-filled) |
| `/projects` | `Projects` | Onboarding: per-client-module milestone checklist board |
| `/renewals` | `Renewals` | Module subscriptions + AMC coming due, earliest first, one-click renewal/top-up proforma |
| `/sms-balances` | `SmsBalances` | SMS credit balances (live from the gateway), lowest first; Low/All/Not-recorded chips, per-row top-up quote, owner **Refresh from gateway** |
| `/module-data` | `ModuleDirectory` | Pick a module → every client on it with that module's fields as columns, searchable, portal URLs clickable |
| `/portals` | `Portals` | Every stored login across all modules — Open ↗ links, 🔑 = password on file (reveal on Client 360) |
| `/mis` | `Mis` | Owner cockpit: this-month billing / collections / outstanding / pipeline / renewals / tickets / SMS-low, each tile click-through |
| `/audit` | `Audit` | Owner audit-log viewer: entity/action filters (from server facets), paginated before/after key-diff |
| `/settings` | `Settings` | Settings hub (Pinterest-style left rail, `?s=` deep-links): Company profile · Document template (live preview) · Reminders (dated cadence rows, append-only) · Sharing default expiry · Integrations status · Appearance. Owner-only except Appearance; `/settings/template` redirects here |
| `/employees` | `Employees` | Owner-only staff management: add / edit / re-role / reset password / deactivate / reactivate, surfacing the last-owner and self-deactivation guards |
| `/profile` | `Profile` | Self-service: edit display name/phone (email + role read-only), change own password (current password required; other sessions purged) |
@ -295,7 +324,7 @@ 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
OS, district/sector filters, a DB password — plaintext since D32 — 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._

Loading…
Cancel
Save