Commit Graph

262 Commits (d05c7ee31b3f7e39f6a838a9aaecdc0b12f0b3f5)
 

Author SHA1 Message Date
Thomas Joise 9fd4062cfe feat(d20): P1 — ticket desk, client branches, per-service data (schema + repos)
New tables ticket + client_branch (both engines: SCHEMA + pg 002-apex-parity);
client_module gains provider/username/password_enc/details/remark with
migrate guards. repos-tickets: team-visible desk, honest pagination, status
lifecycle stamping closed_on, counts + kind suggestions from history.
repos-branches: minimal audited CRUD. repos-modules: service fields on
ClientModule (hasPassword only — ciphertext never leaves), validated details,
setModulePassword/revealModulePassword mirroring the client DB password
discipline. 5 new tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 7d5223908f docs(spec): D20 APEX parity slice — ticket desk, service data, branches, importer v2
Survey verdicts for all 25 real APEX tables; founder calls locked:
full ticket desk (4,567-ticket history), client_module service columns
(provider/username/password_enc/details), client_branch, five modules
= SMS, RTGS, WhatsApp, Mobile App, ATM.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise bb8dfc3b97 docs: D20 — hq.simssoftware.com on the company EC2; runbook gets the decided target + RDS-first sequencing (D19 switch delivered)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 days ago
Thomas Joise 30a1ea1c0a docs: D19 delivered — spec stamped with deviations, D-record, env docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 95df5cb5b9 feat(db): D19 P4 — DATABASE_URL engine selection + Postgres integration suite
startServer picks Postgres when DATABASE_URL is set, SQLite file otherwise.
New pg-engine.test.ts (opt-in via HQ_PG_TEST_URL, wipes its target schema):
migrations from zero, invoice issue with series numbering + due-date stamp,
paisa-exact payment to paid, exactly-once reminder upsert via ON CONFLICT,
savepoint-nested rollback (incl. the mixed dbPassword+patch write), and a
full HTTP boot smoke (health/login/clients) on the postgres engine.
Verified locally against PostgreSQL 17: 5/5. Default suite (sqlite)
346/346, typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 5f9f961d20 feat(db): D19 P1-P3 — async DB interface, dual engines, Postgres adapter
P1: every repo, route, scheduler path and test now speaks one async DB
interface (all/get/run/exec/transaction). better-sqlite3 stays underneath
for dev/tests via SqliteDb (statement cache, manual BEGIN/SAVEPOINT
nesting); openDb stays sync so fixtures remain one-liners. Zero behavior
change: 346/346 tests, typecheck clean.

P2: migrations-pg.ts — numbered Postgres migration set (001-init: bigint
paise, integer flags, TEXT ISO dates, TEXT JSON; reminder CHECK born in
final widened form) + schema_migrations runner.

P3: db-pg.ts — pg Pool behind the same interface: ?->$n rewrite outside
string literals, transactions pinned to one pooled client via
AsyncLocalStorage with savepoint nesting, int8/numeric parsed to JS
numbers so paise math is identical on both engines.

Also: reminder upsert INSERT OR IGNORE -> ON CONFLICT DO NOTHING
(portable, same changes-count semantics).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 08be48817e docs(spec): D19 Postgres switch design + pg driver dependency
Locked decisions: pg driver behind an async Db interface with dual
engines (sqlite for fast tests, pg for prod), bigint paise, TEXT ISO
dates, jsonb payloads, numbered migrations, ?->$n in the adapter,
DATABASE_URL selects the engine. Build order: async refactor first
(zero behavior change), then schema, adapter, dual-engine suite, smoke.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 83f12685c4 docs: record ideal-web-app pass delivered — 346 tests, Settings hub row, spec stamped
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 days ago
Thomas Joise 60698fc08f fix(iwa): final-review fixes — dialog layering/scrim/scroll-lock, a11y labels+disabled+alerts, settings guards+errors, edit-mode locks, strict offsets, titles
27 adversarially-confirmed findings from the multi-agent whole-branch review (2 deferred).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 days ago
Thomas Joise 19fe037bb4 docs: record the review pass — 345 tests, all 8 findings closed
STATUS: count + review summary, D18 sentence in the intro, honest migrate()
column list, routes table gains /reminders /documents /profile /import and the
collapse/user-menu shell description. README + CLAUDE: 345. D18 record in
06-DECISIONS gains the review outcome. Spec banner notes the post-review
deltas (CSV support columns, LIKE filters).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 94018b9ae4 fix(review): close all 8 findings from the D18 slice review
MAJOR
- Documents register: Convert & send no longer bubbles into row navigation
  (stopPropagation guard on the actions cell)
- Pipeline rows: convert_invoice now uses the same confirmed convert-and-send
  as the Documents page (was the old plain draft convert)
