- 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>
Supersedes the flat per-module onboarding templates: milestoneTemplate(db, code)
now returns [...front, ...tail]. front is a shared setting
(project.milestone_template.front, FRONT_FALLBACK code default: enquiry ->
visit/meeting -> quotation sent -> quotation approved) prepended to every
module's checklist. tail resolves per-module setting -> global setting ->
TAIL_FALLBACK (the old generic list), unchanged in precedence.
- STEP_STATUS gains quotation_approved -> ordered (never-downgrade guard kept).
- seed.ts seeds the front once plus SMS/RTGS/MOBILEAPP tails (advance/balance
payment steps collapsed into a single payment_received tail step); no longer
seeds a bare project.milestone_template default.
- migrate-onboarding-templates.ts KEY_MAP updated so advance_paid, balance_paid,
advance_payment and balance_payment all carry forward to payment_received;
its idempotency check holds against the longer composed list.
- Updated seed-templates, milestones-templates, milestone-status,
migrate-onboarding-templates, milestones and milestone-payment tests to the
composed content/mappings (TDD: reverted the implementation, confirmed the
updated tests red, restored the implementation, confirmed green).
Backend + tests only; the live-DB migration run is a separate step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds stalledProjects(db, olderThanDays, today) to repos-milestones.ts:
active projects with a pending step whose last progress (MAX done_on,
or module creation if nothing ticked) is older than N days. GET
/projects/stalled reads ?days= with a project.stall_days setting
fallback (default 30).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Threads an optional `source` through DraftInput/insertDocRow (default 'hq',
preserving current behaviour when omitted), tags module renewals as
'module_renewal', and adds generateBulkSmsPurchaseQuote — an ad-hoc bulk SMS
credit top-up priced via the existing usage rate card, tagged
'bulk_sms_topup' so the client-detail document list can distinguish it from a
subscription renewal. New route: POST /client-modules/:id/bulk-sms-quote.
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>
Add one test case to cover the scenario where old milestone keys that
don't map to the new template (amc_start, setup_done for SMS) are ticked
and should be counted in the migration's dropped tally. Verifies both
dropped and carried counts are accurate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Idempotent migrateOnboardingTemplates(db): for every client_module whose module
has a per-module onboarding template (task 2), rebuilds its milestone rows from
that template, carrying mapped ticks (installed->installation, go_live->go_live,
advance_paid->advance_payment, balance_paid->balance_payment, setup_done->
setup_configuration when present) with done_on and payment_id intact. Unmapped
ticks (amc_start, or setup_done where the new template has no matching step) are
dropped and counted. Audited per project; a second run is a no-op via an
identical-key-set short-circuit. Adds the CLI wrapper (mirrors
apply-name-fixes.ts's open-DB idiom, Postgres or SQLite) for the real one-time
run against production data.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Milestone.paymentId + project_milestone.payment_id (SQLite + Postgres,
additive). setMilestone(..., paymentId?) resolves the payment's
received_on to stamp done_on when a payment-step tick links a real
payment; the linked payment must belong to the same client. api.ts
threads paymentId from the milestone POST body.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ticking the installation/training/go_live onboarding milestone steps now
advances the coarse client_module.status (installed/trained/live), audited
in the same transaction. A STATUS_RANK guard blocks downgrades from
unticking or ticking an earlier step after a later one already landed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Widens milestoneTemplate(db, moduleCode?) to resolve project.milestone_template:<CODE>
before the global project.milestone_template setting, falling back to FALLBACK_TEMPLATE.
ensureProjectMilestones now looks up the client_module's module code and resolves the
template through it, so each module can carry its own onboarding checklist as a dated
setting row instead of one hardcoded global list.
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>
Appends ' LTD NO. <n>' where the Kerala co-op directory lookup found a registration
number (found=true). Guarded: only found rows, skips names that already contain the
number (space/punct-insensitive), appends the number only (no name/casing overwrite),
audited via updateClient. Applied on live data after backup: 91 names updated (K-Z half).
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>
Reads the reviewed name-review.json and applies each changed name via updateClient
(audited, guarded: only updates when the stored name still matches what was reviewed).
Ran on live data after a backup: 187 names corrected (co-op style unified to
CO-OPERATIVE, run-on splits, ARP00KARA->ARPOOKARA, double spaces), 0 skipped.
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>
Founder: there is no Tecnostac in this application. Replaced the seed default
company.name ('Tecnostac' -> 'SiMS'), demo-seed data, ~22 test fixtures/emails, and
docs (Tecnostac -> SiMS, @tecnostac.com -> @sims.com). Live DB company.name also
updated to 'SiMS' (shows on dashboard, documents, letterhead, reminder emails). Owner
email was already SiMS (simssoftware13@gmail.com). Full suite 416 green.
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>
Owner has no HQ_SECRET_KEY, so the D31 unlock-with-key path was impossible. crypto is
now fully keyless: encrypt/decrypt ignore keyHex (params kept so a key can be wired back
later); decrypt returns '' for any leftover legacy AES value (unreadable → treated as
unset, never fed back as ciphertext). Key-based /admin/migrate-plaintext replaced by
keyless /admin/clear-legacy-credentials (clearLegacyCiphertext) which NULLs unreadable
pre-D31 values. .env.example marks HQ_SECRET_KEY removed.
Ran the cleanup on live data: 76 SMS + 110 client DB passwords were unrecoverable and
cleared (usernames intact) — must be re-populated in plaintext (APEX re-import / re-entry).
Also fixes the one real defect the D31 adversarial review confirmed: maybeRefreshSmsDaily
still had a 'keyHex==="" -> no-op' guard that would silently kill the DAILY auto-refresh
once the key was gone (manual refresh masked it). Removed; the daily pull now runs keyless
like the manual path. Full suite 416 green.
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>
Removes credential encryption at rest so the SMS-gateway logins (and other portal/DB
passwords, module secrets, Gmail token) are usable daily without a held-out
HQ_SECRET_KEY. Keystone: crypto.encrypt() stores tagged plaintext ('plain:'),
decrypt() untags keylessly and still reads legacy AES *with* the key to migrate it.
Drops the six 'refuses/cannot without HQ_SECRET_KEY' guards. Scope is the reversible
credential vault ONLY — login passwords stay one-way scrypt-hashed (@sims/auth).
Secrets still kept out of list payloads + audit rows; reveal still managerial+audited.
migrateLegacyCiphertext + POST /admin/migrate-plaintext (owner) rewrite existing AES
rows as plaintext using the key one final time (idempotent, one audit summary), after
which the key is never needed. SECURITY: a DB dump now exposes these in the clear.
Tests updated to the plaintext policy; new migrate-plaintext.test locks decrypt +
migration + idempotency. Full suite 416 green; typecheck clean.
Co-Authored-By: Claude Fable 5 <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>
Security review of 58c9799: (1) block loopback/link-local/private targets for the
admin-set sms.balance_api_url before sending client credentials (assertSafeGatewayUrl
— rejects 127/10/172.16-31/192.168/169.254/localhost/::1/.internal), so the setting
can't be pointed at cloud metadata or internal services; (2) redirect:'manual' so a
3xx can't carry credentials to another host; (3) error messages never interpolate the
credential-bearing URL. http:// transport kept — it is the vendor panel's only
interface and the operator supplied it. Tests cover the block list.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>