Goal
Land the missing-CTA layer the audit flagged — buyer-profile chip across every screen, capture modal, summary sheet, saved-units screen — and integrate it into TopBar.
Council Review (CONV-30) amendments apply — see
plans/sales-app-react-module-sequence.md§Council Review. Phase 4 amendments: A-BUYUX-2 (CRITICAL — server-sidelookupId+ magic link, no localStorage-only), A-SEC-1 (Clear-session UI affordance + Reset-demo + idle-clear), A-SEC-3 (email validation positive cases), A-RES-5 (POST = sync localStorage write THEN fire-and-forget with 3s timeout), A-SEQ-3 (re-verify scrim/contrast vs identified chip), A-SEQ-4 (replace UnitDetail stub), A-SALESUX-2 (OfferSummary artifact spec + PDF). State access uses Zustand selectors per A-SCALE-1.Per-module 4-step design loop (A-DEVUX-1): BuyerProfile chip + summary sheet are photo-light → real-page-stub. BuyerProfileCaptureModal is a density module → use
/wireframefor form-field hierarchy first. SavedUnits is photo-heavy.
Tasks
BuyerProfile chip + TopBar slot
- [ ] PAUSE: Brief Roman on BuyerProfile chip before coding — anonymous vs identified visual states, badge style for "(N saved)", over-panorama legibility.
- [ ]
src/features/buyer-profile/BuyerProfile.tsx— top-right chip rendered inside TopBar's right slot; two states: anonymous ("Save my session" outline button + user icon) vs identified (initials avatar + first name + "(N saved)" badge); click opens capture modal (anonymous, ONLY on 2nd+ save trigger or explicit "Save my session" click — A-BUYUX-1) or summary sheet (identified). Subscribe viauseBuyerProfile(s => ({ profile: s.profile, count: s.savedUnits.length })). - [ ]
src/features/buyer-profile/BuyerProfile.stories.tsx(anonymous + identified state stories) - [ ] Rewrite
src/features/buyer-profile/README.md - [ ] A-BUYUX-3 sticky price chip: when
?stage=unit&unit=…is active, TopBar surfaces a thin breadcrumb "B-1402 · 2BR · AED 2.6M · Sea". Read URL params + fixture to compose breadcrumb. - [ ] PAUSE: Live-iterate BuyerProfile chip with Roman in browser, including verification per A-SEQ-3 that chip is legible over 360° hero (z-index + scrim contrast).
- [ ] AU English check on touched files.
BuyerProfileCaptureModal (density)
- [ ] PAUSE:
/wireframe2-3 lo-fi variants — form-field hierarchy, all-visible vs progressive disclosure, optional/required treatment, A-SEC-2 consent checkbox + privacy link placement (CRITICAL for ADGM compliance — see Phase 5 CRM-CONTRACT.md spec). - [ ] PAUSE: Brief Roman + pick variant before coding.
- [ ]
src/features/buyer-profile/BuyerProfileCaptureModal.tsx— shadcn Dialog; controlled inputs (firstname required, lastname optional, email required validated per A-SEC-3 — HTML5type="email"+ presence +value.includes('@'); positive test cases:[email protected],[email protected],[email protected], phone optional); pre-fillsprefsfromuseBuyerProfile.getState().profile?.prefs; A-SEC-2 consent checkbox + privacy link required; on submit callsuseBuyerProfile.getState().update({ profile: ... })(Zustandpersisthandles localStorage write synchronously per A-RES-5); THEN fire-and-forget POST to/api/buyer-profile-stubwith 3sAbortControllertimeout — on any fetch error/non-OK status, swallow silently (console.warn only); modal closes, chip flips to identified. A-BUYUX-2 (CRITICAL): stub response includeslookupId: <uuid>andmagicLinkSent: boolean— storelookupIdin store + URL?profile=<lookupId>for cross-device restore. - [ ]
src/features/buyer-profile/BuyerProfileCaptureModal.stories.tsx - [ ] PAUSE: Live-iterate CaptureModal with Roman.
- [ ] AU English check on touched files.
BuyerProfileSummary (photo-light)
- [ ] PAUSE: Brief Roman on BuyerProfileSummary before coding.
- [ ]
src/features/buyer-profile/BuyerProfileSummary.tsx— shadcn Sheet; captured profile + edit pencil + saved-units list (resolved against fixture); "Generate offer summary →" CTA links to/projects/[slug]/saved. A-SEC-1 Clear-session destructive action: "Clear my session" button callsuseBuyerProfile.persist.clearStorage()and shows confirmation toast. - [ ]
src/features/buyer-profile/BuyerProfileSummary.stories.tsx - [ ] PAUSE: Live-iterate Summary with Roman.
- [ ] AU English check on touched files.
SavedUnits screen (photo-heavy)
- [ ] PAUSE: Brief Roman on SavedUnits before coding — group by status, card density, footer CTAs.
- [ ]
src/features/saved-units/SavedUnits.tsx— full-screen module rendered by/projects/[slug]/savedroute (Phase 5); subscribes viauseBuyerProfile(s => s.savedUnits); lists saved units grouped by status using<StatusPill>(A-HEALTH-4 — no parallel status-chip implementation); each row shows layout image + code + price; footer "Email me this offer" button (triggers OfferSummary per A-SALESUX-2) + "Continue browsing" link. - [ ]
src/features/saved-units/SavedUnits.stories.tsx+README.md - [ ] PAUSE: Live-iterate SavedUnits with Roman.
- [ ] AU English check on touched files.
OfferSummary artifact (A-SALESUX-2 — new module)
- [ ] PAUSE: Brief Roman on OfferSummary — print-stylesheet vs PDF render, what fields appear, broker-branding placement.
- [ ]
src/features/saved-units/OfferSummary.tsx— print-stylesheet/PDF-friendly layout (broker name + project header + per-unit snapshot price/payment-plan/status-as-of-date). Single-click "Email to buyer" + "Download PDF" + "Copy shareable link". TS type for the offer artifact lives insrc/types/index.tsand CRM-CONTRACT.md §b/§f. - [ ] PAUSE: Live-iterate OfferSummary with Roman.
- [ ] AU English check on touched files.
TopBar integration + cross-cutting
- [ ] Modify
src/features/top-bar/TopBar.tsx— add right-side chip slot. A-SEC-1 Reset-demo affordance: small operator action (visible whendemoMode: truein fixture) that callsuseBuyerProfile.persist.clearStorage()for sales-team use on shared devices. Auto-clear onbeforeunloador after N min idle whendemoMode: true. - [ ] Modify
src/features/top-bar/TopBar.stories.tsx— add anonymous + identified state stories. - [ ] A-SEQ-4 stub cleanup: delete
console.log + toaststub insrc/features/unit-detail/UnitDetail.tsx; verify in Storybook + grep that noconsole.logremains in the module. - [ ] Add presentation prefs slice — extend Zustand store with
currency: 'AED' | 'USD'andunits: 'sqft' | 'sqm'fields plussetPresentationPrefs(partial)action; consumed by TopBar + UnitDetail + FloorplateSelection + SavedUnits + OfferSummary. (Per A-SCALE-1 — sibling slice in same store, not separate Context.) - [ ] Vitest: BuyerProfileCaptureModal validation — empty firstname rejected; invalid email rejected; positive cases (
[email protected],[email protected],[email protected]) accepted. - [ ] Manual verification: capture profile in story → refresh → identified state restored from Zustand persist.
What's Next
Confirm Phase 3 (sales-app-react-selection) is complete. Then start with BuyerProfileCaptureModal (the form is the trickiest piece; UnitDetail's save button is currently stubbed pending this). Then BuyerProfile chip + topbar integration (so the chip is visible everywhere). Then BuyerProfileSummary and SavedUnits.
Key Context
- Plan:
plans/sales-app-react-module-sequence.mdPhase 4 - Phase 3 must be DONE; UnitDetail's "Save" stub gets replaced by a real call to open this modal
- BuyerProfile Context lives at
src/lib/buyer-profile-context.tsx(Phase 1) - localStorage key
oo:buyer-profile:v1(versioned for future migration) - No real form library — controlled inputs + tiny
useStatefor errors per the plan; do NOT add react-hook-form / conform without Roman approval - TopBar already has a right side; Phase 4 task 5 adds an explicit slot that BuyerProfile chip occupies (no breaking changes to existing TopBar callers — slot is opt-in)
- POST endpoint
/api/buyer-profile-stubis built in Phase 5 — for nowfetchto it and let it 404 quietly OR conditionally fetch only when aSTUB_APIenv var is set - Eval pack: plan §Phase 4 evaluation table (artefact = 4 storybook URLs + identified-chip screenshot; 2-min check = capture form → submit → chip identified → refresh → still identified)
Session Log
- CONV-30 (2026-05-09): Workstream created via
/planratification ofsales-app-react-module-sequence - CONV-30 (2026-05-11, buyer-journey track close): Plan integrated post-council (7 CRITICAL + 22 HIGH + 7 MEDIUM amendments inline), renamed from path2- slug to sales-app-react-, parallel brand-language-and-identity track established. State ratified, ready for /build (or /plan plans/brand-language-and-identity.md for the brand track).