You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sims-hq/docs/superpowers/specs/2026-07-17-ideal-web-app-de...

7.9 KiB

"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/templateNavigate 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)