- APEX importer carries anydesk/os/district/sector: stg_client columns
  (+ migrate guard), optional CSV headers staged, commit INSERT maps them
- Clients page: district/sector filter inputs wired to the server filters
  (server side widened to LIKE for narrow-as-you-type)

MINOR
- WhatsApp/secretary typed contact roles: datalist on the contact Role input,
  labeled rows (with Copy) on the Client 360 support card
- Dashboard overdue + dues-aging now anchor on COALESCE(due_date, doc_date) —
  an unpaid invoice not yet due no longer shows as overdue (test added)
- PATCH /clients/:id is atomic: dbPassword + field patch in one transaction
- Audit same-txn: setClientDbPassword, stageCsv, and the import stage/commit
  route audits all wrapped in db.transaction

Suite: 345 tests / 66 files, typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 8064c49465 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>
5 days ago
Thomas Joise f30532b7d1 feat(import): APEX cutover web UI — stage/verify/commit over the existing importer (Phase 7, D18 WS-B)
- POST /import/stage (CSV text), GET /import/status (report + first-50 problem
  rows with honest totals), POST /import/commit — all owner-only, audited;
  commit still refuses while ANY staged row carries problems (existing guard)
- /import page (ADMIN nav, owner-only): file pickers → problem tables →
  verification stats (incl. invoice-value cross-check) → confirm-gated Commit;
  re-stage freely, series seeding message surfaced
- 2 route tests (403s for staff; bad→refuse→clean→commit round-trip incl.
  series seeding past legacy numbers); suite 344 green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise b5d252b7dc feat(shell): sidebar collapse + My Day dashboard (Phase 5, D18 WS-E)
- sidebar collapses to an icon rail (footer toggle, localStorage-persisted;
  nav tooltips via title; <900px hamburger untouched)
- dashboardView gains mine-scoping: staff are ALWAYS scoped to their book
  (clients they own, docs/payments they created, their follow-ups, their queue);
  owner/manager get a Mine/Everyone toggle (?mine=1)
- 1 test; suite green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 30c2e3b60b feat(clients): support-access data — AnyDesk/OS, district+sector filters, encrypted DB password with audited reveal (Phase 6, D18 WS-F)
- additive client columns; toClient never maps db_password_enc (payloads carry
  only hasDbPassword); listClients gains district/sector filters
- setClientDbPassword: AES-256-GCM via HQ_SECRET_KEY, refuses loudly without a
  key; revealClientDbPassword decrypts + writes one audit row per reveal
- PATCH /clients/🆔 dbPassword is owner/manager-gated inside the same route;
  POST /clients/:id/reveal-db-password (owner/manager)
- client dialog gains district/sector/AnyDesk/OS; Client 360 gains the Support
  card (copy AnyDesk, •••• + Reveal/Set for managerial)
- 5 tests; suite green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 28aa08c9c9 merge: Task 9 polish sweep from isolated worktree (favicon, titles, toasts)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 828f533a69 feat(templates): section-per-module quotation PDF (Phase 9, D18 WS-H)
- QUOTATION renders one titled section per line: module name, its what's-
  included block, SAC + qty × rate + amount; sections never split mid-page and
  each module after the first opens a fresh page in print
- summary line up top ('This proposal covers: …'); totals/terms/signature
  unchanged; INVOICE/PROFORMA/CN/RECEIPT keep the compact grid (regression-
  asserted); preview-fidelity golden test untouched and green
- 3 tests; suite green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 747026fa3b feat(modules): roster full-edit + assign/unassign; one-click Convert & send on the documents register (Phase 8, D18 WS-G)
- listClientsByModule rows carry cmId; ClientModulePatch gains kind/edition
  (kind validated against module.allowedKinds — same audited write path as
  Client 360, so the two views can never disagree)
- Modules roster: inline edit (kind/edition/renewal/status), ✕ unassign with
  confirm (deactivates the link, totals shrink), Assign client… type-ahead
- Documents register: issued proformas get a Convert & send quick action
  (same convert-and-send endpoint + confirm as the document view)
- 2 tests; suite 333 green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise a882d108fe feat(hq-web): favicon, per-route tab titles, success-toast sweep
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 3f7f08a908 feat(hq-web): user chip opens a Profile/Logout menu (Phase 4 finish, D18 WS-E)
The chip was a bare link to /profile with a separate logout icon; the spec
(§5) calls for a small menu. Chip is now a button toggling a two-item menu
(Profile / Logout) that closes on outside click, Escape, and route change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 08dda8bab3 fix(hq-web): edit dialogs clear blanked optional fields (follow-up, outcome, plan amount)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise d95bf6d467 feat(employees): self-service profile + change-own-password; phone/title fields (Phase 4, D18 WS-E)
- GET/PATCH /me (self-scoped whitelist — role/email untouchable) + POST /me/password
- changeOwnPassword: current password required; every OTHER session dies in the
  same txn, the changing session survives; audited without hashes
