- 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>
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>
RecordHeader's action-button cluster crowded/overlapped the client name on
narrow phones, and DataTable (Tickets, etc.) ran wider than the viewport,
clipping columns with no way to reach them.
Add a <=640px media query: .wf-rec wraps so .wf-rec-actions drops to its
own full-width row below the avatar/name/meta; desktop layout is untouched
outside the query. Wrap DataTable's <table> in a new .wf-table-wrap div
that scrolls horizontally on overflow at that breakpoint (nowrap cells so
the table can exceed 100% instead of being squeezed illegibly), keeping
the page body itself from scrolling sideways.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Column headers are now sortable across every table in the app (one shared component).
Sort compares cell text — numeric columns by their leading number, else natural text.
Sorting preserves the original row index so onRowClick/rowTone keep pointing at the
right record; action columns (empty label) aren't sortable. asc → desc → off cycle.
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>
- 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>
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>
Red-team money findings:
- createCreditNote caps the CN to the invoice's remaining creditable value
(invoice payable − already-credited by non-cancelled CNs), read+insert in
one txn. Blocks a CN exceeding its invoice / unlimited CNs flipping it 'paid'.
- nextDocNo allocates via one atomic UPDATE ... RETURNING (row lock) instead of
read-then-bump — no duplicate/burned numbers under concurrent issue (Postgres).
- markStatus rejects accepted/lost on non-QUOTATION docs — an issued invoice can
no longer be hidden from overdue chasing via a quote-only transition.
- computeBill bounds qty magnitude (keeps fractional for weighed Store goods);
HQ buildLines additionally requires whole-unit integer qty (SMS/module seats).
- recordPayment rejects TDS greater than the cash received — no fabricated-TDS
'settlement' with no money.
money-guards test locks all four. Suite money paths green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
project_milestone: each client_module (a project) carries a checklist
seeded from the dated project.milestone_template setting (advance paid /
setup / installed / go-live / balance / AMC — owner-editable). Ticking
stamps the date; audited. ensureProjectMilestones is idempotent and
back-fills imported projects. Cross-project board (per-milestone pending
counts) + drill-down (who's pending go-live). Routes: GET/POST
/client-modules/:id/milestones, GET /projects/board, /projects/pending/:key.
assignModule seeds the checklist for new projects.
Also fixes a latent flaky-ordering bug: uuidv7() now bumps monotonically
within a process, so ORDER BY id is genuine creation order (the guarantee
pagination + the sync cursor already assume) — sub-millisecond ties no
longer fall back to the random tail. Suite 377 green, stable across runs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
- 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>
This repo keeps its 60-commit HQ history and becomes independent of the Store
product. Removed the Store apps (pos, store-server, backoffice), the Store-only
packages (config, scanning, search-core, printing — printing was already a dead
dependency in hq-web), and the Store planning docs.
Shared packages are forked and trimmed to exactly what HQ uses: domain keeps
ids/money/business-day/doc-series/gstin/documents; auth keeps pin; billing-engine
keeps compute/tax. Store-only tests for the removed modules were dropped, and
auth.test.ts was trimmed to its PIN blocks. Package renamed sims-hq; root
tsconfig/vitest paths narrowed to the four kept packages.
Verified independently: npm install + typecheck clean, 177 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Snapshot of the store-product workstream's in-progress work (apps/pos,
apps/backoffice, apps/store-server, packages): returns + credit notes, GST
returns exports, offline bill queue, rate-limiting, session policy, print
guard, batch tracking, and the SEC-C6/SEC-A5 auth fixes; plus docs/18 red-team
review. Committed to preserve work for the repo push. HQ console already
committed separately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
POS, back office, store-server, all packages, workspace configs, README/
BUILDING, and the WIP HQ-2 plan — everything except gitignored data
(client import staging, .env, local DBs).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>