feat(pwa): rebuild /profile against DS primitives + view/edit toggle (#142) #169

Merged
james merged 1 commit from 142-profile-ds-rebuild into main 2026-06-20 03:35:48 +00:00
Owner

Closes #142.

Summary

  • /profile now opens read-only. A page-level Edit (secondary, pencil-led) reveals form controls; the header flips to ghost Cancel + primary Done. Done submits the basics form if dirty (re-using the existing PUT) and exits; Cancel reverts the form to the cached profile and exits.
  • Basics: view mode renders the mono-eyebrow + value pattern from the design (Name / Title statement / Brief). Empty fields fall back to an em-dash placeholder. Edit mode is Field + Input + Textarea.
  • Picture: view mode shows the Avatar (initials derived from the name) + the user's name + viewHint copy. Edit mode shows the avatar + Upload (secondary) + Remove (ghost) + the format/size hint. Upload is still one-shot — the upload itself is the commit, no buffer-on-Done.
  • Contacts: view mode renders Badge(kind) · value · label. Edit mode adds a pencil + trash IconButton pair per row; the pencil expands that row into an inline form (Select + value + label + Save/Cancel). A dashed add-row at the bottom of the list lets the user POST a new contact. Per-row mutations stay eager — Done is not a bulk-commit for contacts.
  • Carol DS token aliases throughout; no inline styles. New profile.module.css carries the layout, mono-eyebrow read rows, picture frame, and contact-row patterns.
  • A tiny forwardRef + useImperativeHandle bridge connects the page-level Done/Cancel to the BasicsSection's TanStack Form, keeping the form's complex typing internal to the component while letting the page drive submitIfDirty() / reset() imperatively.
  • New picture.uploadButton + picture.viewHint keys in messages/en.json; existing keys re-used.

Server flows untouched: /api/profile PUT, /api/profile/contacts POST/PATCH/DELETE, /api/profile/picture POST/DELETE all see identical request shapes. Correctness-over-optimistic discipline from #21 is preserved — every mutation still invalidates ["profile"] and waits.

Test plan

  • npm run lint / npx tsc --noEmit / npm test all clean (440 passed).
  • View mode parity in both themes (Picture/Basics/Contacts).
  • Edit mode parity in both themes; per-row pencil expands inline form; dashed add-row at bottom.
  • Inline-style grep of rendered HTML returns only style="color:transparent" from the sidebar <img> (Next.js Image default; not from this page).
  • Manual: Edit → modify basics → Done → values persist + view shows the new copy. Edit → modify basics → Cancel → values revert. Edit → click row pencil → modify → Save → row updates. Edit → click row trash → confirm → row deleted.

🤖 Generated with Claude Code

Closes #142. ## Summary - `/profile` now opens read-only. A page-level `Edit` (secondary, pencil-led) reveals form controls; the header flips to ghost `Cancel` + primary `Done`. Done submits the basics form if dirty (re-using the existing PUT) and exits; Cancel reverts the form to the cached profile and exits. - **Basics**: view mode renders the mono-eyebrow + value pattern from the design (Name / Title statement / Brief). Empty fields fall back to an em-dash placeholder. Edit mode is `Field` + `Input` + `Textarea`. - **Picture**: view mode shows the Avatar (initials derived from the name) + the user's name + `viewHint` copy. Edit mode shows the avatar + `Upload` (secondary) + `Remove` (ghost) + the format/size hint. Upload is still one-shot — the upload itself is the commit, no buffer-on-Done. - **Contacts**: view mode renders `Badge(kind) · value · label`. Edit mode adds a pencil + trash `IconButton` pair per row; the pencil expands that row into an inline form (Select + value + label + Save/Cancel). A dashed add-row at the bottom of the list lets the user POST a new contact. Per-row mutations stay eager — Done is not a bulk-commit for contacts. - Carol DS token aliases throughout; no inline styles. New `profile.module.css` carries the layout, mono-eyebrow read rows, picture frame, and contact-row patterns. - A tiny `forwardRef` + `useImperativeHandle` bridge connects the page-level Done/Cancel to the BasicsSection's TanStack Form, keeping the form's complex typing internal to the component while letting the page drive `submitIfDirty()` / `reset()` imperatively. - New `picture.uploadButton` + `picture.viewHint` keys in `messages/en.json`; existing keys re-used. Server flows untouched: `/api/profile` PUT, `/api/profile/contacts` POST/PATCH/DELETE, `/api/profile/picture` POST/DELETE all see identical request shapes. Correctness-over-optimistic discipline from #21 is preserved — every mutation still invalidates `["profile"]` and waits. ## Test plan - [x] `npm run lint` / `npx tsc --noEmit` / `npm test` all clean (440 passed). - [x] View mode parity in both themes (Picture/Basics/Contacts). - [x] Edit mode parity in both themes; per-row pencil expands inline form; dashed add-row at bottom. - [x] Inline-style grep of rendered HTML returns only `style="color:transparent"` from the sidebar `<img>` (Next.js Image default; not from this page). - [x] Manual: Edit → modify basics → Done → values persist + view shows the new copy. Edit → modify basics → Cancel → values revert. Edit → click row pencil → modify → Save → row updates. Edit → click row trash → confirm → row deleted. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(pwa): rebuild /profile against DS primitives + view/edit toggle (#142)
Some checks failed
Commits / Conventional Commits (pull_request) Successful in 12s
PR / OSV-Scanner (pull_request) Successful in 26s
PR / Package age policy (soft) (pull_request) Successful in 19s
PR / Static analysis (pull_request) Successful in 50s
Secrets / gitleaks (pull_request) Successful in 14s
PR / Trivy (image) (pull_request) Failing after 1m21s
PR / Build (pull_request) Successful in 4m1s
PR / npm audit (pull_request) Successful in 4m5s
PR / Test (sqlite) (pull_request) Successful in 4m20s
PR / Lint (pull_request) Successful in 4m35s
PR / Typecheck (pull_request) Successful in 4m37s
PR / Coverage (soft) (pull_request) Successful in 4m36s
PR / Test (postgres) (pull_request) Failing after 5m23s
724f3a7a88
The profile page now opens read-only with a page-level Edit button.
Edit swaps the header to Cancel / Done, reveals form controls in
Basics (Field + Input + Textarea), turns each contact row into a
read row + pencil/trash IconButton pair, and surfaces a dashed
add-row at the bottom of the contacts list. Cancel reverts the
basics form to the cached profile; Done submits the form if dirty
(re-using the existing PUT) and exits. Contacts keep their per-row
Save semantics — each row's inline edit fires its own PATCH so the
list stays correct even if Done is never pressed. Picture upload
is still one-shot (#21 pipeline unchanged).

The mono-eyebrow + value pattern from the design powers the Basics
read view (Name / Title statement / Brief); empty fields show an
em-dash placeholder so the read column never collapses. Contact
rows render Badge(kind) · value · label, matching the design.

A small forwardRef + useImperativeHandle bridge connects the
page-level Done/Cancel to the BasicsSection's TanStack Form —
keeping the form's typing internal to the component while letting
the page drive submit/reset imperatively.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Trivy (container image)

Threshold: high  ·  Total findings: 121  ·  At/above threshold: 1

critical high medium low
0 1 50 70
severity id package installed / range fix
high CVE-2026-12151 undici 6.25.0 6.27.0, 7.28.0, 8.5.0
<!-- scanner-comment: trivy --> ### Trivy (container image) **Threshold:** `high` &nbsp;·&nbsp; **Total findings:** 121 &nbsp;·&nbsp; **At/above threshold:** 1 | critical | high | medium | low | |---:|---:|---:|---:| | 0 | 1 | 50 | 70 | | severity | id | package | installed / range | fix | |---|---|---|---|---| | high | [CVE-2026-12151](https://avd.aquasec.com/nvd/cve-2026-12151) | undici | 6.25.0 | `6.27.0, 7.28.0, 8.5.0` |

📊 Test coverage

Patch coverage: no testable lines changed.

Overall (app/, lib/, db/, excluding UI per ADR-0019):

Metric Value Soft target
Lines 80.8% ≥ 50%
Branches 80.8% ≥ 75%
Functions 88.5% informational

Soft thresholds per ADR-0019. Coverage is informational and does not block merge.

<!-- coverage-comment --> ## 📊 Test coverage **Patch coverage:** no testable lines changed. **Overall** (`app/`, `lib/`, `db/`, excluding UI per ADR-0019): | Metric | Value | Soft target | |---|---|---| | Lines | 80.8% ✅ | ≥ 50% | | Branches | 80.8% ✅ | ≥ 75% | | Functions | 88.5% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
james merged commit ea6f724f8f into main 2026-06-20 03:35:48 +00:00
james deleted branch 142-profile-ds-rebuild 2026-06-20 03:35:48 +00:00
Sign in to join this conversation.
No description provided.