docs(spec): ideal-web-app pass — edit everywhere, dialogs, toasts, settings hub

Pinterest-chrome modals (Dialog/ConfirmDialog), Toaster, client editing (server
already supports it), send-by-email modal replacing window.prompt, /settings hub
with dated reminder-schedule rows + sharing default (two small additive backend
slices), favicon + per-route titles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 5 days ago
parent 02bc75d691
commit 7a9c6f9459

@ -0,0 +1,111 @@
# "Ideal Web App" Pass — Edit Everywhere, Dialogs, Toasts, Settings Hub
*Approved 2026-07-17 (Approach A). Fixes the six gaps that keep the console feeling like a
wireframe despite the D17 restyle: no record editing, browser `window.prompt/confirm`
popups, silent success, no Settings home, inline wireframe forms, no favicon. Visual
reference: Pinterest's product chrome — centered rounded modals with generous padding and
pill buttons, and a left-rail settings page.*
## 1. Goal & scope
Make every flow feel like a finished web application. Mostly UI; two **small additive
backend slices** (reminder-schedule endpoints, sharing default). No schema changes beyond
zero (settings ride the existing `setting` table; cadence changes are new dated
`reminder_schedule` rows per rule 3). `@sims/ui` stays generic.
**Key server fact:** `PATCH /clients/:id` already updates name / gstin / stateCode /
address / contacts / status / notes (`repos-clients.ts:85`) — client editing is pure UI.
`updateAmc`, `updateRecurringPlan`, `updateInteraction`, `patchModule`, `patchEmployee`
likewise exist and are under-used by the UI.
## 2. App-chrome kit (`packages/ui` — generic only)
| Piece | Contract |
|---|---|
| `Dialog` | Pinterest modal. Props: `open, onClose, title, size?: 'sm'\|'md'\|'lg'` (420/560/720px max-width), `footer?: ReactNode`, children = body (scrolls if tall). 16px radius, `--shadow-lg`, scrim (blur + dim) closes on click; Esc closes; **focus trap + focus restore** (same technique as `CommandPalette`); `role="dialog" aria-modal aria-label={title}`; ✕ button in the title row. Replaces the legacy `Modal` (currently zero call sites — delete it). |
| `ConfirmDialog` | On top of Dialog (`size='sm'`): `open, onClose, onConfirm, title, body?, confirmLabel?, tone?: 'danger'`. Confirm button is ink (or danger red); busy state while `onConfirm`'s promise resolves. Kills every `window.confirm`. |
| `Toaster` | `ToastProvider` (mounted once in `main.tsx`) + `useToast()``toast.ok(msg)`, `toast.err(msg)`. Bottom-right stack, max 3 visible, 4s auto-dismiss + ✕, `aria-live="polite"`, ok/err tones from tokens. Pure state logic in an exported reducer (`toast-store.ts`) so it's unit-testable without a DOM. |
| `FormGrid` | `display:grid; grid-template-columns: 1fr 1fr; gap 12px` (single column ≤560px); fields span-2 via a `wide` prop on a `FormField` wrapper (thin wrapper over existing `Field`). |
| `Button pill` | `pill?: boolean` prop → 999px radius (Pinterest button shape) — used in dialog footers only. |
CSS: new `wf-dialog*`, `wf-toast*`, `wf-formgrid` blocks in `tokens.css`; z-order — drawer
40 < modal-back 50 < palette 60 < toasts 70.
## 3. Edit everywhere (all modals; toast on success; ConfirmDialog on destructive)
| Surface | Modal content | Endpoint (exists) |
|---|---|---|
| Client 360 header → **Edit** | name · GSTIN · state · address · notes + **contacts repeater** (name/email/phone/role rows, add/remove, ≥0 rows) | `PATCH /clients/:id` |
| Clients → New client | same form, empty; `?new=1` opens it | `POST /clients` |
| AMC row → Edit / new | coverage, period, amount ₹, reminder days | `PATCH /amc/:id`, create |
| Plan row → Edit / new | module, cadence, amount, next run, policy | `PATCH /recurring/:id`, create |
| Interaction row → Edit / log | type, date, outcome, follow-up, notes | `PATCH /interactions/:id`, create |
| Employees add / edit / reset-pw | as today's inline forms | existing |
| Modules new / add-price | as today's inline forms | existing |
| Deactivate (AMC/plan/employee) | ConfirmDialog, danger tone | existing |
Quote-content editor stays inline (it has the live preview pane). Inline `Notice` remains
only for in-form validation; all mutation success/failure also lands a toast.
## 4. Send-by-email modal + document confirms
- **Send** (DocumentView): Dialog with **To**`<select>` of the client's contacts that
have emails + an "other…" free-text option — and **Subject** prefilled
`${docType} ${docNo}`. Gmail state inline: connected → send enabled; disconnected/dead →
disabled with the reconnect hint. Calls the existing `documentAction(id,'send',{to,subject})`.
- Convert-to-invoice-and-send, supersede, cancel, credit-note: ConfirmDialog with the real
consequence copy (number consumption, email dispatch). All six `window.*` popups die.
## 5. Settings hub — `/settings` (Pinterest left rail + one panel)
Owner-only except Appearance. Rail sections; active section in `?s=` (deep-linkable).
| Section | Panel | Backend |
|---|---|---|
| Company | profile form (GSTIN checksum + state code validated server-side) | `GET/PUT /settings/company` (exists) |
| Document template | today's `DocumentTemplate` editor moved in whole (form + live preview); old route `/settings/template``Navigate` to `/settings?s=template` | exists |
| Reminders | read-only table of `reminder_schedule` rows (rule kind · effective from/to · day offsets · subject) + **“New dated row” modal** (rule kind, effective_from, offsets CSV validated `3,7,14`-style, subject, body). Rows are never edited or deleted — a change is a new row (rule 3). Also two number fields: overdue days / renewal days. | **new** `GET /settings/reminders``{ overdueDays, renewalDays, schedules[] }`; `PUT /settings/reminders` → the two day settings; `POST /settings/schedules` → insert row. Owner-gated, audited, transactional. |
| Sharing | default share-link expiry days (blank = never) | **new** `GET/PUT /settings/sharing` → setting row `share.default_expiry_days`; `createShare` reads it as the default when the caller passes nothing (fallback 30 unchanged). |
| Integrations | Gmail: `/email/status` (connected/address/dead + reconnect hint). AWS: last month's ranking total via existing `GET /aws/ranking` (or "no data"). Read-only. | exists |
| Appearance | theme mode + accent swatches (moves the full picker here; the compact top-bar toggle stays) | none |
Nav: ADMIN group = **Settings** (owner-only; replaces the Document Template item) +
Employees. `Doc Template` breadcrumb/palette entries update accordingly.
## 6. Polish
- **Favicon**: teal rounded-square SVG with "HQ" mark (`apps/hq-web/public/favicon.svg` +
link in `index.html`); same mark reused as the login brand accent.
- **`document.title` per route**: `"<Page> · SiMS HQ"` set in `Layout` from the nav map
(record pages: doc no / client name once loaded).
- Errors from mutations surface as `toast.err`; page-level fetch failures keep
`ErrorState` (unchanged).
## 7. Explicitly not doing
Masonry/card re-skins of tables (Tables Rule); editing issued documents (immutability);
schedule-row edit/delete (dated rows are append-only); WhatsApp/send-channel changes;
password-protected shares; drawer pattern (modal chosen); i18n.
## 8. Testing & verification
- Kit: `toast-store` reducer + offsets-CSV validator get pure vitest tests; Dialog focus
logic reuses the proven palette pattern (no DOM env in repo — behavior verified e2e).
- Backend: `settings-reminders.test.ts` + `settings-sharing.test.ts` in `apps/hq/test`
(real-DB style like siblings): schedule insert appends + audits + never mutates, day
settings round-trip, `resolveSchedule` picks the new dated row from its effective date,
`createShare` honors the sharing default and the explicit param still wins.
- Gates: typecheck + full suite (293 + new) green; e2e walk — edit client (name + contact
add), send modal both Gmail states, one ConfirmDialog per document action, every settings
section round-trip, toasts, favicon/tab titles, dark mode + 420px pass.
## 9. Build order
1. Kit (Dialog/Confirm/Toaster/FormGrid/pill + CSS + toast tests)
2. Backend slices (reminders + sharing endpoints, tests)
3. Send modal + document ConfirmDialogs
4. Client edit + New-client modal
5. Remaining edit/create modals (AMC, plans, interactions, employees, modules)
6. Settings hub + nav/route changes
7. Favicon + titles + toast sweep
8. Verification pass (§8)
Loading…
Cancel
Save