- staff_user.phone/title additive columns, surfaced on Employees (columns + edit
  dialog) and the new Profile page; user chip in the shell opens /profile
- 7 tests; suite 331/331 across 64 files

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 1a8a3faf5d feat(hq-web): AMC/plan/interaction/employee/module dialogs — edit everywhere, confirmed deactivations
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 59c41ee8f7 fix(hq-web): guard client dialog against double-submit and cancel-during-save
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 0aca8b42b4 feat(hq-web): client create/edit dialog with contacts repeater — clients finally editable
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise e612c5cf81 feat(hq-web): standalone Reminders page (go-live WS-C, phase 3)
- /reminders page: Queued/Sent/Failed/Dismissed chips mapping to ?status=,
  managerial-only Mine/All toggle via ?owner= (staff are server-forced to
  their own rows), kind/client/doc/due/status table with Send/Preview/
  Dismiss on open rows, honest server-driven pager (rule 8).
- Extract the Dashboard queue actions into shared
  components/ReminderQueue.tsx (QueueActions, RULE_LABEL, reminderTone)
  with a re-entry guard on Send/Dismiss; Dashboard queue card links
  "View all" to /reminders.
- api.ts: getReminders now takes { status, owner, page, pageSize } and
  returns the full QueueItem rows + page metadata; login stores the
  staff id (hq.id) so Mine can narrow by owner.
