- packages/ui/src/tokens.css: bound .wf-table-wrap to a viewport-derived max-height
and matching overflow-y so it's a real scroll container again. overflow-x: auto
alone left overflow-y at its computed "auto" (per the CSS overflow spec, a
visible/non-visible axis pairing promotes visible to auto) with no height bound,
so its scrollTop was permanently 0 and table.wf th's `position: sticky` never had
anything to stick against — the header just scrolled away with the page on every
table screen. Chose the bounded-wrapper fix (over deleting the dead sticky rule)
since it keeps the header pinned as advertised; short tables render unchanged,
long ones scroll inside the box exactly like the existing .dash-list pattern.
Also moved the .wf-table-wrap rule out of apps/hq-web/src/app.css into tokens.css
next to the other table.wf rules, since it's part of the @sims/ui component.
- apps/hq-web/src/pages/Dashboard.tsx: the "Onboarding stalled" card treated
"still loading" the same as "genuinely empty", flashing "Nothing stalled."
before the fetch resolved. Card now takes a loading prop and shows the usual
skeleton placeholder while pending; the positive empty copy and error state
are unchanged.
- apps/hq-web/src/pages/ClientDetail.tsx: getTicketTypes was fetched unconditionally
on every Client 360 load for the tickets tab's Type column. Gated it behind the
tickets tab being active (moved the tab computation earlier so it's available to
gate the loader); NewTicketDialog's own getTicketTypes call for the new-ticket
pre-fill is untouched.
- STATUS.md: prose under the verification table still said "The 416 figure..." while
the table itself says 528 — updated the prose to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pre-ship audit swarm findings on the Client Detail redesign:
- StatusLine's isPaymentStep now also matches the legacy advance_paid/
balance_paid keys (the generic fallback template's vocabulary on modules
with no seeded per-module tail — WHATSAPP, ATM, AMC, ...), so ticking the
payment step there opens the picker instead of silently stamping a date.
- Every milestone-tick path (plain toggle, payment link, credentials,
document link) now reloads the client-modules list so the module block
header, summary table and status select never go stale after a tick.
- A done payment/document milestone step now surfaces its link (linked
document -> /documents/<id>; linked payment -> date/amount from the
loaded ledger) instead of showing no evidence the link landed.
- Tickets: guard TICKET_STATUS_LABEL lookups against non-status chip
values (e.g. the synthetic "overdue" chip) that were throwing and
blanking the whole console.
- Dashboard: distinguish a failed "Onboarding stalled" fetch from a
genuine empty result instead of reporting "Nothing stalled." on error.
- Client 360 tickets tab: add the Type column, same rendering as the
Tickets desk.
- Drop the redundant read-only status badge next to the editable
ClientModuleStatusSelect in the module block header.
- Only mount NewTicketDialog while actually open, so it stops firing
getModules/getTicketTypes on every Client 360 load.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Red-team pass on the redesigned Client 360 / Tickets / Dashboard / Modules screens found
real overlap/clipping bugs, all fixed:
- Client header still broke at 768px (tablet): the button cluster only stacked below the
name under 640px, so at 768 the name got squeezed to ~38px and force-wrapped mid-word.
Bumped the stacking breakpoint to 860px.
- Per-module Documents rows and the Payments Outstanding-panel rows were plain nowrap flex
rows with no scroll; at 375px the amount/status badge silently clipped past the card
edge with no way to reach them. Both now wrap.
- Biggest one: every DataTable silently lost its rightmost columns at desktop width
(>=901px) — table.wf's display:block/table toggle assumed overflow-x:auto still worked
once display flipped to table, but Chromium computes overflow-x as visible on a
display:table box, so wide tables (Tickets' 11 columns) overflowed the viewport with no
scrollbar. Fixed at the root: DataTable now wraps its <table> in a .wf-table-wrap scroll
container instead of relying on the table element to scroll itself.
Verified against screenshots at 375/768/1440, light+dark, before and after; typecheck and
the full test suite (498 passed) stay clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The status-line payment picker only fired for advance_payment/balance_payment,
which no shipped template contains (the composed tails and the migration both
standardise on payment_received) — so payment linking was unreachable and
project_milestone.payment_id was never written by the UI. Keeps the legacy keys
for projects not yet migrated. Also refreshes the stale STATUS test count.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frontend half of two Client-Detail follow-ups, plus the StatusLine doc refresh.
Quotation step -> document link:
- Milestone gains documentId; setMilestone threads an optional trailing
documentId (contract: doneOn, paymentId, documentId).
- Ticking `quotation_sent` on the status line now opens DocumentPickerDialog
instead of stamping a bare date. It mirrors PaymentPickerDialog exactly —
same state shape in ClientDetail, same Dialog props — and lists the client's
QUOTATION/PROFORMA documents straight from the already-loaded ledger (no
extra fetch), with "Create new document…" handing off to the composer.
Ticket classification:
- Ticket gains `type`; getTicketTypes() reads the `ticket.types` setting
(config over code) with a code fallback only for the load window.
- New-ticket dialog gets a Type select; Tickets page gets Type filter chips
wired to the ?type= list param and a Type column.
- Every ticket's module cell deep-links to /clients/<id>?tab=modules, and each
Client 360 module block gets "Raise ticket for this module", which opens the
new-ticket dialog pre-filled with that client + module code.
Also refreshes the now-stale StatusLine JSDoc so it describes all three
hand-off dialogs — payment, credentials (account_creation) and document.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The outstanding panel never rendered a footer row — the rule has had no
matching markup since the panel landed. Removing it keeps app.css honest.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surfaces stalledProjects() on the Dashboard bento grid, listing active
projects parked past the stall threshold; each row jumps to the
client's Modules tab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds owner-only, audited routes for the two config settings milestoneTemplate
composes (project.milestone_template.front and project.milestone_template:<CODE>),
plus a Modules page section to edit them: staged label rows with add/remove/
reorder/Save, keys auto-derived from labels when blank, uniqueness enforced.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ticking the "Account creation & registration" onboarding step now opens a
small credentials dialog (mirrors the existing payment-step picker) instead
of a bare toggle. Saving reuses ServiceDataCard's exact patchClientModule
call and managerial password gate, then marks the milestone done; "Skip"
marks it done without touching credentials so the step never hard-blocks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- AssignModuleForm: remove SMS-forced username/password gate at assign time
(creds now captured later via module Access -> Edit); reword to "Add a
module" with a one-line hint on what assigning starts.
- Modules summary table: drop Installed/Completed/Trained columns (dates
live in the per-module status line); keep Module/Kind/Status/Billed/Settled.
- Each module's collapsible block now has an inline ClientModuleStatusSelect
next to its status badge, so status is editable without opening the table.
- Payments tab: remove the Outstanding panel's duplicate "Advance on
account" footer line -- the pre-existing StatCard is the single source.
- Remove RowDate, now unused after the summary-table trim.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
clientLedger now attaches allocations (docNo + amount) to each payment and
returns an outstanding[] list — issued invoices with outstandingPaise > 0
plus open (draft/sent/accepted) proformas, oldest-first by docNo. Read-only
extension over the existing settlement math (outstandingPaise, allocations);
no money/allocation logic changed. Mirrors the widened Payment/Ledger shapes
onto apps/hq-web/src/api.ts for the upcoming Payments tab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds client.category (SQLite migrate + PG migration 014), derived from the name to match
the official Kerala co-op classification: Service Bank / Urban Bank / Vanitha / Housing /
Employees / Agricultural / Marketing / Society (other) / Store / Other. Backfilled by a
portable CASE (most-specific-first); new/edited clients derive it via categoryFromName.
Clients screen gains a Category filter dropdown + column. Live backfill: 135 Service
Bank, 38 Employees, 15 Vanitha, 14 Store, 12 Agricultural, 11 Urban, … typecheck + full
suite (416) green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Captures every /auth/login attempt in a login_event table (SQLite schema + PG migration
013): staff_id (or null for an unknown user), username tried, outcome (success/failed/
locked), IP, and user-agent — best-effort, never breaks login. New owner 'Login history'
page (Admin): who signed in / failed, when, from which IP + parsed device, outcome filter,
paginated. login() now returns mustChangePassword; an invited user on their default
password is sent to Profile to change it on sign-in. typecheck + auth/employee tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds client.client_type (SQLite migrate + PG migration 012), backfilled from the name:
a name ending in STORE/STORES = 'Store', everything else = 'PACS' (14 Stores of 273).
New clients default the same way; editable per client. Clients screen gains an
All types / PACS / Store filter and a Type column. typecheck + client tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- staff_user gains must_change_password (SQLite migrate + PG migration 011): the
invited-employee indicator, cleared on the user's first self password-change.
- createEmployee takes phone/title/mustChangePassword; Employee carries mustChangePassword.
- scripts/import-employees.ts (tsx, Postgres- or SQLite-aware) seeds the 11 from
empmaster.xlsx: username = first-name slug, default password <username>@123, role staff,
active per ACTIVE_STAT, must-change flagged. Idempotent. Ran on live DB — 11 added.
- Employees list: 'Invited · must change pw' badge + a notice explaining the default
password pattern so the owner can share logins. Verified: adithya/adithya@123 → 200,
inactive raveena blocked. typecheck + employee tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clients is the most-used screen, so it moves from the 'Clients & billing' group to the
top Overview group, right after Dashboard. Documents/Renewals/SMS credits stay in
Clients & billing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SMS balances now lists ONLY live SMS subscriptions (status='live'); quoted/installing
ones are excluded (105 -> 79 real). The daily/manual gateway pull is likewise live-only.
- Removed the full-row red tint on low rows — the red balance badge alone signals it.
- Assigning the SMS module now REQUIRES the gateway username + password (both the Client
360 'Assign module' form and the Modules-page '+ Add client' panel): Assign is blocked
until they're filled, and they're set on the new subscription in the same flow. So a
live SMS client always has the credentials the balance pull needs.
Test updated (assign live); typecheck + suite green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The portal/gateway password could only be set via a separate 'Set…' button in the read
view — easy to miss, and absent from the Edit form (where you'd look for it). Add a
Password field to the Edit form (owner/manager) alongside Username: blank keeps the
current one, typing sets it. Rides the same audited PATCH the button used. So on a
client's SMS module you can now add/change the gateway password right where you edit
the login. typecheck + build clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On Client 360 → Modules, a client with many modules was one long flat stack of service
cards + checklists — hard to scroll and find, say, RTGS. Now each module is its own
collapsible section with a coloured accent header (module name + status + kind); all of
that module's data lives inside it. Click a header to collapse/expand. Headings stand
out so you can jump straight to the module you want.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
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>
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>
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>
- 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>
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>
- 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>
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>
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>
- 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>
- 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>
- 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>
- 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>
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>
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>
'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>
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>
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>
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>
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>
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>
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>
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>
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>