Commit Graph

86 Commits (6939115f6cb4122da801eddf2bd8281a987059ca)

Author SHA1 Message Date
Thomas Joise 6939115f6c fix(ui): pager alignment, one-line billing kinds, Fields explanation
- Table pagers (Modules roster, Client 360 tickets) used an unstyled .wf-pager so the
  summary text and Prev/Next butted together. Add .wf-pager flex styling: summary on the
  left, Prev/page/Next pushed to the right end.
- Module Details: 'Billing kinds' label + checkboxes now on one line (was stacked).
- Module Fields: add a plain-language explainer — what the fields are (with an SMS
  example) and WHERE they show up (each client's Modules tab + Catalog → Module data).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 days ago
Thomas Joise 7f740a7e2e feat(ui): split module screen into Clients | Setup tabs + prominent Add-client
A module's detail screen was one long stack mixing day-to-day (who's on it) with owner
config (details/fields/quote/prices). Now two tabs:
- Clients (default): the roster + install details, with a prominent primary
  '+ Add client to this module' button (was a quiet 'Assign client…'). Tab shows the
  client count.
- Setup (owner only): Details, Fields, Quote content, Price book.
Opening a module always lands on Clients. typecheck + build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 days ago
Thomas Joise 6178e0a6a3 feat(ui): friendly Add-a-field form for module fields
Replaces the raw per-row grid (label/key/type/options/help/required inputs) with:
- Existing fields as clean cards (name, plain-language type, required badge, options, hint)
  with Edit / Remove / reorder.
- '+ Add a field' opens a dialog in plain language: Field name, 'What kind of
  information?' (Text / Number / Date / Yes-No / Choice from a list / Secret, each with a
  one-line hint), Options (only for a list), Hint, Required?.
- The internal lower_snake_case key is AUTO-derived from the field name (unique) and never
  shown — existing fields keep their key (it ties to stored data). Each add/edit/remove/
  reorder saves immediately (no separate Save step). typecheck + build clean; backend
  fieldSpec validation unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 days ago
