Goal
Land the load-bearing scaffold (types, Riviera fixture, Pannellum wrapper, BuyerProfile React Context, format helpers) so every subsequent sales-app-react phase is pure composition on top.
Council Review (CONV-30) amendments apply — see
plans/sales-app-react-module-sequence.md§Council Review for full list. Phase 1 amendments include: A-SCALE-1 (CRITICAL — Roman directive: switch state primitive from Context to Zustand for cross-app reuse / multi-app future), A-RES-3/S-T4 (CRITICAL, persist middleware + version + Zod rehydrate validation), A-HEALTH-2 (full Panorama prop surface up front), A-DEMO-1 (loading slot), A-RES-1 (Storybook fallback = iframe story, not canvas polyfill), A-IMPL-2 (test convention + scripts), A-IMPL-3 (story title taxonomy), A-IMPL-4 (UnitStatus/Aspect as discriminated unions), A-HEALTH-5 (getProject helper + MissingProjectFallback), A-BRAND-4 (extract Wordmark.tsx swap point), A-HEALTH-1 (ESLint bare-string rule).Drift-check (A-DEVUX-5): this task list mirrors plan Phase 1 tasks 1-7 plus the amendments above.
Tasks
- [ ] A-DEVUX-2 prerequisite: Roman runs
/plan plans/brand-language-and-identity.mdin parallel with this workstream. Brand/planshould ratify before task 2 (fixture content review) below. - [ ] Extend
src/types/index.ts— addProject,Floor,Floorplate,Layout,Unit,PriceBand,Amenity,Architect,Developer,PaymentPlan,TourNode,Building(per plan §Phase 1 task 1 + A-BUYUX-4 building-filter scaling). Also exportUnitStatusandAspectas discriminated unions per A-IMPL-4. - [ ] Create
src/data/riviera.ts— populate oneProjectvalue with content from Roman's pasted Mered + Herzog & de Meuron source material (Al Reem Island, 468 apartments + ocean villas + sky villas, mother-of-pearl façade, bay-window typology, 4-pool/padel/spa amenities, 60/40 payment plan, Q1 2029 handover, 1BR from 2.1M / 2BR from 3M / 3BR from 5M AED). Generate ~30-unit subset, 4 floorplates (l2/l6 real + 2 stub-tagged), 2 layouts (east + north), 6 POIs againstriviera/360-aerial.jpg, tourNodes graph (only aerial active, interior nodes placeholder pending Roman's incoming files) - [ ] Create
src/lib/pannellum.tsx—'use client'<Panorama>wrapper, dynamic import insideuseEffect, hide default Pannellum UI via CSS, props:panoramaUrl,pois?,onPoiClick?,autoLoad?,compass? - [ ] Create
src/lib/pannellum.stories.tsx— Storybook story mounting Panorama againstriviera/360-aerial.jpg(proves wrapper works in isolation; surfaces bundler issues early) - [ ] Create
src/lib/buyer-profile-store.ts— Zustand store withpersistmiddleware (per CONV-30 amendment A-SCALE-1; replaces the original Context plan). Addzustand(~3kB) andzodto package.json deps. Store shape:{ profile: BuyerProfile | null, savedUnits: string[], mode: 'buyer' | 'broker-shortlist', update(partial), clear(), addSavedUnit(id), removeSavedUnit(id), setMode(mode) }. Persist config:{ name: 'oo:buyer-profile:v1', version: 1, partialize: (s) => ({ profile: s.profile, savedUnits: s.savedUnits, mode: s.mode }), migrate: (state, version) => state, onRehydrateStorage: () => (state, error) => { if (error || !rehydratedShapeIsValid(state)) { useBuyerProfile.persist.clearStorage(); } } }. Validate rehydrated shape with Zod schemaBuyerProfileV1SchemainsideonRehydrateStorage. HookuseBuyerProfile()returns selected slice (consumers pass selector for fine-grained subscription). RootLayout does NOT wrap in Provider (Zustand stores are global). - [ ] Create
src/lib/format.ts—formatAed(price, currency),formatArea(sqft, units)helpers - [ ] Verify:
pnpm exec tsc --noEmitpasses;pnpm storybookshows Pannellum + existing TopBar/InfoPanel stories without console errors
What's Next
Read os/plans/sales-app-react-module-sequence.md Phase 1 in full, then the codebase research findings in §Step 2. Read docs/next16-tailwind4-notes.md BEFORE writing any code (per AGENTS.md directive). Start with extending src/types/index.ts — that unblocks the Riviera fixture, which unblocks the Pannellum wrapper story.
Key Context
- Plan:
plans/sales-app-react-module-sequence.md(full plan, Phase 1 is ours) - Sales-app:
os/design-system/sales-app/ - Existing module pattern:
src/features/top-bar/andsrc/features/info-panel/are the conventions to mirror - Token system:
src/app/globals.css@theme inlineblock; per-project accent via[data-project="riviera"]CSS-var override - shadcn primitives in
src/components/ui/(Button, Sheet, Dialog, Input, Label, Separator, Badge, Tooltip, ToggleGroup, Toggle) - Pannellum 2.5.7 in
package.jsondeps - Source material for Riviera fixture: Roman's pasted Mered + H&dM marketing content (CONV-30 plan ratification turn)
- Real 360° asset:
public/imagery/imagery/riviera/360-aerial.jpg. Other imagery: 12 flat renders inriviera/, 4 floorplans infloorplans/ - Eval pack: see plan §Phase 1 evaluation table (artefact = Pannellum Storybook URL + git diff; 2-min check = drag panorama in Storybook + verify fixture has substantial real content)
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).