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>
@ -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.
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) |