Thomas Joise 8485e69ff0 fix(ui): module page as master→detail screen (clear which module you're in)
Selecting a module used to dump a stack of panels below the list with no indication of
which module — confusing (founder feedback on 'Fields — Mobile App'). Now clicking a
module opens its OWN screen: a breadcrumb + big header naming the module (name · code ·
SAC · kinds), a Back button, then clearly-labelled sections — Details (editable),
Clients on this module, Fields (plain-language: 'the details you record for each client
on this module'), Quote content, Price book. The list hides while you're in a module.
Row action is now 'Open →'. typecheck + build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 days ago
Thomas Joise 56686dd4d6 feat: show + download SMS logins; install details in Module data
- SMS credits screen (managerial): Username + Password columns shown plainly (D32
  plaintext), a Show/Hide toggle, and 'Download logins (CSV)' (client, code, username,
  password, provider, balance, install date, phone). Password decrypted server-side only
  for owner/manager viewers (smsBalances includeSecrets); staff still see balances only.
- Module data directory: rows now carry the lifecycle install details — Status, Installed
  date, Next renewal columns alongside the module's own fields, so the whole book shows
  when each client went live plus all their service data. typecheck + tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 days ago
Thomas Joise f0bba301e9 feat(docs): require a reason when cancelling a document
Cancelling an issued document now needs a reason: cancelDocument takes it, refuses an
empty one, and records it on the 'cancelled' event (visible in the doc timeline) and in
the audit after-image. The Cancel dialog gains a required reason box (the confirm stays
open with an error until one is given). Supersede & recreate cancels with 'Superseded &
recreated'. Documents are never hard-deleted (immutability) — cancel is the path, now
always explained. Full suite 416 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 days ago
Thomas Joise d68fc257a2 fix(docs): summary line for legacy imports + tidy Record-payment panel
- Tax invoice line table: APEX-imported documents carry only totals (0 lines), so the
  table printed blank with just the grand total. Add a display-only fallback summary
  line derived from the doc's own totals — description from the doc-number's module
  prefix, qty 1, rate = taxable, GST% back-computed. Issued docs unchanged (immutability).
  New HQ documents already carry full line detail; unaffected.
- DocumentView: the Record-payment form dropped in bare between the share row and the
  PDF. Wrap it in a titled card with a Close button so it reads as a clean section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 days ago
Thomas Joise b065332f89 fix(ui): explicit Edit button per module row (inline form), separate from client roster
Clicking a module row shows its clients (roster) — which fought with editing. Add an
Edit button per row (owner) that opens the inline 'Edit module' form directly (name,
SAC, billing kinds, multi-sub, active), stopPropagation so it doesn't also open the
roster; Save/Cancel close it. Row-click still drills into clients + field-spec + quote
+ prices. typecheck + build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 days ago
Thomas Joise 432587c7a1 fix(ui): Client 360 shows documents + payments newest-first
The ledger returns rows oldest-first, so 'Recent documents' sliced the OLDEST 5 and
the Documents/Payments tables listed oldest→newest. Sort newest-first in the view
(by date then doc-no for docs; received-on then id for payments) so recent transactions
lead. Frontend-only; no shared query/test touched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 074e810415 fix(ui): edit-module details form + keyboard nav in New Document client picker
- Modules: selecting a module now shows an 'Edit module' form (owner) — name, SAC,
  billing kinds, multi-sub, active (via existing PATCH /modules; code stays the
  identity). Header hints that clicking a module opens its editors. Fills the gap where
  a module's core details couldn't be edited from the UI.
- New Document client type-ahead: arrow Up/Down move the highlight, Enter selects, Esc
  closes, hover tracks — proper combobox keyboard nav (was mouse-only). typecheck + build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 23089a9b1f feat(d30): clients-per-module count on Modules page + comment fix
- Modules page now shows a "Clients" column — how many clients sit on
  each module — via one lightweight best-effort roster count per module
  (cell shows '…' until it loads, never blocks the table). (Modules.tsx)
- Fix a stale comment: the reminder-queue badge now lives on the Reminders
  nav item, not Dashboard. (Layout.tsx)

All sidebar count badges kept as-is (Reminders / Pipeline / Tickets /
Renewals / SMS / Clients / Documents-drafts / Modules). The dead
@fontsource inter+jetbrains deps were already dropped from package.json.

Typecheck clean; web build green. Verified live: Modules "Clients" column
renders (seed AMC module -> 0), badges intact, zero console errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3 days ago
Thomas Joise 8ed56a97d5 feat(d30): Home as landing page + more sidebar counts
- Make the Home hub the landing: `/` now renders Home and the Dashboard
  moves to `/dashboard` (nav item, routes, and the hub's Dashboard card
  all updated). Unknown routes still fall back to `/` (Home).
- Add sidebar count badges for Clients (total book size), Documents
  (drafts still to finish) and Modules (count) — neutral, best-effort,
  alongside the existing Reminders/Pipeline/Tickets/Renewals/SMS counts.

Typecheck clean; web build green. Verified live: / → Home, /dashboard →
Dashboard, and the Modules count badge renders from real data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3 days ago
Thomas Joise b8e2be628f feat(d30): trim Home hub to main sections + sidebar count badges
- Home hub (/home) now shows only the day-to-day sections — Dashboard,
  Clients, Documents, Reminders, Renewals, Pipeline, Tickets, SMS credits,
  Modules — as a flat card grid; everything else stays in the sidebar.
- Sidebar section count badges (Layout.tsx): Reminders, Pipeline, Tickets,
  Renewals and SMS now show a live count, fetched best-effort. Badges are
  neutral by default, amber for SMS low balance, red for failed sends; the
  reminder-queue badge moves from Dashboard to Reminders. Hidden when the
  rail is collapsed.
- Neutral count-badge base + hub spacing (app.css).

Typecheck clean; web build + 410 tests green. The server.ts prod DB
password is intentionally left uncommitted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3 days ago
Thomas Joise 69c59815f0 feat(d28): Portals quick-list + global search (clients/docs/tickets)
Two 'easily get to it' access screens:
- Portals (Catalog): every subscription with a stored login/URL across all modules,
  Open ↗ links, 🔑 = password on file (reveal stays on Client 360). listPortals().
- Global search: the Ctrl-K palette now spans clients + issued documents + tickets in
  one /search call (repos-search.globalSearch), each hit routing to its record; per-type
  top-N with an honest 'capped' flag (no silent truncation).
Full suite 410 green; all three endpoints smoke-tested on live data (portals 78,
search capped, RTGS directory 13 fields).

Note: includes pages/Home.tsx (the concurrent dashboard-redesign session's Home hub)
so main.tsx's existing /home route resolves and the tree stays buildable; that session
owns its further iteration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 3f974e9d3d feat(d28): Module directory — every client on a module x its fields
New 'Module data' screen (Catalog): pick a module, see all active clients on it with
that module's own field-spec values as columns — the whole book at once (all RTGS IPs,
all CloudBackup buckets, all RecoverX portals), searchable, portal URLs clickable, each
row links to the client. moduleDirectory(db,moduleId) returns fieldSpec + rows; secret
VALUES never leave the server (shown as a lock), reveal stays on Client 360. Reuses the
field-specs so a new module field appears here with zero code change. typecheck green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 343cc6795d feat(d28): Settings → Operations (SMS gateway URL, low threshold, ticket SLA)
'All this should be db': the three knobs the new features used to read from code
defaults are now editable DB settings in one easy place — Settings → Operations
(owner). GET/PUT /settings/operations reads/writes sms.balance_api_url (SSRF-guarded
before store), sms.low_balance_threshold, ticket.sla_days. No release needed to
retune any of them; the app already resolves each from the DB with a safe fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 24f52fad0a feat: theme palettes + Geist font + dashboard cockpit styles
Add a `data-palette` theme axis (neutral ground) alongside mode + accent:
Warm (default), Slate, Graphite, Zinc — selectable in Settings > Appearance,
persisted per browser. Backward-compatible: `warm` needs no attribute so the
existing look is unchanged; accent (7 hues) and light/dark stay orthogonal.

Swap the UI font Inter -> Geist Variable + Geist Mono Variable (bundled offline).

Add the `.dash-*` cockpit styles finishing the redesigned Dashboard markup
already in tree (flat Today hero, KPI row, reminder queue, bento sections;
low-colour, red reserved for real failures).

packages/ui theme.ts/tokens.css/ThemeSwitcher.tsx are shared — restyles every
page. Typecheck clean; 410 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3 days ago
Thomas Joise fa15027f4d feat(d28): clickable 'Open ↗' link on module URL fields
Any module service field holding an http(s) value now shows a direct 'Open ↗' link
beside it on Client 360 — the RecoverX/CloudBackup/MobileApp portal + AWS-console
shortcuts the founder asked for, working for any URL-valued field (no new type).

Also (DB config, via PATCH /modules — audited, not code): extended the RecoverX,
CloudBackup and MobileApp field-specs with the operational fields requested
(workspace, portal URL, admin login, install date, payment status; S3 bucket / IAM
user / folder / server for cloud; corp id / server / config for the mobile app).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 58c97994bd feat(d28): SMS gateway live balance sync (BalAlert API) + daily auto + low alert
Pull each SMS client's actual credit balance from the provider panel instead of
manual entry. sms.balance_api_url (dated setting, default 164.52.195.161/API/
BalAlert.aspx) returns pipe-delimited code|STATUS|payload; parseBalanceResponse
reads the trailing number and flags ERROR lines, never guessing. Uses each
subscription's already-imported username/password_enc — the automated path decrypts
directly (no reveal-audit spam) and audits sms_balance ONLY when it changes;
per-poll telemetry lands in the unaudited sms_balance_check table (SQLite schema +
PG migration 010). Managerial 'Refresh from gateway' button (POST /reports/sms-
balances/refresh) + per-client refresh; scheduler does one pass per day
(maybeRefreshSmsDaily). Low-balance alert on the Dashboard + cockpit tile. Needs
HQ_SECRET_KEY (import-time key) to decrypt — no-ops with a clear message otherwise,
never stores plaintext. Parser unit-tested; full suite 408 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 650cde43a2 feat(d28): #3 ticket desk — SLA aging + overdue filter + bill-from-ticket
Age column on the workbench (days since opened); a still-open ticket aged past
ticket.sla_days (dated setting, default 7) shows red with a warning. New 'Overdue'
filter chip with an honest server-side count (still-open AND opened on/before the
SLA cutoff). Each row gains a 'Bill' action that opens New Document with the client
pre-selected (/documents/new?client=<id>) — the light ticket->invoice path, staff
add the lines. Kept deliberately un-bulky: no kanban, reuses the existing table.
Test locks the overdue filter + count. typecheck + full suite (403) green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise e83d70a4ff feat(d28): #6 SMS credit review + low-balance upsell
New 'SMS balances' screen: every SMS client with its credit balance (the module's
own sms_balance service field), lowest first so the ones about to run dry sit on
top. Low = below sms.low_balance_threshold (dated setting, config over code);
'Low' is the default view = the upsell list. One click raises a top-up proforma
(reuses the module renewal-quote path); client name links to the Modules tab where
the balance is kept. Owner cockpit gains an 'SMS balance low' tile as the at-a-
glance alert. Read-only report, no audit. Test locks parse/flag/sort. typecheck +
web build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 5761544c74 feat(d27): #8 audit-log viewer (owner-only)
listAuditPage (filters: entity/action/user/entity-id/date; honest total; newest
first) + auditFacets. GET /audit owner-only. New /audit 'Audit log' page: entity/
action filters from server facets, paginated table with a per-row before->after
key diff, actor names mapped via employees. Reading the log writes no audit row.
Test locks filter+pagination. typecheck + web build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 7f6e2ce904 feat(d27): #5 owner MIS cockpit
repos-mis misCockpit(today): this-month vs last for billed (issued invoices) +
collections (payments), total outstanding (dues-aging), open pipeline value,
renewals due in 30d, open tickets, reminder queue — composed from the existing
repo aggregates so figures reconcile with Reports/Dashboard. Owner-only GET
/reports/mis. New /mis 'Cockpit' page: KPI tiles with MoM deltas + click-through
to the source pages. Money in tests. typecheck + web build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 419dfebbaa feat(d27): #1 Renewals page (command center UI)
New WORK page /renewals: 30/60/90-day window chips, table of due module +
AMC renewals (client, type badge, due date, days-until with red/amber tint,
amount, total), one-click 'Renewal quote' on module rows (-> proforma draft)
and 'Open AMC' on AMC rows. api: getRenewals + generateModuleRenewalQuote.
typecheck + web build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise 18e47eb448 feat(d26): functional-feature UIs — GST report, doc search, statement, bulk ops
- Reports: GST summary tab (FY month range, per-month CGST/SGST/IGST + total,
  emphasized TOTAL row).
- Documents: debounced free-text search feeding q.
- Client 360: printable account statement (Dr/Cr ledger + running/closing
  balance + advance, @media print isolation); buildStatement + 4 tests.
- Clients: owner-only bulk-edit selection -> set a field across many (POST
  /clients/bulk). Onboarding board: owner-only bulk 'mark done' on the pending
  drill-down (POST /projects/bulk-milestone). Both confirm + toast + refresh.
- api.ts: getGstSummary, bulkUpdateClients, bulkSetMilestone, documents q.
typecheck + web build clean, web tests 8/8, full suite 397 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 days ago
Thomas Joise bbe125892d fix(d26): accent picker off top bar + mobile UX pass + responsive billing
- The 7-swatch accent selector is removed from the top bar (it now lives only
  in Settings > Appearance); the bar keeps a single compact light/dark ModeToggle.
  This declutters the header and fixes most of the mobile congestion.
- Mobile pass (<=640px): search takes the bar, Ctrl-K hint + Gmail pill hidden,
  avatar-only user chip; pages get real padding + bottom room; toolbars/chips
  wrap with air; inputs full-width & 42px tall; stats 2-up; tables padded.
- Billing composer: client search + line-item cards go full-width and stack on
  phones (composer-search / composer-line classes) so no container is undersized.
typecheck + web build clean, web tests 4/4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 9b920e50f9 feat(d25): bright doc type/status colours + sectioned client details + username UI
- Badge extended with green/blue/violet/gold/red tones (light+dark). Document
  TYPE badges: Quotation=gold, Proforma=violet, Invoice=teal, Credit note=red,
  Receipt=green (Documents, DocumentView, Client 360). STATUS brightened:
  paid/accepted=green, sent=blue, part-paid=amber, draft=grey, lost/cancelled=red.
- Client 360: new sectioned Details card (identity, full address, contacts
  labeled Office/Secretary/WhatsApp with mailto:/tel: links).
- Employees create form gains a required Username (login id) field + table
  column; Login field relabeled 'Username'.
typecheck + web build clean, web tests 4/4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 10b849a990 feat(d25): module display order (sort_order) + username login
- module.sort_order (both engines, migrate + pg 008); listModules orders by
  sort_order then code; createModule/updateModule + ModulePatch carry it, so
  the app-wide module order is owner-editable config (default 100 = trails).
- Login accepts a username: field is type=text (autocomplete username),
  relabeled 'Email or username' — a plain 'admin' login now works.
Module + web tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise ce6b564e2b feat(pwa/d25): installable PWA + phone polish
- Web manifest (standalone, id/scope/start_url=/, teal theme) + icon set
  generated from the HQ mark: pwa-192, pwa-512, maskable-512 (full-bleed
  safe-zone), apple-touch-icon 180, icon.svg. sharp added as a devDep for
  generation.
- Service worker (public/sw.js): installable + fast repeat loads without
  faking offline. NEVER touches /api or /share (always live); navigations
  network-first with an app-shell fallback; hashed assets cache-first. Registered
  from main.tsx in production builds only (skips Vite dev/HMR).
- index.html: manifest link, apple-touch-icon, apple/mobile-web-app meta,
  viewport-fit=cover; vite-env.d.ts adds vite/client types.
- Phone polish (app.css): safe-area insets, 16px inputs (kills iOS focus-zoom),
  40px tap targets, bottom-sheet dialogs, 2-up stat cards — on top of the existing
  <900px drawer + scrolling tables.
typecheck + web build clean, web tests 4/4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 35a26de442 fix(review): frontend quality pass — a11y, UX safety, error handling (medium/low)
Red-team frontend clusters (a11y + UX + code quality), three coordinated passes:

Accessibility:
- DataTable clickable rows keyboard-operable (role=button, tabindex, Enter/Space).
- <main> landmark + skip link; icon-only buttons get aria-labels; inputs relying
  on placeholder alone get aria-labels; AWS-ranking link is a real <button>;
  Login fields wrapped in a <form> so Enter submits from either field; toasts
  role=alert for errors (urgency not by color alone).

UX safety:
- 'Notify all' and reminder 'Send' now behind ConfirmDialogs naming the count/
  client; document action bar disables while an action is in flight (no
  double-submit dupes); SendDialog surfaces why a send is blocked instead of
  failing silently; client search + ticket search debounced (~300ms); inline
  Client-360 edit failures surface via toast, not an off-screen banner; LivePreview
  gets a real Retry; destructive client-status transitions confirm first.

Code quality:
- ErrorBoundary wraps the app (a render throw no longer white-screens).
- useData clears stale data on dep change (no record flashing under a new id).
- Shared ErrorBoundary/SecretField/Pager components extracted; Pager adopted in
  Documents; write-call api types tightened off Record<string,unknown>.

typecheck clean (root + both workspaces), web build clean, web tests 4/4, full
suite 388 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise c5480cf8cd feat(d22): P2 — onboarding checklist per project + Projects (Onboarding) board
Per client-module in the Modules tab: a MilestoneChecklist (tick with
date, + Add step), reloading in place. New Onboarding work page: Stats
header + per-milestone board (done/total, pending badge, warn/ok tint)
with click-through to the pending projects for each milestone (client
links to Client 360). Nav + /projects route. typecheck + web build clean,
web tests 4/4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 892ae9e96e feat(d21): P3 — module Fields editor + self-rendering service form
Modules page: owner FieldSpecEditor where a module defines its own fields
(label, snake_case key, type, select options, required, reorder) → saved via
patchModule({ fieldSpec }); staff read-only. Client 360 service card: below
the D20 login block, a dynamic form built from the owning module fieldSpec —
one typed input per field (text/number/date/boolean/select), required honored,
non-secret edits collected into one PUT; secret fields as ••••/Reveal/Copy +
Set/Update mirroring the portal-password pattern (managerial, audited).
Empty fieldSpec renders nothing extra. Additive — D20 UI intact. typecheck +
web build clean, web tests 4/4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise ea958ace40 fix(d20): ticket module suggestions read the live catalog, not a constant
A module created tomorrow appears in the ticket dialog with zero code
changes; the five APEX codes remain only as a loading fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 days ago
Thomas Joise 1d598e8a96 feat(d20): P3 — Tickets page, Client 360 tickets/branches, module service cards
Tickets: WORK page with whole-desk status chips + counts, Mine filter,
search, honest pager, per-row assign + status quick actions, Drop behind
confirm, New-ticket dialog (client type-ahead, branch select, module/kind
datalists). Client 360: Tickets tab (client-scoped, same actions) +
Branches section (add/rename/deactivate) + per-module ServiceDataCard
(provider/username, portal password ••••/Reveal/Copy managerial-only,
editable labeled details, remark). Also: /Apex/ git-ignored — real export
holds plaintext credentials, must never land in git.

Co-Authored-By: Claude Fable 5 <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>
5 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>
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