- Nav item under WORK + route; no backend changes. A contract test pins
  the sent/dismissed chips, ?owner= narrowing and cross-chip staff scope
  at HTTP level (apps/hq/test/reminders-page.test.ts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 5d81550e34 feat(hq-web): send-email composer dialog + styled confirms — window.prompt/confirm eliminated
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 584fb291f8 feat(documents): paginated register with honest total + Documents page (Phase 2, WS-D)
listDocuments now returns { documents, total, page, pageSize } (default 50,
cap 200, newest first) with the client name joined server-side — no N+1.
GET /documents passes page/pageSize through; type/status/clientId filters
compose against the same WHERE for both count and page (rule 8: no silent
caps). clientLedger walks the paginated repo in 200-row batches so the
ledger still surfaces every document. New /documents screen: type chips,
status filter, mono doc numbers, amount column, pager honouring the server
total; rows open /documents/:id. Nav entry under WORK (exact-match so New
Document keeps its own highlight).

Note: the GET /documents route hunk itself landed via 8861b46, swept into
a concurrent commit from the same working tree; this commit carries the
repo/ledger/web halves and the tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 8861b46afd feat(settings): configurable default share-link expiry (owner, audited)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise d28bca5cf0 fix(settings): validate whole reminders PUT body before writing any setting
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 36fcf795a8 test(documents): assert due-date stamp on the recurring-generation issue path
Closes the WS-A coverage gap: due-dates.test.ts proved the manual-issue and
convert paths but relied on code inspection for recurring generation. A manual
recurring plan now runs through runDailyScan and the generated invoice's
due_date is asserted as doc_date + seeded 15-day terms. Suite 310/310.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise bc140fc54c feat(documents): invoice due dates — terms default + override, honest overdue chase (Phase 1, D18)
- document.due_date additive column; billing.payment_terms_days seeded (15)
- issueDocument stamps doc_date+terms on INVOICE when absent — covers manual
  issue, convert-and-send and recurring generation in one place
- drafts accept dueDate (INVOICE only, validated); composer date field
  auto-filled from GET /settings/billing; invoice PDF prints Due Date
- overdue scan + reminder anchor on COALESCE(due_date, doc_date); email says
  'was due on X and is now N day(s) overdue' when a due date exists
- legacy fixtures pinned to due_date=NULL (sanctioned: they test the doc_date
  anchor); 9 new tests; suite 309/309 across 60 files

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise aa05060181 feat(settings): reminder day-settings + append-only dated schedule endpoints (owner, audited)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 4e2b71cf9f fix(ui): toast auto-dismiss id derived inside the state updater (React 18 batching)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 03402ba0e1 feat(ui): Toaster with pure store (TDD) + FormGrid; provider mounted at root
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 0b46c03530 fix(ui): ConfirmDialog error handling + Dialog focus hardening
- ConfirmDialog: a rejected onConfirm no longer closes the dialog as if it
  succeeded; the rejection is handled (no unhandledrejection), busy clears,
  and the error message is shown inline (role=alert)
- ConfirmDialog: Cancel / scrim / Escape / X are gated while busy, so the
  dialog cannot be dismissed (and onClose cannot double-fire) mid-flight
- Dialog: Escape + Tab trap moved to a document-level keydown listener so
  they still work when focus escapes the card (e.g. lands on <body>)
- Dialog: focus restore now runs via effect cleanup, covering the
  {show && <Dialog/>} unmount-while-open pattern
- Dialog: initial focus skips disabled controls

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 2fbb4c6442 feat(ui): Dialog + ConfirmDialog (Pinterest chrome), pill buttons; Modal removed
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 66ec742523 docs(spec): go-live cluster gains WS-G editable roster + one-click convert everywhere, WS-H section-per-module quotation PDF (now 10 phases)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise e68b3b3b55 docs(plan): ideal-web-app pass — 10-task implementation plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 7a9c6f9459 docs(spec): ideal-web-app pass — edit everywhere, dialogs, toasts, settings hub
Pinterest-chrome modals (Dialog/ConfirmDialog), Toaster, client editing (server
already supports it), send-by-email modal replacing window.prompt, /settings hub
with dated reminder-schedule rows + sharing default (two small additive backend
slices), favicon + per-route titles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 02bc75d691 docs(spec): go-live cluster design — due dates, import UI, reminders/documents pages, shell & profile, client support data (8-phase roadmap)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 8cb37f648a docs: bring all documents current with the delivered redesign (D17)
- 06-DECISIONS: new D17 entry — warm ops-console direction, teal/ink,
  mono/sans split, Ctrl-K palette, Client 360 ribbon+tabs, UI-only scope
- DEPLOY-HQ: fix pm2 launch — start from repo root (a pm2 app started from
  apps/hq would boot an empty DB in apps/hq/data), no rebuild-per-restart
- 14-SPEC: cross-link the delivered UI-direction spec (D17)
- README: redesign feature bullet; STATUS: login + Client 360 screen rows
  reflect split panel, KPI row, pulse ribbon, deep-linkable tabs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 418555179b docs: bring every document current — runbook gains D16 go-live knobs, delivery stamps on all executed specs/plans, 14-SPEC delivery note, README doc table
- DEPLOY-HQ.md §6b: share.base_url (required for quote follow-up emails),
  quote.followup.policy manual/auto, dated reminder_schedule cadences,
  employee accounts + account-owner assignment at go-live
- STATUS/README/CLAUDE already at 293 tests; specs/plans now carry their
  DELIVERED/EXECUTED stamps so the archive reads honestly

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 7862452989 docs: record funnel phases 7-10 delivered — 293 tests, gaps list updated (D16 final)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 953cf1dff8 feat(modules): module→client roster, notify-all, CSV export (Phase 10, spec §10)
- listClientsByModule: reverse lookup over active links, ordered by client name,
  paginated with a true total; per-row dated price via priceOn + next renewal;
  footer revenue total spans ALL links (active=0 excluded by design, stated)
- GET /modules/:id/clients (paginated) + /clients.csv (streams every row, no cap)
- POST /modules/:id/notify (owner/manager): resolves the full roster first, sends
  via the Gmail path, one email_log + one audit row per attempted recipient,
  names every unreachable client in the response (warn, never truncate)
- Modules page roster panel: table + revenue footer + Notify-all composer + CSV
- 8 tests; suite green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 4a1747958e feat(reminders): invoice dN milestone escalation from the dated schedule (Phase 9, spec §8)
- invoice_overdue scan: monthly bucket → d7/d15/d30 milestones resolved via
  resolveSchedule (dated config, rule 3); each fires once via the idempotency key
- F7 catch-up guard: scan gaps and the monthly→dN cutover enqueue only the
  highest crossed milestone, never the whole ladder
- ctx.daysOverdue populated in reminderContext (A7: anchored on doc_date) so the
  'N day(s) past due' template text renders; injectable today for determinism
- daysBetweenIso helper; 5 tests; suite green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise e9357bd8e6 fix(redesign): final-review fixes — palette races+a11y, avatar dark contrast, micro-label floor, drawer focus, pulse legend/labels/targets, warn contrast
26 adversarially-confirmed findings from the multi-agent whole-branch review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise fffcc2a05d feat(documents): one-click convert-and-send + proforma supersede (Phases 7-8, spec §8)
- POST /documents/:id/convert-and-send: guards before any write (proforma-only,
  Gmail alive, recipient resolved), ONE txn convert+issue, email strictly after
  commit — failed send never rolls back the issued invoice (warning instead)
- supersedeProforma: cancel (number stays consumed) + fresh linked PROFORMA
  draft carrying the payload verbatim; INVOICE hard-rejected to credit-note path
- POST /documents/:id/supersede route; DocumentView gains both buttons
- 10 tests; suite 280 green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago
Thomas Joise 1db0a8c6b7 docs(spec): record as-built deviations from final review adjudication
PageHeader slots trimmed; Client 360 KPI substitution (no server outstanding
figure); 'since' dropped (no createdAt); §8 render tests → pure-function tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 days ago
Thomas Joise 5a1c84f1de docs: record quote-to-close funnel slice (D16)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 days ago