Files
Parsons/docs/memory/session-log.md
Richie 826f888e87 Memory: session-log 2026-04-23c + registry catch-up; retire NearbyPackageCard
Session log entry for today (2026-04-23c) covers the 8-commit demo
update pass: MiniCard-based nearby-verified grid, package fixtures
cleanup (drop 'unknown' treatment) + 10 new verified packages, Vite
envDir fix for Google Maps, always-recommended Comparison route,
responsive PackageDetail CTAs, ComparisonTable tier-aware Unknown
rendering, and info-card un-stick.

Plus the pre-drafted 2026-04-23b session-log entry (extractions,
CompareBar pattern, PackageDetail toggle, basket persistence) and the
matching component-registry updates for those prior-session changes.

Registry today: NearbyPackageCard row removed (now orphaned and deleted
— no consumers after MiniCard swap). PackagesStep, PackageDetail, and
ComparisonTable rows updated for this session's changes.

Decisions-log D052–D056 live locally; decisions-log.md is gitignored so
those additions aren't in this commit. Flagging the gitignore mismatch
— the session log references decision IDs that won't resolve for anyone
pulling from the remote until that's sorted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:23:45 +10:00

140 KiB
Raw Blame History

Session log

This file tracks work completed across Claude Code sessions. Every agent MUST read this file before starting work and update it after completing work.

Earlier sessions (2026-03-24 through 2026-03-26d) archived in docs/memory/archive/sessions-through-2026-03-26.md

Format

Each entry follows this structure:

### Session [date] — [brief description]
**Agent(s):** [which agents were active]
**Work completed:**
- [bullet points of what was done]
**Decisions made:**
- [any design/architecture decisions with brief rationale]
**Open questions:**
- [anything unresolved that needs human input]
**Next steps:**
- [what should happen next]

Sessions

Agent(s): Claude Opus 4.7 (1M context)

Context: User walked through a series of demo-visible issues after reviewing https://parsons.tensordesign.com.au/arrangement/. Each landed as its own commit + deploy; total 8 commits.

Work completed:

  • Nearby-verified section on PackagesStep now surfaces providers, not packages (D052): the nearby-verified SecondaryList arm was rebuilt around MiniCard. Data shape renamed NearbyVerifiedPackageNearbyVerifiedProvider (id, name, imageUrl, location, startingPrice, rating?, reviewCount?); verified is implicit, not a data field, since the section is verified-only by definition. Callback onNearbyPackageClickonNearbyProviderClick, routing directly on provider id. Layout: 2-col CSS grid (repeat(2, 1fr)) on sm+, 1-col on xs; capped at 4 via NEARBY_VERIFIED_LIMIT. Heading changed "Similar packages from verified providers nearby" → "Similar packages from verified providers" (dropped "nearby"). Heading icon aligned to top line of multi-line text via alignItems: flex-start + mt: '3px' (FA icon convention). Demo fixture nearbyVerifiedSamples replaced by nearbyVerifiedProviders derived from the main providers fixture filtered to tier === 'verified' && imageUrl. NearbyPackageCard molecule is orphaned and retired in the same pass (registry row + folder deleted).
  • Packages fixtures cleanup + 10 new verified packages:
    • Dashes (D053): removed all 10 fixture entries with treatment: 'unknown' from Optionals. Narrowed Optional['treatment'] from IncludedTreatment | 'unknown' to IncludedTreatment. Simplified optionalsForStep / optionalsForComparison helpers — the 'unknown' branches are gone. PackageDetail no longer renders bare em-dash rows; ComparisonTable's lookupValue already returned unavailable (→ "Not Included") for missing items via the section cross-join.
    • New packages: distribution randomised, max 5 per verified provider. Parsons 3 → 5 (+ Traditional Burial, Memorial Service), Rankins 2 → 3 (+ Direct Cremation), Killick 2 → 3 (+ Traditional Burial), Mackay 1 → 4 (+ Premium, Simple Cremation, Memorial Service), Mannings 1 → 4 (+ Premium, Simple Cremation, Direct Cremation). Each new package follows the canonical-Essentials rule (same 9 line items, per-package prices/treatments). Mackay + Mannings comparison maps rewritten from the single-package .map(pkg => ({...})) shape to the indexed-array pattern used by parsons/rankins/killick. Their packagesByProvider entries now slice(0,1)/slice(1) so "Other packages from this provider" renders.
  • PackagesStep selection lookup bug fix — selectedPackage was derived only from the primary packages array, so clicking a package in "Other packages from X" updated selectedPackageId but the detail panel stayed empty. Added a fallback to the same-provider-more secondary list.
  • Vite demo config: envDir = __dirname (D056). The demo's Google Maps API key was never in the production bundle. vite.demo.config.ts sets root: src/demo/apps/<slice>/, and Vite's default envDir is that root — no env files there, so .env.local at the repo root was silently ignored and VITE_GOOGLE_MAPS_API_KEY came out undefined. ProviderMap fell through to its "no API key" empty state on prod. Adding envDir: __dirname fixes it for every current and future slice.
  • Comparison route: always include a recommended column (D055). Demo's Comparison.tsx resolves a static DEMO_RECOMMENDED_KEY = 'parsons:deluxe' and passes it as recommendedPackage. Dedupes against basket so if the user has Parsons Deluxe selected it only appears once (as the recommended column). Basket mechanics unchanged — useComparisonBasket still caps user selections at 3; the recommended is layered as an editorial suggestion. Empty state demoted to defensive (now only fires when both packages AND recommendedPackage miss).
  • PackageDetail CTAs side-by-side on mobile. Buttons stacked on xs because of flexDirection: { xs: 'column', sm: 'row' } + size="large" — labels couldn't fit a ~360px column side by side. Fixed flexDirection: 'row' for all viewports and size: isMobile ? 'medium' : 'large' (40px/48px). useMediaQuery(theme.breakpoints.down('sm')) drives the switch.
  • ComparisonTable: unverified providers show "Unknown" (D054). lookupValue now checks pkg.provider.verified — unverified missing cells return { type: 'unknown' } (existing "Unknown" + info-icon render), verified missing cells keep { type: 'unavailable' } (→ "Not Included" in Optionals/Extras; em-dash in Essentials as the safety net — canonical-essentials rule means that path shouldn't fire in practice). Dropped the "* Some providers have not provided an itemised pricing breakdown. Their items are shown as '—' above." footer and the now-unused Z_HEADER_ROW constant.
  • ComparisonTable info card un-stuck. The top-left "Package Comparison" info card had position: sticky, left: 0 which pinned it over the leftmost package column (the recommended one since D040) on horizontal scroll. Dropped the sticky positioning — info card scrolls with the package headers; row-label column below stays sticky on its own (those labels genuinely help orient the reader).

Decisions made: D052, D053, D054, D055, D056.

Verified visually (user eyes, Playwright not used):

  • MiniCard grid renders 2-col desktop / 1-col mobile, max 4; icon aligns with heading top line on mobile.
  • New packages visible in each provider's "Other packages from X" section; "Other from Rankins → Premium" selection now surfaces PackageDetail.
  • Dashes gone from verified-provider PackageDetail Optionals.
  • Google Maps loads on prod (grep of built bundle confirmed AIzaSy key baked in).
  • ComparisonPage shows Parsons Deluxe as leftmost recommended column with the filled-brand + star badge on every visit.
  • CTAs side-by-side on mobile at medium size.
  • Botanical (tier2) comparison cells render "Unknown" — no more em-dashes except the canonical-Essentials safety-net path (which verified providers never trigger in practice).
  • Info card scrolls away on horizontal scroll; recommended card fully visible.

Preflight + deploy: Each commit preflighted (TS + ESLint + Prettier clean; Storybook built once) and deployed via npm run demo:publish to https://parsons.tensordesign.com.au/arrangement/. Hardcoded-hex warnings all pre-existing in HomePage / FuneralFinderV2-4 / PackageDetail — none introduced this session.

Open questions:

  • None.

Next steps:

  • Push commits to backup + fa-dev git remotes at git.tensordesign.com.au.

Session 2026-04-23b — Extractions, CompareBar pattern, PackageDetail toggle, basket persistence

Agent(s): Claude Opus 4.7 (1M context)

Context: Continuation of 2026-04-23 work. User asked for a polish + cleanup pass on the comparison feature and its surrounding components, ending with a deploy. Several discrete changes delivered across component + demo layers.

Work completed:

  • Three molecule extractions (D048) from ProvidersStep, each with its own barrel export + Storybook stories:
    • LocationSearchInput (src/components/molecules/LocationSearchInput/) — committed-chip location search. Owns the subtle endAdornment absolute-anchor CSS so future Autocomplete-with-custom-endAdornment users don't rediscover it.
    • HelpBar (src/components/molecules/HelpBar/) — promoted from a WizardLayout-internal component so WizardLayout and ProvidersStep's mobile-map branch share one source of truth (prevents the phone/styling drift that had already crept in).
    • SortMenu (src/components/molecules/SortMenu/) — trigger Button + anchored Menu with variant: 'compact' \| 'verbose'. Intended for VenueStep / CoffinsStep reuse; today only ProvidersStep consumes it. ProvidersStep shed ~240 lines net (1087 → ~850) and now reads as a composition of small molecules.
  • CompareBar:
    • Sized up: Badge large (32px), body1 text, Button medium (40px) on desktop; responsive size-down on xs.
    • Compare icon removed — label alone at the larger size.
    • Horizontal centering fixed — was 171px off-centre on desktop because Slide's internal transform was clobbering translateX(-50%). Switched to left:0; right:0; mx:auto; width:fit-content which Slide doesn't fight.
    • bottom bumped from theme.spacing(9) to theme.spacing(16) — the FA theme uses a 4px spacing base, so 9 was only 36px (overlapping the ~40px HelpBar). 16 = 64px with clean 25px gap.
    • Mobile collapse with auto-peek on add (D049): single-Paper width-animation approach. Right-anchored on xs, middle content (status + CTA) animates to max-width: 0 while the pill's right edge stays pinned — visually retracts to the corner as one unit. Grey-filled chevron swaps between right (collapse) and left (expand) in place. Auto-peek: new add while collapsed → bar peeks back in for 3s. Collapsed badge shows just count (not count/3) with pinned min-width for circular look.
    • z-index dropped from snackbar (1400) to drawer (1200). MapProviderDrawer raised from 3 to modal (1300). Drawer now visually covers CompareBar on mobile map view when a pin/cluster is active.
  • PackageDetail inCart toggle (D050): new inCart?: boolean prop. Both states share the same soft/secondary chrome + "Compare" label; added state adds a trailing CheckRoundedIcon. Click is a toggle (caller wires to basket.toggle(key)). Two earlier patterns were implemented then rejected by the user: an inert brand-tinted pill (too much space) and an "Added" label swap (cognitive overhead). aria-pressed + aria-label carry state for SR. Threaded via PackagesStep.isSelectedPackageInCartPackages.tsx computes basket.has(key).
  • Basket URL-sync sticky (D051): useBasketUrlSync pre-fix silently wiped the basket on back-navigation from /providers/:id/packages?compare=foo to / because the new empty ?compare= was treated as a "set to empty" signal. Fix: when in-app navigation drops the param but the store still has items, re-attach the store's keys to the new URL instead of wiping. Shared links + manual URL edits still hydrate the store. Demo-only change.
  • Minor polish:
    • MapProviderDrawer cluster rows: verified-icon alignment fix + 1.25 lineHeight on name + px:2 header padding aligns heading with row content (was px:1.5).
    • Deployed checkpoint mid-session (commit 6434d113) and end-of-session.

Decisions made: D048, D049, D050, D051.

Verified visually (Playwright):

  • Basket persists across provider → map → provider → map (URL carries ?compare=... through every route).
  • Mobile CompareBar collapses/expands as one unit; collapsed badge is circular; grey chevron visible; auto-peek fires on add; HelpBar overlap gone.
  • Drawer z-index: drawer covers CompareBar when active; CompareBar reappears when drawer dismissed.
  • PackageDetail toggle: chrome identical between default and added states; trailing tick on right when inCart.

Preflight + deploy (early checkpoint): all critical checks pass (TS, Storybook build, token sync, ESLint, Prettier). Deployed to https://parsons.tensordesign.com.au/arrangement/.

Open questions:

  • None blocking. If slide/peek on mobile feels wrong after user testing, the pattern is one git revert away.

Next steps:

  • Memory / registry catch-up pass (in progress at session end), then a second deploy.

Session 2026-04-23 — ProvidersStep polish + mobile map-first layout + deploy

Agent(s): Claude Opus 4.7 (1M context)

Work completed:

  • ProvidersStep desktop polish (Track 1 of the 2026-04-22 handoff):
    • Sort button now reads Sort: <value> (was a bare "Recommended" indistinguishable from a filter); price sort labels cleaned of their internal colons to avoid double-colon rendering.
    • Results count bolds the number in primary text.
    • viewMode toggle on the map panel kept as the mobile affordance (desktop toggle still visible but unchanged per user).
  • Sticky search — committed-chip pattern (D046): replaced the raw TextField with an Autocomplete multiple + freeSolo capped at 1 location. Typing produces a draft; Enter or the right-hand primary search-icon button commits to an FA Chip (grey/neutral), tap X to clear. Icon spacing tightened, focus ring stripped on the search + Filters + Sort by controls per user call.
  • Mobile map-first layout (D045): on xs + viewMode=map, ProvidersStep branches to a custom layout: nav + full-bleed map + floating control card (search + Filters + Sort by + List|Map toggle) + bottom drawer + help bar. h1 + subhead dropped on mobile map view to save vertical space; remain on desktop and mobile list. Drawer slides up on pin/cluster tap, slides down on close X. Single-pin drawer renders a ProviderCard edge-to-edge (entire card clickable → onSelectProvider); cluster drawer renders an inline list of verified-slot + name + location + rating + "From $X" rows, tap a row to drill in (pan+zoom + swap drawer content to the single-pin card). Close X lives in a 40px header strip so it never overlaps the Verified badge.
  • ProviderMap externalisable popups (D047): opt-in externalisePopups prop + onActiveChange callback + imperative ProviderMapHandle (clearActive, drillIntoProvider). Desktop behaviour unchanged when these aren't used; mobile drawer consumes them. forwardRef type changed from HTMLDivElement to ProviderMapHandle; no existing callsite passes a DOM ref so safe.
  • MapPin verified icon: inline Material Verified (outlined) SVG on the left of the name for verified providers. Inline SVG, not @mui/icons-material, because MapPin is mounted via createRoot outside the ThemeProvider. Max label width bumped 180→210px.
  • Mobile cluster drawer rows: verified icon now aligns with the name's top line (matches desktop ClusterPopup fix from D043 refinement) + new right-aligned "From $X" price column.
  • Controls unified across mobile views: mobile list-view sticky controls match the map-view floating chips — white fill, neutral-300 border, shadow-sm, 32px height, 14px/600 text. Mobile sort label switches from "Sort: " to compact "Sort by" (desktop keeps verbose label). Desktop map-panel floating toggle resized to match Filters/Sort button height + type.
  • Filter dialog cleanup (desktop + mobile): Location field removed (the sticky search is primary); Funeral-type chips bumped small → medium; Reset filters button always renders (disabled when no filters active); provider-feature switches (Verified only, Online arrangements) align to the first text line.
  • PackagesStep mobile drill-in bug fix: added a local hasDrilledIn flag so the mobile layout only swaps to the detail view after an explicit user tap on a package. Previously the demo route seeding selectedPackageId to the first matching package (for desktop auto-display) also forced mobile into the detail view — users arriving from the map drawer saw a single package instead of the list. Back/forward from the detail resets the flag.
  • Drawer close animation: drawerOpen now excludes the exiting phase so tapping the close X slides the drawer down immediately instead of lingering with an opacity fade-back-in (bug was: transform and opacity transitions interleaving wrong).
  • Whitelist localhost for the Google Maps API key (user added http://localhost:5180/* and http://localhost:6006/* to HTTP referrer restrictions), unblocking local map/drawer end-to-end testing.
  • Preflight + deploy: all checks pass (TS, Storybook build, token sync, ESLint, Prettier). Hex-colour warnings all pre-existing in HomePage. Deployed to https://parsons.tensordesign.com.au/arrangement/.

Decisions made: D045, D046, D047 (see decisions-log).

Open questions:

  • User flagged "still more edits to be done" at checkpoint — pending list will come in the next session.

Next steps:

  • Pick up the user's remaining ProvidersStep polish items.
  • Process the 00-Inbox/Mobile list-mode layout for ProvidersStep + ProviderMap.md note (now implemented — can be archived when the user runs /process-inbox).

Session 2026-04-22 — Session-end handoff

Status at end of session: Map work reached a stable checkpoint. User asked to resume in a new session rather than continue accumulating context. All work committed.

What's next (for the next session to pick up):

  1. ProvidersStep component polish — user wants iteration on the step itself and some of its constituent components (likely SearchBar, filter chips, FilterPanel, ProviderCard layout, sort/view-mode controls). No specific issues flagged yet — a fresh design review pass will surface them.
  2. Mobile layout for ProvidersStep + ProviderMap. The existing list-map WizardLayout hides the right (map) panel entirely on mobile via display: { xs: 'none', md: 'flex' }. The viewMode: 'list' | 'map' toggle is wired in state but doesn't yet control panel visibility on small screens. Two possible approaches to discuss: (a) wire viewMode to swap panels on mobile (list OR map, full-bleed), or (b) a bottom-sheet overlay pattern where the map is full-bleed and the list is a drawer. Worth a design conversation before building.
  3. Demo deploy (/preflight + npm run demo:publish) — held until the above lands and the user is ready to share.

Deferred (previously flagged, still open): animated morph between pin and popup landed this session, so it's off the list; the only remaining map follow-up is mobile — folded into item 2 above.


Session 2026-04-21c — ClusterMarker + ClusterPopup + clustering in ProviderMap

Agent(s): Claude Opus 4.7 (1M context)

Context: User iterated on clustering in Figma Make (reference: https://www.figma.com/make/704nCLj7uFqIQzBmAA21ql/Funeral-Provider-Finder-Map). Picked the "list-popup" approach over zoom-in: clicking a cluster morphs it into a scrollable list of providers rather than zooming the map. Directive: style pin + popup to match FA design system, use a version of existing cards in the list rows, impose rules on when clustering activates.

Decisions made (D043):

  • Library: @googlemaps/markerclusterer (15KB gz, MIT, official Google team). GridAlgorithm for screen-pixel-based clustering — not geographic distance (pixel distance adapts to zoom automatically).
  • Rules: gridSize: 70 pixels, maxZoom: 13 — past zoom 13 every pin shows individually no matter how close, so tight geographic clusters only form when the user's actually zoomed out.
  • UI model: cluster click → ClusterPopup list at centroid (no zoom-in). Row click → drill into MapPopup with back chevron to return to list. Map click → revert all.
  • Row layout: inlined 48px-thumbnail row inside ClusterPopup, not ProviderCardCompact. That molecule's 120160px thumbnail left only ~120px for the name column at 320px popup width; long names overlapped the image. The inlined 48px layout fits 5+ rows without scrolling.
  • Marker rendering change: markers now rendered imperatively (React createRoot into AdvancedMarkerElement.content divs) because markerclusterer requires imperative marker instances. Popup layer (MapPopup, ClusterPopup) stays declarative via vis.gl's <AdvancedMarker> JSX.

Work completed:

  • npm install --legacy-peer-deps @googlemaps/markerclusterer.
  • New atom src/components/atoms/ClusterMarker/ — 36px circular count badge, sibling palette to MapPin (hasVerified → brand-700 promoted, else neutral-100). Same nub + shadow. 5 stories.
  • New molecule src/components/molecules/ClusterPopup/ — scrollable list popup with header bar ("N providers in this area" + close X), verified-first sort, 320px wide, matches MapPopup's nub. Internal ProviderRow sub-component: 48px thumbnail (or location-icon fallback), name + location + rating + verified check. 5 stories.
  • src/components/molecules/MapPopup/MapPopup.tsx — added onBack?: () => void prop. When provided, renders a small chevron IconButton in the top-left of the card (absolute-positioned on Paper which now has position: relative). Used by ProviderMap when a MapPopup was drilled into from a cluster list, so the user can return to the list.
  • src/components/organisms/ProviderMap/ProviderMap.tsxfull refactor:
    • Imperative markers via useMapsLibrary('marker') + createRoot mounting <MapPin /> into each marker's content div. Roots unmounted on cleanup.
    • MarkerClusterer wraps the markers with GridAlgorithm({ gridSize: 70, maxZoom: 13 }). Custom renderer creates cluster markers with <ClusterMarker /> content.
    • State machine: activeProviderId, activeCluster: { providers, position }. When cluster is open (no drilled provider), ClusterPopup renders at activeCluster.position. When drilled (both set), MapPopup renders with onBack chevron. Map click clears both.
    • hiddenIds set: providers whose popup is currently showing (or whose cluster's popup is showing) get filtered out of the marker layer. Rebuilds markers on change.
    • Callbacks stashed in refs to avoid marker rebuild on parent re-render.
    • Vis.gl Map import renamed to GoogleMap to avoid collision with JS Map constructor.
  • Registry: MapPin row unchanged; new rows for ClusterMarker (atom) and ClusterPopup (molecule); ProviderMap row rewritten to describe clustering + drill-in state machine.
  • Decisions log: D043 added.

Preflight: TS clean, ESLint clean. Verified in Storybook + via Playwright screenshot — cluster of 4 east-coast providers collapses on initial zoom (NSWQLD view), clicking opens ClusterPopup with verified providers sorted to top, layout clean at 320px.

Open questions:

  • None blocking. Drill-in back navigation not explicitly tested via Playwright but code path is straightforward.

Next steps:

  • User visual review of clustering in Storybook.
  • Then /preflight + npm run demo:publish to push to parsons.tensordesign.com.au.
  • Future: mobile map sheet (still deferred), possibly animated morph (framer-motion layoutId) if the simple swap feels abrupt in use.

Post-review tweaks (same session):

  • Font regression fix(t: Theme) => t.typography.fontFamily accessors in MapPin + ClusterMarker don't work when the component is rendered via createRoot into an imperative marker content div (no ThemeProvider in that disconnected tree → MUI default Roboto). Swapped to fontFamily: 'var(--fa-font-family-body)' — CSS vars are global and propagate regardless of React tree. Lesson: any component that might be mounted via createRoot must avoid MUI theme callback accessors for fonts/colours; use CSS vars instead.

  • Unverified avatar (reverted — see D044) — briefly added 48×48 initials fallback for providers without photos; removed when the whole image column was dropped.

  • Verified icon position — moved from right of the provider name to left (before the name in the flex row). Matches list-convention for "tier indicator then content."

  • Popup z-index — active popup AdvancedMarkers bumped from zIndex={100} to zIndex={1000} to cleanly beat any lat-based stacking, so open popups always sit on top of other markers.

  • Image-free cluster rows + pan+zoom drill-in (D044) — after review, the mixed thumbnail / initials-avatar treatment felt fragmented. Dropped the image column entirely in ClusterPopup rows; row layout is now a fixed verified-icon slot (so titles align across tiers) + name + location/rating. Drilling into a provider now pans and zooms the map to their coords at zoom 15 (past CLUSTER_MAX_ZOOM, so cluster members break apart into nearby pins around the selected one) and opens their MapPopup. Cluster state is cleared on drill-in — the back chevron and onBack prop on MapPopup are removed (zoom-out-to-reform-cluster is the natural backwards flow). New MapRefCapture internal component in ProviderMap uses useMap() to stash the map instance in a ref so handleDrillIntoProvider (outside the Map context) can call panTo/setZoom.

  • ClusterPopup polish (2026-04-22) — three refinements: (a) verified icon now aligns with the provider name's top line (outer row switched to alignItems: flex-start, icon slot given height: 1.25em so it sits on the name's line-box instead of the row's vertical centre); (b) per-row price added — startingPrice and priceLabel extended onto the ClusterPopupProvider interface, right-aligned "From $X" column with copper colouring for verified and text.primary for unverified; (c) smooth open/close transitions — new optional exiting prop on MapPopup and ClusterPopup drives an opacity+scale CSS transition (180ms, transformOrigin: bottom center so the pin-point stays put). ProviderMap now routes map-clicks and cluster-close through a shared closeWithExit handler that sets exiting=true, waits 180ms, then actually clears state. Ref-backed timer + cancelExit guard handles rapid-click races. Matching 180ms opacity fade-in added to MapPin and ClusterMarker atoms via @keyframes so pins reappear smoothly instead of snapping in when a popup unmounts.

  • Click-leak bugs (late fix same day) — two user-reported issues traced to the same root cause: DOM clicks inside popups + cluster marker clicks were bubbling to Map.onClick, which cleared our state the same frame we set it.

    • Symptom 1: clicking a row in the cluster popup zoomed the map but didn't open the MapPopup; user had to click the pin again.
    • Symptom 2: clicking a cluster while a provider popup was open didn't close the provider popup (the provider's state survived because handleClusterClick and handleMapClick ran in sequence with handleMapClick winning last).
    • Fixes applied: (a) Switched cluster clicks to MarkerClusterer's native onClusterClick option — this both overrides the library's default "zoom to fit cluster" behaviour and is the proper hook to stop the event. Defensively tries event.stop(), event.stopPropagation(), and event.domEvent?.stopPropagation() because the event shape passed by markerclusterer v2.6 is not quite the typed google.maps.MapMouseEvent (missing .stop()). (b) Added marker.addListener('click', event => { event.stop(); ... }) on each pin marker to stop propagation at the Google Maps event level (in addition to the existing DOM stopPropagation from MapPin's onClick, which stays for keyboard users). (c) Added DOM-level stopPropagation on all interactive elements inside ClusterPopup (rows, close button, and a catch-all on the root Box for empty-area clicks) and wrapped MapPopup's onClick handler so it stops propagation too — the molecule now assumes it's rendered inside a map context. Lesson: any React content rendered inside a Google Maps AdvancedMarker.content div that sits over a map with its own onClick must stop both the DOM click (for React-handled elements) and the Google Maps click event (for marker-attached listeners) — Google's 'click' event is separate from DOM bubbling.

Session 2026-04-21b — MapPin simplification + ProviderMap morph behaviour

Agent(s): Claude Opus 4.7 (1M context)

Context: User reviewed the freshly-built ProviderMap in Storybook and requested interaction + visual changes.

Decisions made (D042):

  • Morph over overlay — clicking a pin replaces it with a MapPopup at the same coord (single marker per location). Map-click reverts to pin. POPUP_LIFT_PX transform removed.
  • Verified pins promoted to the former verified-active palette (brand-700 bg, white text, brand-200 price). Unverified unchanged.
  • Variants dropped — MapPin name-only / price-only / active variants removed. name required; active prop gone. Selection handled at the organism level.
  • No consolidation of MapPin + MapPopup into one component — organism-level swap preserves standalone reusability and keeps atoms lightweight.

Work completed:

  • MapPin.tsx — rewritten palette constants (5 entries per tier, no active-*); active prop removed; name now required; JSDoc updated; a11y aria-label simplified.
  • MapPin.stories.tsx — 9 stories → 5 (Verified, Unverified, CustomPriceLabel, LongName, MapSimulation).
  • MapPopup.stories.tsx — one usage of <MapPin ... active /> updated to drop the removed prop.
  • ProviderMap.tsxPinMarker + PopupMarker now mutually exclusive per provider via activeId = activeMarkerId ?? selectedProviderId. PopupMarker no longer transforms; it renders at the pin's coord. POPUP_LIFT_PX constant removed. active wiring to MapPin removed.
  • Registry: MapPin and ProviderMap rows rewritten to match new behaviour.
  • Decisions log: D042 added.

Preflight: TS clean, ESLint clean. Storybook HMR picked up the changes cleanly.

Open questions:

  • User previewed in Storybook; next iteration will cover clustering/stacking behaviour when zoomed out.

Next steps:

  • User visual review of morph + new palette.
  • Then /preflight + npm run demo:publish to push to parsons.tensordesign.com.au.
  • Future: clustering via @googlemaps/markerclusterer, and the deferred mobile map sheet.

Session 2026-04-21 — ProviderMap organism (Google Maps) + wire into arrangement demo

Agent(s): Claude Opus 4.7 (1M context)

Context: The arrangement demo at parsons.tensordesign.com.au/arrangement had an empty mapPanel slot on ProvidersStep. Build the map organism and wire it into the demo. Prior session's registry had MapPin (atom, done), MapPopup (molecule, done), and MapCard (molecule, planned — "deferred until map integration").

Decisions made:

  • D041 — Google Maps via @vis.gl/react-google-maps; MapCard retired (superseded by MapPopup). Popup rendered as a second AdvancedMarker (not Google InfoWindow) to preserve MapPopup's own nub and avoid competing chrome. Client already runs Google Maps in production, so the demo's stack matches — zero migration if this becomes prod.
  • API key: demo-owned, created under Richie's Google Cloud account (fa-demo-maps project). Restricted to HTTP referrers: localhost:6006, localhost:5173, localhost:4173, and https://parsons.tensordesign.com.au/*. Scoped to Maps JavaScript API only. Key lives in .env.local (gitignored via .env.*), VITE_GOOGLE_MAPS_API_KEY. Vite bakes it into the bundle at build time — exposed to the browser, but the referrer restriction is the real security boundary.
  • Mobile deferred: the existing list-map WizardLayout hides the right panel entirely on mobile (display: { xs: 'none', md: 'flex' }). Wiring the viewMode: 'list' | 'map' toggle to a panel swap, or building a mobile map sheet, is layout work that touches other wizard steps — out of scope for this task. Desktop-only for now.

Work completed:

  • npm install --legacy-peer-deps @vis.gl/react-google-maps (pre-existing Storybook addon-a11y@8.6.14 ↔ react@8.6.18 peer conflict unrelated to our package).
  • New organism src/components/organisms/ProviderMap/{ProviderMap.tsx,ProviderMap.stories.tsx,index.ts}:
    • APIProvider + Map (mapId fa-provider-map, disableDefaultUI + zoomControl, gestureHandling="greedy", onClick closes popup).
    • Internal FitBounds component (uses useMap + useEffect) auto-fits bounds across all geocoded providers on mount/update; single-provider maps centre at zoom 13 with explicit setCenter/setZoom.
    • Internal PinMarkerAdvancedMarker wrapping our MapPin atom. active = selectedProviderId === p.id || activeMarkerId === p.id. zIndex lifts when active.
    • Internal PopupMarker — second AdvancedMarker at same coords with transform: translateY(-50px) on the wrapper, rendering MapPopup molecule. zIndex 100. Click → onSelectProvider(id).
    • Empty states: !apiKey → "Map unavailable — Google Maps API key not configured"; no geocoded providers → "Map unavailable — No provider locations to display". Never throws.
    • Root Box: role="application", aria-label="Provider map", display: flex; flex: 1; minHeight: 300; bgcolor: surface-cool.
    • 7 stories: Default, WithSelectedProvider, InteractiveSelection, NoCoords, NoApiKey, SingleProvider, PartialCoords.
  • Extended ProviderData type with coords?: { lat: number; lng: number } (optional — legacy callers unaffected).
  • Added real coords to the 7 demo providers in src/demo/shared/fixtures/providers.ts:
    • parsons → Wentworth NSW (-34.1074, 141.9166)
    • rankins → Warrawong NSW (-34.4870, 150.8970)
    • wollongong-city → Wollongong NSW (-34.4278, 150.8931)
    • killick → Kingaroy QLD (-26.5408, 151.8388)
    • mackay → Ourimbah NSW (-33.3644, 151.3728)
    • mannings → Bega NSW (-36.6742, 149.8417)
    • botanical → Newtown NSW (-33.8988, 151.1794)
  • Wired <ProviderMap> into src/demo/apps/arrangement/routes/Providers.tsx via the mapPanel prop. Same onSelectProvider navigation target as the list.
  • Registry updated: ProviderMap row added to Organisms; MapCard row marked superseded with pointer to D041.
  • Decisions log: D041 added.

Preflight: TS clean, ESLint clean on the new component (2 warnings for unused // eslint-disable-next-line no-alert directives were auto-removed). Full preflight + deploy still pending.

Open questions:

  • None blocking. Awaiting user's visual review in Storybook before deploy.

Next steps:

  • User reviews ProviderMap stories in Storybook (already running on :6006).
  • Run /preflight and npm run demo:publish once approved.
  • Mobile map sheet / viewMode panel swap is the tracked follow-up.

Session 2026-04-20 — PackageDetail polish + PackagesStep spacing/drill-in + NearbyPackageCard elevation

Agent(s): Claude Opus 4.7 (1M context)

Context: Continuation of 2026-04-17b (tier consolidation, still uncommitted). This session focused on finishing the PackagesStep page: PackageDetail header/shadow, price disclaimer visual fix, vertical rhythm in the left column, mobile drill-in navigation, and several copy/content refinements. Preflight clean at end of session.

Work completed:

PackageDetail organism:

  • Header band background: surface-warmbackground.paper (white). Warm tint was competing with the page header and CTA.
  • Added boxShadow: var(--fa-card-shadow-default) to root for card elevation.
  • Compare button wire-through: added onCompare prop to PackagesStep, passed through to PackageDetail for all three tiers (button only renders when callback provided — was already built into PackageDetail but never exposed).
  • Price-disclaimer info-box refined: padding 12/8 → 16/12, gap 8 → 10, line-height 1.4 → 1.5, icon alignment fix. Initial attempt with a wrapper Box centering the icon on full 1.5em line-box pushed the icon above optical cap-centre (~2px high). Reverted to the codebase convention mt: '3px' directly on the icon (matches PaymentStep / CrematoriumStep).

PackagesStep page:

  • Vertical-rhythm pass on the left column. Three user-flagged gaps tuned (multiple iterations — 3 → 4 → 6 → 8 on divider, 3 → 4 → 6 on container mb):
    • Provider card → h1: mb: 3mb: 6 (24 → 48px).
    • Subheading → "Matching your preferences" heading: mb: 3mb: 6 (24 → 48px).
    • Primary list → Divider → secondary section: primary list mb: 3mb: 4, Divider mb: 2.5my: 8 (both same-provider-more and nearby-verified dividers). Total gap ≈ 128px + divider line — intentionally larger than 1 & 2 because it separates two distinct groups.
  • Mobile drill-in navigation (< md breakpoint): added useMediaQuery + mobileShowDetail derived state. On mobile, list and detail render mutually exclusively via display: { xs: ..., md: 'block' } toggles. onSelectPackage signature widened to (id: string | null) => void so the mobile back button can clear selection. When a package is selected on mobile, the WizardLayout's Back button label/action swaps: "Back" → onBack becomes "Back to packages" → onSelectPackage(null). useEffect scrolls window to top on drill-in so the detail isn't stranded mid-page. Desktop unchanged (both panels always visible).
  • "See all" link copy changed from See all {total} packages from {provider.name} to See {overflow} more packages from this provider (overflow = sameProviderPackages.length - SAME_PROVIDER_INLINE_LIMIT). Rationale: (a) smaller, action-oriented number ("more" implies gain, not total); (b) "this provider" sidesteps the wrap risk on long provider names; (c) the section heading above ("Other packages from [Provider]") already resolves the ambiguous "this provider" reference. Minor imprecision: clicking lands on "All packages from [Provider]" which shows all packages, not just overflow — acceptable ("user asked for more, got the whole picture").
  • Verified provider image: placeholder URL → real local asset /images/placeholder/hparsonsvenue.jpg. Source was 2048×1366 / 591KB; resized in place to 640×427 / 52KB via ImageMagick (quality 82, metadata stripped). Provider card displays at 120160px wide so 640 is plenty for 2× retina.

NearbyPackageCard molecule:

  • Dropped variant="outlined" override — now inherits Card atom's default elevated (shadow). Matches the primary ServiceOption cards in the same column. Per design discussion: shadow signals "interactive, lifts on engage" for selectable/linked cards; outlined reserved for context/container cards.

ProviderCardCompact:

  • Kept outlined (discussed, not changed). Different role: page-level context header, not part of the selection group. Shadow across everything in the column would flatten hierarchy.

Decisions made:

  • Icon alignment convention reaffirmed: 16px icons with body2 text use mt: '3px' directly on the icon with alignItems: 'flex-start' on the parent. Don't wrap the icon in a flex-center box — MUI icons have SVG padding that puts geometric centre above optical cap-centre.
  • Shadow vs outlined: interactive content cards (ServiceOption, NearbyPackageCard) use shadow; context/container cards (ProviderCardCompact) use outlined. Avoids flattening visual hierarchy.
  • Mobile drill-in over inline-expand or bottom-sheet: cleanest state model (selection drives view), well-established pattern, no new components.
  • "See X more" over "See all X": smaller framing, sidesteps long-name wrap.

Figma / Make exploration:

  • Captured PackagesStep Tier2 story to the Parsons Figma file (https://www.figma.com/design/XUDUrw4yMkEexBCCYHXUvT?node-id=6073-25005) for Gemini 3 polish pass in Figma Make.
  • Drafted two Gemini 3 prompts tailored to Figma Make — one for polish (constraints-last, preserves structure), one for mobile exploration (3 directions: drill-in, inline-expand, bottom-sheet). User chose drill-in for implementation.
  • Research notes: Gemini 3 weights tail of prompt heavily; prefers TC-EBC framework (Task / Context / Boundaries / Criteria); terse over verbose; declare design-system constraints upfront.

Preflight: TS, ESLint, Prettier (auto-fixed 4 files), Storybook build, token sync, exports all PASS. Hardcoded-hex scan: only pre-existing values in other files; none introduced in this session's edits.

Open questions:

  • None.

Next steps:

  • Carry-overs from ComparisonPage 2026-04-17 still open: [P1] mobile tab rail arrow-key nav, [P2] dot indicator tap targets (44px min), [P3] desktop empty state, plus the deferred collapsing sticky header.
  • MapCard molecule still "planned" in registry — deferred until map integration.
  • Provider profile page — onProviderClick is wired but destination doesn't exist yet.

Session 2026-04-17 — ComparisonPage restructure (scroll model, sticky-left, tiered hover) + card refinements + mobile polish

Agent(s): Claude Opus 4.7 (1M context)

Work completed:

Phase A — WizardLayout bleed variant (new):

  • Added a 7th layout variant bleed for pages that own their own alignment logic.
  • Viewport-locked (height: 100vh; overflow: hidden on root). <main> becomes the single scroll container (both axes) via flex: 1; overflow: auto.
  • No inner Container — children render full-bleed.
  • Back link slot passed into children (like list-map / detail-toggles) so it scrolls with content.
  • data-wizard-scroll attribute on the scroll host — used by descendants to find the scroll ancestor (e.g. for IntersectionObserver roots).

Phase B — ComparisonPage + ComparisonTable restructure:

  • ComparisonPage (desktop) now uses bleed. Structure: centred page-header container (maxWidth = COMPARISON_TABLE_COL_WIDTH × 4 = 1200px) → <Divider> → full-bleed flex row [spacer][table][spacer]. Matching horizontal padding (16/24px) between container and spacers keeps content left-edges aligned on all viewports.
  • Own <Back> link inside the page-header container (not WizardLayout's) so it sits under the shared left-edge rather than the raw scroll-host edge.
  • ComparisonTable rewritten with fixed column widths: COMPARISON_TABLE_COL_WIDTH = 300 exported as a constant. Natural width = 300 × (packages.length + 1).
  • Sticky-left on row-label column across every per-section mini-table. Works because each mini-table shares the scroll ancestor (the bleed <main>).
  • Tiered hover: base row-cells → surface-subtle (#fafafa), recommended column cells → surface-warm (#fef9f5). Recommended column has a resting 50%-opacity warm tint that promotes to full warm on row hover (via color-mix).
  • Per-section mini-tables kept (user preference) with left-accent brand heading.
  • "Not Included" rendering: new rule — unavailable cell values in sections whose heading matches OPTIONAL_SECTION_HEADINGS (currently Optionals, Extras) render "Not Included" in neutral-500; Essentials keeps the em-dash. Meaning: missing in Essentials = "not itemised", missing in Extras = "deliberately absent".
  • CellIconText local helper introduced for icon+label cell values (complimentary, included, unknown). Sets lineHeight: 1 on flex container AND Typography so geometric and optical centres align — fixes the icons-sitting-slightly-high visual bug.

Phase C (stripped): Built a collapsing sticky mini-header (IntersectionObserver + sentinel + max-height transition) then removed per user request — they want to revisit later. All machinery (observer, state, sentinel, Button import, z-index const) removed clean.

ComparisonColumnCard refinements:

  • Fixed physical height consistency: outer wrapper in ComparisonTable is now display: flex; flexDirection: column; minWidth: 0, and the card root receives flex: 1 so all cards stretch to the tallest grid-row height.
  • Top padding inside card bumped from conditional 2.5/3 to uniform pt: 5 (40px) — significantly more breathing room above the provider name. Bottom padding pb: 2.5pb: 3.
  • Badge size bumped from small (22px) to medium (26px) with icon fontSize 14 → 16 and top: -12-13.
  • Remove link footer: instead of conditional Link-vs-Box, always renders the same Link element; visibility-hidden + tabindex=-1 + aria-hidden when no Remove action applies. CTA + footer row layouts now identical across all cards, fixing a sub-pixel CTA-height drift.
  • Provider name: wraps to up to 2 lines via WebkitLineClamp: 2. Name Box has minHeight: 36 (= 2 × label-line-height) and alignItems: flex-end — 1-line names sit at the bottom of a reserved 2-line slot, so location/rating/price below stay on the same baseline regardless of wrap.
  • Inline verified icon adjusted from mt: 3px + alignItems:center to mb: 2px + alignItems:flex-end to pair with bottom-aligned text.
  • Tooltip threshold for long provider names raised from 24 to 50 chars (wrapping handles most cases).
  • Internal grouping spacing pass: outer gap 0.5 → 1, Divider my: 1 → 1.5. Price subgroup wrapped in its own sub-Box with gap: 0.25 so "Total package price" label + amount stay visually joined despite the larger outer gap. Redundant Button mt: 1.5 and Remove mt: 0.5 removed.

Mobile polish — ComparisonTabCard:

  • Width: 210 → 235 (+25px).
  • Recommended badge aligned with desktop: filled variant + StarRoundedIcon, keeps size="small" per user instruction.
  • Removed the glow (0 0 12px rgba(186, 131, 78, 0.3)) — uses same var(--fa-shadow-sm) as other cards. No custom active-recommended glow either.
  • Border colour: brand-500brand-600 (consistent with primary).
  • Top padding inside card: 2.4 → 3.5.

Mobile polish — ComparisonPackageCard:

  • Badge removed in favour of inline verified icon (consistent with desktop ComparisonColumnCard pattern): VerifiedOutlinedIcon in brand-600 sits left of provider name in a flex row when provider.verified is true.
  • Warm background confined to the provider header only: removed selected={pkg.isRecommended} from the root Card (it was applying surface-warm across the whole card). Explicit bgcolor: 'background.paper'. Border: explicit 2px solid brand-600 on the Card when recommended (matches desktop ComparisonColumnCard exactly).
  • Header horizontal padding: px: 2.5 → px: 3. Top padding pt: 2.5 → pt: 3, bottom pb: 2 → pb: 4.
  • Spacing between groups: badge→name→metadata bumped (mb: 1 → 2, mb: 0.5 → 1.25, mb: 1.5 → 2). Divider mb: 1.5 → my: 3. Package-info subgroup wrapped in flex column with gap: 0.75 between name and price block; inner price block has gap: 0.25. Button mt: 2 → mt: 3.
  • Sections area: py: 2.5 → pt: 3.5, pb: 3. Between sections mb: 3 → mb: 5. Section heading → first item mb: 1.5 → mb: 2.5. Item rows py: 1.5 → py: 2. Redundant mt: 1 on subsequent section headings removed (parent spacing now owns the gap).

Mobile polish — ComparisonPage:

  • Added <Divider> between page header (h1 + subhead) and package selector.
  • Added <Typography component="h2">Choose a package to view</Typography> as user-centric heading for the tab rail. Hooked up as the tablist's aria-labelledby.
  • Dot indicator added below the rail: 8×8px grey neutral-300 dots, active dot expands to 24×8px brand-600 pill (width transition 200ms). Tap-to-navigate. Container is aria-hidden="true" with dots having tabIndex: -1 — purely visual supplement; the tab rail above remains the canonical accessible navigation, avoiding duplicate tabstops for screen reader / keyboard users.
  • Tab rail mb: 3 → 1.5 so dots sit close to the rail they describe.

Test data update:

  • pkgMackay.provider.name extended to "Mackay Family Funeral Directors & Cremation Services" (52 chars) — exercises the 2-line provider-name wrap in all four comparison stories.

Decisions made:

  • Scroll model A (single scroll container on main, page header scrolls away) chosen over B (table-internal scroll with page chrome pinned) and C (hybrid). Makes the width-matching left-edge alignment trick work naturally.
  • Sticky columns: row-labels only, not recommended column. Simpler, preserves horizontal space for other packages on medium screens.
  • Per-section mini-tables kept — user preferred visual rhythm over single continuous table.
  • Tokens unchanged: brand-50 (#fef9f5) and brand-600 (#b0610f) are exactly what Make's exploration landed on. No token work needed.
  • Collapsing sticky header dropped — user will revisit. Preserve option; removed all machinery cleanly.
  • Info-icon tooltip on mobile package card: accepted as exception. Touch-device tooltip UX isn't ideal but info is supplementary.

Open questions:

  • None blocking.

Next steps:

  • Committed in 2 commits (f146bb0f restructure + 312a77ae mobile polish). Branch is 10 commits ahead of origin/main.
  • User-flagged next focus areas (the reason this session ended): (1) Package Select page refinements; (2) Map pins (MapPin atom is done, MapPopup molecule is done, MapCard molecule is still "planned" in the registry — deferred until map integration).
  • Audit run at end of session — scored 19/20 (Excellent). Carry-overs:
    • [P1] Mobile tab rail arrow-key navigationrole="tablist" expects Left/Right to move focus between tabs, Home/End to jump first/last. Currently Tab into first tab works, but arrows don't move focus. ~20 lines: keyboard handler on tablist moving activeTabIdx.
    • [P2] Dot indicator tap targets ~24px — below WCAG 2.5.5 (44×44). Acceptable as a supplement (tab rail is canonical nav) but worth bumping padding from p: 1 to p: 1.5 or wrapping in a larger hit region with visually 8px dot.
    • [P3] Empty-state on desktop — if user removes all packages, desktop renders header + divider + empty table zone. Mobile already guards allPackages.length > 0; desktop branch should mirror.
    • [P3] Info icon tooltips on mobile — 14px tap + long-press affordance. User has explicitly accepted as exception; noted for record.
  • Deferred from this session (ready to rebuild, machinery stripped cleanly): the collapsing sticky mini-header on desktop ComparisonTable. Design: full ComparisonColumnCard row position: sticky; top: 0, sentinel + IntersectionObserver detects when full row scrolls out, collapses to a mini row showing provider + price + CTA. User wants to pick this up later.

Session 2026-04-17b — PackagesStep tier consolidation + NearbyPackageCard + "See all" variant

Agent(s): Claude Opus 4.7 (1M context)

Context: User asked to continue work on the package select page. Before touching code, scoped out the consolidation: three separate pages (PackagesStep, UnverifiedPackageT3, UnverifiedPackageT2) were structurally identical modulo a handful of tier-driven differences — provider image, heading copy, secondary-list content, and PackageDetail flags. Same layout, triplicated. Plus a legacy PackageSelectPage story inside PackageDetail.stories.tsx predating the real page components.

Decisions made:

  • One unified PackagesStep with providerTier: 'verified' | 'tier3' | 'tier2' rather than per-tier pages. Reasoning: every polish pass would otherwise be triplicated (the Comparison work last session landed 20+ small touches — that's a real tax); differences are all data/copy, not layout. PackageDetail already validated this pattern.
  • Copy/CTA/disclaimer/itemized-unavailable all derived from tier via an internal TIER_COPY map. Business defaults live in the page, PackageDetail stays prop-driven and tier-agnostic.
  • Discriminated secondaryList union: { kind: 'same-provider-more', packages } (verified) vs { kind: 'nearby-verified', packages } (unverified). Rendering branches cleanly from the discriminator; types force callers to pick the right shape.
  • Routing model for "See all" + package clicks: URL-driven — every (provider, package, preferences) triple has its own route. Clicking a nearby-verified package → route change to that provider's PackagesStep. "See all N packages from [Provider]" → route change to the same PackagesStep with showAllFromProvider=true (preference-filter dropped). Component doesn't own "show all" state; caller hands in the full list and flips the flag.
  • showAllFromProvider variant: flat list, title becomes "All packages from [Provider]", no grouping, no secondary list, selectedPackageId preserved from the origin view. Subhead "Every package [Provider] offers, including those outside your preferences."
  • >3 rule for same-provider-more: show first 3 + "See all N packages from [Provider] →" Link (MUI Link with component="button" + ArrowForwardIcon). Below the limit, render all inline with no link. Callback is onSeeAllPackages.
  • "Matching your preferences" accent-bar heading suppressed when no secondary list follows — same rule as before; the label only appears when there's a contrasting group below it. Also suppressed in showAllFromProvider mode.
  • NearbyPackageCard extracted as a molecule rather than kept inline. ~50 lines of bespoke JSX was duplicated across T2 and T3 — now a single molecule with its own stories (Default, WithoutRating, Static, Stacked).
  • Legacy PackageSelectPage story deleted from PackageDetail.stories.tsx. Story was a page-level mock predating the real page components — misleading dead weight. Unused imports/helpers pruned (useState, ServiceOption, ProviderCardCompact, Chip, Typography, Button, Navigation, ArrowBackIcon, DEMO_IMAGE, packages, funeralTypes, FALogoNav).

Work completed:

  • New molecule: src/components/molecules/NearbyPackageCard/ (tsx + stories + index).
  • New shared types file: src/components/pages/PackagesStep/types.tsProviderTier, PackagesStepProvider, PackageData, NearbyVerifiedPackage, SecondaryList discriminated union. Page re-exports them for callers.
  • Rewrote src/components/pages/PackagesStep/PackagesStep.tsx as the tier-aware unified component. TIER_COPY map, SAME_PROVIDER_INLINE_LIMIT = 3, GroupHeading local helper (accent bar + label, primary/secondary emphasis).
  • Rewrote PackagesStep.stories.tsx — 9 stories: Verified, VerifiedWithManyOtherPackages (exercises >3 rule), AllFromProvider (showAllFromProvider variant), Tier3, Tier2, NoSelection, VerifiedNoSecondary, PrePlanning, WithError.
  • Deleted src/components/pages/UnverifiedPackageT2/ and src/components/pages/UnverifiedPackageT3/ entirely.
  • Deleted the legacy PackageSelectPage story in PackageDetail.stories.tsx and pruned orphaned imports/helpers.
  • Registry: added NearbyPackageCard row to Molecules; rewrote PackagesStep row to describe the tier-aware shape and the showAllFromProvider variant.

Non-blocking TODOs noted:

  • Provider profile page (future) — onProviderClick is wired up on the provider card but the destination isn't built yet.
  • Mobile polish — desktop only this pass (by user instruction); mobile layout to be reconsidered after the desktop shape settles. Current mobile state is the list-detail WizardLayout's stacked fallback; not visually tuned.

Preflight: typecheck clean, lint clean.

Open questions:

  • None blocking.

Next steps:

  • Uncommitted. Expect a single commit for the consolidation.
  • Mobile pass on the unified PackagesStep next.
  • Provider profile page when it comes up.

Session 2026-04-16b — ComparisonColumnCard refinements

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Recommended banner removed — was causing CTA misalignment between recommended and non-recommended cards. Replaced with a floating "Recommended" badge (primary fill, star icon) that replaces the "Verified" badge (soft, checkmark) when recommended.
  • Card border fix — selected state was using brand-500 (via --fa-card-border-selected token) which looked inconsistent with the rest of the primary system. Recommended cards now override to brand-600 to match banner/button primary colour.
  • Inline verified icon — small VerifiedOutlinedIcon in brand-600 now sits to the left of the provider name only on recommended cards (recommended providers are always verified). Regular verified providers rely on the top badge alone.
  • No-rating fallback — when provider.rating == null, renders a single in the rating slot to keep card heights consistent across all columns.
  • CTA alignment — recommended cards now render an invisible Remove placeholder (same height) so the primary CTA button aligns with non-recommended cards that do have a Remove link.
  • Remove link dropped to caption — from body2 (14px) to caption (12px) on both real and spacer versions.
  • Gap between verified icon and provider name bumped from 0.5 to 0.75.

Decisions made:

  • Star (StarRoundedIcon) used for Recommended badge icon — reads as "standout" at 14px and differentiates from the Verified checkmark.
  • Recommended-card border override is local (sx) rather than a new theme token — the inconsistency is between the token (brand-500) and the rest of the system (brand-600); the right long-term fix is probably updating the token itself.

Next steps (tomorrow):

  • Retry Sheffield remote push on next /publish (still 502 last session).
  • Consider promoting the brand-500 → brand-600 border fix to the --fa-card-border-selected token if it applies broadly.

Session 2026-04-16 — HomePage refinements + Navigation dropdown + hero image

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Navigation organism extendedNavItem.children now supported. Desktop renders dropdown menu (MUI Menu, click-triggered), mobile renders collapsible section in drawer. Story Organisms/Navigation > WithDropdown added.
  • Locations nav added — Melbourne, Brisbane, Sydney, South Coast NSW, Central Coast NSW, placed before FAQ in all four HomePage stories.
  • Hero refinements (HomePage) — new hero-couple.jpg background (compressed 1.4MB → 239KB via ImageMagick -quality 82 -interlace Plane -strip); italic tagline "Trusted by thousands of families across Australia" above h1; hero text container widened from md (900px) to 990px; mobile top padding increased; mobile side padding on FuneralFinder bumped from 16px to 24px.
  • Hero h1 typography — promoted from display3 (same as section h2s) to display2 (52px desktop / 24px mobile) for hierarchy contrast. Still renders as semantic <h1>.
  • Section overlines added — "FUNERAL ARRANGER REVIEWS" above "What families are saying"; "WHY USE FUNERAL ARRANGER" above "4 Reasons to use Funeral Arranger" (renamed from "How it works"). Placeholder body copy removed from features section.
  • Features block body copy removed — "Search local funeral directors, compare transparent pricing..." paragraph removed; body now only renders if explicitly passed.
  • CTA button promoted — "Start planning" in the "We Are Here When You Need Us" section changed from text/large to contained/medium.
  • Arrow removed from "Start exploring" text button.
  • FuneralFinderV3 header removed — form now starts directly at "How can we help"; unused heading/subheading props deleted; consumers updated.
  • Mobile typography scaling — body1 paragraphs under display3 headings now scale to 14px on mobile (better hierarchy vs the 22px mobile display3). Applied to Discover, Why Use FA, and Features sections, plus FAQ questions.
  • Why Use FA text block — centre-aligned on mobile, left-aligned on desktop.
  • HomePage captured to Figma — node 6061-25005 via capture-to-figma skill.

Publishing:

  • Builds 69 published to Chromatic across the session. Build 9 is current client-facing: https://69c9f45d915025e56139051e-ktlogciofe.chromatic.com/
  • Asset sync step added to /publish skill — new hero-couple.jpg picked up automatically.
  • Sheffield remote returning 502 intermittently (server-side) — skipped, retry on next publish.

Decisions made:

  • body1 under display3 gets a mobile fontSize override (14px) rather than adding a new theme variant — the issue is contextual, not systemic.
  • Nav dropdown trigger is a plain button (no chevron) per user preference; mobile keeps expand/collapse chevrons because they're tappable affordances.
  • Hero image filename convention: short + content-descriptive (hero-couple.jpg), not marketing-suffixed.

Next steps:

  • Retry Sheffield push on next /publish.
  • Consider extracting the inline FAQ accordion in HomePage to a shared molecule if used on more pages.

Session 2026-04-13 — Chromatic image hosting + Gitea domain migration

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Gitea domain migration — updated all remotes (backup, fa-dev) and references from git.richie-snitch.online to git.tensordesign.com.au. Updated publish skill and memory files.
  • Chromatic image hosting solution — created src/utils/assetUrl.ts utility that resolves image paths via STORYBOOK_ASSET_BASE env var. When set (Chromatic builds), images load from Gitea ParsonsAssets repo. When empty (local dev), paths resolve via staticDirs as before.
  • Fixed path inconsistency — story files used /brandassets/images/... which only worked locally via Vite root serving, not in builds. Changed to /images/... (correct for staticDirs) wrapped in assetUrl().
  • Updated 4 files: HomePage.tsx, HomePageV2.stories.tsx, HomePageV3.stories.tsx, HomePageV4.stories.tsx
  • Updated publish skill — added Step 4 (sync assets to Gitea) and Step 5 (Chromatic with STORYBOOK_ASSET_BASE env var)
  • Created init scriptscripts/init-assets-repo.sh for one-time setup of ParsonsAssets repo on Gitea

Decisions made:

  • ParsonsAssets Gitea repo must be public so Chromatic clients can load images without auth
  • /brandlogo/ SVG paths left as-is — they're small, tracked in git, and work via staticDirs in builds
  • Only /brandassets/images/... paths converted to assetUrl() — these are the large untracked binaries
  • Scripts directory gitignored (contains embedded credentials)

Open questions:

  • User needs to create ParsonsAssets repo on Gitea and run scripts/init-assets-repo.sh (server not reachable from dev machine)

Next steps:

  • Create public ParsonsAssets repo on git.tensordesign.com.au
  • Run init script to push 1.1GB of brand assets
  • Test /publish end-to-end to verify Chromatic images render

Session 2026-04-12b — Homepage V3 redesign + FuneralFinder refinements

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • HomePage V3 promoted to production — story title changed from Archive/HomePage V3 to Pages/HomePage
  • FuneralFinder V3 refinements: responsive CTA (medium on mobile, large on desktop), button text "Search Local Providers" → "Search", "Free to use" bumped from captionSm to caption, tightened location pin-to-text gap
  • New section: "Why Use Funeral Arranger" — text left + image right (people.png), overline + display3 heading + body copy
  • New section: "Three ways we can help you today" — 3 feature cards with placeholder images, headings, descriptions, outlined CTA buttons
  • Section reorder: partner logos carousel moved above Discover section, inherits overlap padding from FuneralFinder
  • Background colour scheme from Figma Make: warm-grey alternating — #f3efea (darkest), #fdfbf9 (lightest), #f8f5f1 (mid), white sections with #f3efea borders
  • Overline headings ("WHY USE FUNERAL ARRANGER", "WHAT YOU CAN DO HERE") styled in copper brand-600
  • Card drop shadows on three feature cards (shadow-md, removed border)
  • Heading consistency: all section headings unified to display3 serif; "Why Use FA" was h3, FAQ was h2 — both fixed
  • SEO fixes: h1 grammar ("funeral directors pricing" → "funeral director pricing"), logo section heading <p><h2>, aria-labelledby on partner section
  • Section padding increased from py 8/12 to 10/14 across all sections
  • Testimonials left-aligned with consistent 560px max-width, centred block
  • "See more" button added below FAQ accordion
  • FAQ heading changed to "Frequently Asked Questions"
  • Logo section changed to white bg, overline style heading, increased heading-to-logos gap
  • Figma capture sent to Parsons Figma file (node 6049-25005)

Decisions made:

  • Page-level warm-grey backgrounds use direct hex values (#f3efea, #fdfbf9, #f8f5f1) rather than brand tokens — brand-100 (#f7ecdf) is too golden; the Figma tones are neutral-warm
  • All section headings use display3 (Noto Serif SC) visually, rendered as semantic h2 tags
  • Three feature card buttons are outlined, text-only (no arrows)

Next steps:

  • Replace feature card placeholder images with real screenshots/illustrations
  • Wire up onClick handlers for feature card CTAs and FAQ "See more" when routing is in place
  • Consider meta title/description for production deployment

Session 2026-04-12 — ComparisonPage refinements + new molecules

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • New molecule ComparisonColumnCard — extracted ~150 lines of desktop column header JSX from ComparisonTable. Floating verified badge, recommended banner, provider info, price, CTA, Remove link. Stories: Verified, Unverified, Recommended, LongName, NoRating, SideBySide.
  • New molecule ComparisonTabCard — extracted mobile tab rail card from ComparisonPage. Provider name + package name + price. Recommended badge in normal flow (not absolute, avoids overflow clipping). Brand glow for recommended. Fixed 210px width. Stories: Default, Active, Recommended, RecommendedActive, LongName, Rail.
  • CellValue "unknown" restyled — changed from Badge to icon+text (like Complimentary but in neutral.500 grey). InfoOutlinedIcon on the right at 14px, matching the item info icons. Both desktop (ComparisonTable) and mobile (ComparisonPackageCard) renderers updated.
  • Unverified provider story data — all three story files (ComparisonTable, ComparisonPage, ComparisonPageV1) updated: Inglewood (unverified) shows unknown for every item across all sections. No more dashes in essentials for unverified providers.
  • Mobile tab rail overhaul:
    • Recommended badge (replaces star icon), in normal document flow with mb: -10px overlap
    • Package price shown on each tab card in brand colour
    • Shadow on all cards (shadow-sm), brand glow on recommended (0 0 12px)
    • Center-on-select scroll behaviour, user's first package centered on mount
    • Overflow clipping fixed via padding approach (px: 2, mx: -2) + normal-flow badge
  • ComparisonPackageCard — added shadow-sm, reduced CTA button from large to medium
  • ComparisonTable — removed ~150 lines of inline header card JSX, 5 unused imports
  • Both ComparisonPage V1 and V2 updated to use new molecules (ComparisonTabCard, ComparisonColumnCard via ComparisonTable)
  • Registry updated: added ComparisonColumnCard and ComparisonTabCard rows, updated ComparisonPackageCard notes
  • Preflight: tsc ✓, eslint ✓, prettier ✓

Decisions made:

  • Unknown cell type uses InfoOutlinedIcon (not HelpOutlineIcon) — matches the info icons on item names in the first column for visual consistency
  • Dashes (unavailable) reserved for optionals/extras where a provider doesn't offer that item; essentials always show unknown for unverified providers
  • Mobile tab rail badge uses normal document flow (not absolute positioning) to avoid overflow clipping from overflow-x: auto
  • Tab rail centres selected card (not scroll-to-left)
  • ComparisonTabCard fixed at 210px width

Next steps:

  • User starting a new session

Session 2026-04-09b — Extract ComparisonPackageCard molecule

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • New molecule src/components/molecules/ComparisonPackageCard/ — extracted from the duplicated MobilePackageCard + MobileCellValue helpers that lived inline in both V1 and V2.
    • API: { pkg: ComparisonPackage, onArrange, sx } — reuses the existing ComparisonPackage type from ComparisonTable
    • Stories: Verified (default), Unverified, Recommended, ItemizedUnavailable — rendered in a 400px-max wrapper via decorator
    • Full forwardRef + displayName
  • V2 ComparisonPage.tsx: removed ~250 lines of inline helpers + 7 now-unused imports (Tooltip, InfoOutlinedIcon, CheckCircleOutlineIcon, LocationOnOutlinedIcon, VerifiedOutlinedIcon, Badge, Divider, ComparisonCellValue type). Imports ComparisonPackageCard and uses it in the mobile tabpanel.
  • V1 ComparisonPageV1.tsx: same cleanup as V2.
  • Registry updated: added ComparisonPackageCard row to molecules table.
  • Preflight: tsc ✓, eslint ✓, prettier ✓ (one auto-format pass on new molecule), storybook build ✓.

Why: User anticipated frequent card-level tweaks and didn't want to make each edit twice (V1 + V2). Extraction creates one source of truth for the mobile comparison card layout.

Decisions made:

  • Kept the desktop column header (in ComparisonTable) inline — it's tightly coupled to the grid/sticky layout and has a floating verified badge + Remove link that differ from the mobile card variant. Extracting it would create an awkward shared API for marginal gain.
  • Did not create a shared formatPrice util — kept one local copy in the molecule (and one that still exists in ComparisonTable). That's a separate cleanup for later if it becomes painful.

Next steps:

  • Client review of ComparisonPage V2 still pending
  • Possible future extract: ComparisonColumnHeader (desktop) if reused outside comparison

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Archived V1 — copied ComparisonPage.tsxComparisonPageV1.tsx, renamed component + displayName, created ComparisonPageV1.stories.tsx with title Archive/ComparisonPage V1, exported both from index.ts.
  • Built V2 (new production):
    • Desktop: recommended package now prepends to allPackages → appears as the first (leftmost) column in ComparisonTable
    • Mobile tab rail: recommended is the first tab, but defaultTabIdx = recommendedPackage ? 1 : 0 — the first user-selected package is the initially active tab, not the recommended
    • JSDoc updated to reflect V2 behaviour and reference V1 archive
  • Better star for mobile tab rail (applied to both V1 and V2):
    • Replaced ★ ${provider.name} text star with StarRoundedIcon (brand-600, 16px)
    • Icon sits in a flex row with the provider name, flexShrink: 0 so it always renders, aria-label="Recommended"
    • Visible against both the selected tab state (warm bg) and unselected (white bg)
  • Memory updates: component-registry (V1 archived, V2 production), decisions-log D040 (recommended-left rationale, supersedes D038 placement), this session entry
  • Preflight: tsc ✓, eslint ComparisonPage dir ✓, prettier ✓

Decisions made:

  • D040: Recommended appears on left (desktop), first in rail (mobile), but first user package is initially active on mobile — anchors comparison without hard-upselling in grief-sensitive context

Open questions:

  • None — awaiting client review feedback

Next steps:

  • Client review of V2 today
  • If approved, remove V1 archive after a safe rollback window
  • Remaining roadmap items: wire CompareBar into PackagesStep/ProvidersStep, comparison state persistence

Session 2026-04-07 — Package comparison iteration (Figma-informed)

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • ComparisonTable major iteration from Figma feedback:
    • Dark info card → soft grey info card (surface.subtle, no border), stretches to match card heights, text at top
    • Provider cards: no logos, floating verified badge (VerifiedOutlinedIcon, consistent with ProviderCard/MiniCard/MapPopup), rating in cards (body2 size)
    • Separate bordered tables per section (Essentials, Optionals, Extras) with left accent borders (3px brand-500)
    • Reviews section removed (rating lives in cards)
    • Horizontal scroll on narrow desktops (minWidth enforcement)
    • Cards: flex stretch + spacer for CTA bottom-alignment across mixed verified/unverified
    • Row hover highlight (brand-50), font hierarchy (labels text.secondary, values fontWeight 600)
  • ComparisonPage iteration:
    • Share + Print buttons in page header (onShare, onPrint props)
    • Mobile verified badge (VerifiedOutlinedIcon in soft brand Badge)
    • Mobile section headings with left accent borders
    • Mobile item rows: 60% max-width for names, inline info icons with nowrap binding
    • Mobile tab rail: mini Card components (provider name + package name) replacing Chips
    • Navigation included by default in all stories
  • CompareBar simplified:
    • Fraction badge (1/3, 2/3, 3/3)
    • Contextual copy: "Add another to compare" / "Ready to compare"
    • Removed package names and remove buttons from pill
  • Figma integration:
    • Created /capture-to-figma skill — captures Storybook stories to Parsons Figma file
    • Created /figma-ideas skill — fetches Figma designs and proposes adaptations
    • Successfully captured ComparisonPage to Figma (node 6041-25005)
    • Applied user's Figma tweaks (node 6047-25005) back to code
  • Cleanup: Removed Figma capture script from preview-head.html, Prettier formatting pass

Decisions made:

  • Info card uses surface.subtle (not dark), stretches to match cards — less visually competing
  • Verified badge uses VerifiedOutlinedIcon (consistent with rest of system), floating above cards
  • Rating lives in card headers, no separate Reviews table
  • Section tables separated with left accent borders (3px brand-500)
  • Mobile tab rail uses mini Cards (provider + package name) not Chips
  • Share/Print are optional props on ComparisonPage

Next steps:

  • Commit all work
  • Wire CompareBar into PackagesStep/ProvidersStep (state management)
  • Consider comparison state persistence (URL params or context)

Session 2026-04-06b — Package comparison feature

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • PackageDetail fix (D039): Added priceLabel?: string to PackageLineItem interface, passed through to LineItem. Updated stories to use Essentials/Optionals/Extras sections with realistic funeral data (D035). "Complimentary Items" → "Optionals".
  • CompareBar molecule (new): Floating comparison basket pill. Fixed bottom, slide-up/down animation. Badge count + provider names + remove × buttons + Compare CTA. Max 3 user packages. Disabled CTA when <2. Inline role="alert" error for max-reached. Mobile: compact count + CTA only. Audit: 18/20 (P2s fixed: error visible on mobile, removed redundant aria-disabled).
  • ComparisonTable organism (new): CSS Grid side-by-side comparison. Sticky header cards with provider logo/name/location/rating + package name + price + CTA. Row-merged sections via buildMergedSections union algorithm. 7 cell value types via discriminated union (D036). Recommended column with warm bg + Badge. Verified → "Make Arrangement", unverified → "Make Enquiry". ARIA role="table" + role="row" + role="columnheader" + role="cell". Desktop only. Audit: 17/20 (P2s fixed: aria-label on recommended column, rowheader on section headings, token-based zebra striping).
  • ComparisonPage page (new): WizardLayout (wide-form). Desktop: full ComparisonTable. Mobile: chip tab rail (role="tablist") + single MobilePackageCard (role="tabpanel"). Recommended package as separate prop, merged as last column/tab. Back link, help bar.
  • Stories: 6 CompareBar stories (Default, SinglePackage, ThreePackages, WithError, Empty, Interactive), 5 ComparisonTable stories (Default, TwoPackages, WithRecommended, MixedVerified, MissingData), 5 ComparisonPage stories (Default, TwoPackages, WithRecommended, MobileView, FullPage with Navigation).
  • Quality gates: TypeScript ✓, ESLint ✓, Storybook build ✓. CompareBar audit 18/20, ComparisonTable audit 17/20.

Decisions made:

  • D035: Package sections standardised to Essentials/Optionals/Extras
  • D036: ComparisonCellValue uses discriminated union for exhaustive rendering
  • D037: Mobile comparison uses chip tabs + card view, not horizontal scroll table
  • D038: Recommended package is a separate prop, always additional to user selections
  • D039: PackageLineItem gains priceLabel for consistency with LineItem molecule

Open questions:

  • None

Next steps:

  • Visual review in Storybook (user + Playwright screenshots)
  • Wire CompareBar into PackagesStep (state management for comparison basket)
  • Consider adding CompareBar to WizardLayout as a slot or portal

Session 2026-04-06 — Retroactive review completion

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Phase 1 atoms complete: Audited Typography (18/20, no P0/P1) and Badge (15/20, P0 role="status" determined false positive — would create unwanted aria-live on static labels)
  • Phase 2 molecules complete: Normalized all 9 molecules — displayName ✓, forwardRef ✓, ARIA ✓, no hardcoded colours. Flagged AddOnOption/ProviderCard ARIA issues reviewed and determined false positives (Switch is semantic control; ProviderCard has ...rest passthrough)
  • Phase 3 organisms complete: Normalized 5 active organisms. Audited Navigation (15/20, no real P0/P1 — focus-visible from MUI theme, CSS vars D031-compliant) and ServiceSelector (17/20, fixed P0: added aria-required to radiogroup)
  • Phase 4 preflight: TypeScript ✓, ESLint ✓, Prettier ✓, Storybook build ✓
  • Review plan updated: All phases marked done. Only /typeset deferred (low risk)

Decisions made:

  • Badge role="status" rejected: static status labels shouldn't be aria-live regions
  • AddOnOption role="checkbox" rejected: Switch is the semantic control, Card click is convenience
  • CSS var usage in organisms is D031-compliant (CSS vars acceptable for semantic tokens per D031)

Open questions:

  • From 2026-04-01: Which HomePage version (V3 or V4) is production?

Next steps:

  • User has components to change/build — shifting to that work

Work completed (continued):

  • MiniCard molecule (new + iterated): Compact vertical card. Image + verified icon-only badge (copper circle, top-right) + title (h6, 2-line max with tooltip on truncation) + meta row + price + badges + chips. Hierarchy: title → meta → price → badges → chips. 3 component tokens. Audit: 20/20.
  • MapPin atom (new + redesigned): Two-line label map marker: name (bold, truncated 180px) + "From $X" (centred, semibold 600). Name optional for price-only variant. Verified = brand palette, unverified = grey. Active inverts + scale. Padding bumped from 8px to 12px for readability. Pure CSS. role="button" + keyboard + focus ring.
  • MapPopup molecule (new + iterated): Clickable floating card (onClick). Image + verified icon badge (matches MiniCard) + name (1-line, tooltip on truncation) + meta + price. Hierarchy matches MiniCard. No-image fallback shows inline verified icon + text. Nub + drop-shadow. 260px wide.
  • Component tokens: miniCard.json and mapPin.json added to Style Dictionary, rebuilt CSS/JS/TS outputs (407 declarations total).

Decisions made:

  • MiniCard uses h6 for title (smaller than ProviderCard's h5), caption for meta, body2 for price
  • MiniCard verified badge is icon-only circle (not text chip) — compact, matches map popup
  • MapPin redesigned from price-only pill to two-line name+price label — transparency and clarity
  • MapPin price centred under name — left-aligned looked unbalanced (visually reviewed)
  • MapPin price weight 600 (semibold) — 500 was too thin at 11px
  • MapPin padding 12px (was 8px) — names were tight against edges
  • MapPopup whole card clickable — removed "View details" link
  • MapPopup name 1-line limit (was 2) with tooltip — tighter for map context
  • Chips in MiniCard rendered as soft default Badges (no interactive Chip atom) for visual simplicity

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • FuneralFinder V4 (new organism): Based on V2 with major changes:
    • 3 numbered steps only (lookingTo, planningFor, funeralType) — location is ungated, no step number
    • No heading/subheading — compact widget for embedding in heroes
    • Refined step indicators: 48px circles matching input height, outline-to-fill + tick animation, no connector lines
    • Labels above, circle + input on same row centred
    • Location field full-width, disabled until step 3 complete
    • "Search" CTA, inline copper error messages per field (D034)
    • Archived in Storybook under Archive/FuneralFinder V4
  • FuneralFinder V3 updates:
    • Removed default subheading (now optional)
    • Heading font weight bumped from 400 → 600 (semibold) for more presence
    • Heading copy: "Find your local providers"
    • CTA: "Search Local Providers"
  • HomePage V1 archived: Original split-hero layout moved to Archive/HomePage V1
  • HomePage V2 archived: Full-bleed hero version broken out as Archive/HomePage V2
  • HomePage V3 (new, archived): Based on V2 with:
    • hero-3.png background image
    • Updated hero copy + bullet-point subheading ("Transparent pricing · No hidden fees · Arrange 24/7")
    • Real venue photos in discover section provider cards
    • Map placeholder image from brandassets
    • Scrolling partner logo bar (9 transparent logos, greyscale, edge-to-edge, continuous loop)
    • CTA section: warm gradient bg, text button, no dividers, wider container
    • Increased section spacing throughout (~25% more breathing room)
    • Hero top padding increased 40%
  • HomePage V4 (new, archived): Same as V3 but uses FuneralFinder V4 via finderSlot prop. Wider container (620px) for stepped form.
  • HomePage component updates:
    • Added finderSlot prop — allows swapping the finder widget without changing the component
    • Increased spacing: hero heading/subtext gap, finder-to-discover gap, all section padding
    • Map container: absolute positioning for dynamic height matching card stack
    • CTA section: gradient bg (brand.100 → surface.warm), text button, no dividers
    • Partner logos: lighter treatment (40% opacity, brightness lift), 48→55px height, 96px gap, no hover interaction
  • Footer restyle: Changed from dark espresso (brand.950) to light grey (surface.subtle) matching the header. All text colours flipped to dark-on-light. Logo no longer needs inverse filter.

Decisions made:

  • FuneralFinder V4 step indicators use 48px circles (same height as inputs) for visual alignment
  • Location field in V4 is gated behind step 3 completion but not numbered
  • Inline error messages replace single bottom error — each field shows its own copper-coloured message
  • Footer matches header bg colour (surface.subtle) for visual consistency
  • Partner logos: only transparent PNGs/WebPs used, opaque logos excluded
  • finderSlot prop added to HomePage for widget flexibility without component duplication

Open questions:

  • HomePage V4 finder width may need further adjustment — looked narrow in Storybook preview panel (620px container, may need 680-720px)
  • Which HomePage version (V3 with FuneralFinderV3 or V4 with FuneralFinderV4) will be the production version?
  • FuneralFinder V4 step indicator design — user liked the direction but may want further iteration

Next steps:

  • Resolve HomePage V4 finder width — test at wider viewport or increase container
  • Decide on production HomePage + FuneralFinder version
  • Build next components/pages as needed
  • Phase 4 remaining: /typeset sample, /preflight full codebase

Session 2026-03-31d — Filters, control bars, UnverifiedProviderStep, HomePage V2

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Phase 4 /adapt reviews: Ran responsive adaptation audits on Navigation, Footer, ProviderCard, VenueCard. Fixed P0/P1 touch target issues in Navigation (hamburger + drawer items minHeight 44px) and Footer (all links minHeight 44px, tagline responsive maxWidth). ProviderCard/VenueCard skipped — meta rows aren't interactive, card itself is the touch target.
  • ProvidersStep structured filters: Replaced generic ProviderFilter[] chip array with ProviderFilterValues interface containing 5 filter controls inside FilterPanel/DialogShell:
    • Location: Autocomplete multiple + freeSolo with chip-in-input pattern (real estate site UX — chip sits inside the search field with X to clear)
    • Service tradition: Autocomplete with type-to-search, 21 options ('None' first, then alphabetical traditions)
    • Funeral type: Chip multi-select with wrapping layout (6 types from spec)
    • Verified providers / Online arrangements: Switch toggles
    • Price range: Dual-knob slider + compact editable inputs (matching CoffinsStep)
  • FilterPanel molecule updates: "Done" → "Apply", "Clear all" moved to footer as "Reset filters" text button
  • DialogShell padding: px 3 → 5 (12px → 20px) for breathing room
  • Filter panel UX iterations (3 rounds):
    • Section headings: labelLg + fontWeight: 600 for visual hierarchy
    • Price inputs: compact fontSize: 0.875rem + tighter padding
    • Funeral type chips: wrap layout (not horizontal scroll) — stays within bounds
    • Price slider: px: 2.5 accommodates MUI thumb radius, no overflow
    • Active filter count includes location search; reset clears search too
  • VenueStep structured filters: Same FilterPanel pattern as ProvidersStep with venue-specific controls:
    • Location (chip-in-input), Venue type (7 chips: Chapel, Church, Cathedral, Outdoor Venue, Hall/Room, Mosque, Temple), Additional services (Video Streaming + Photo Display switches), Service tradition (autocomplete)
  • Control bar (both pages): Below search, single line:
    • [Filters] left, [↕ Recommended] right (compact menu button with dropdown)
    • Sort options: Recommended, Nearest, Price: Low to High, Price: High to Low
    • Results count on own line below with breathing room
  • List/Map view toggle: Floating overlay in top-left of map panel (paper bg + shadow). Text-labelled: "List" / "Map" with icons. Follows Airbnb/Domain pattern — contextually placed where the map lives.
  • New types: ProviderSortBy, VenueSortBy, ListViewMode, VenueFilterValues, VenueTypeOption
  • UnverifiedProviderStep (new page): list-detail layout for scraped provider listings. Left: ProviderCardCompact + "Listing" badge + available info + verified recommendations. Right: warm header band + enquiry CTA. 4 story variants (Default, MinimalData, NoData, NoRecommendations). Graceful degradation when data missing.
  • HomePage V2: Full-bleed hero with parsonshero.png, FuneralFinder inside hero (overlaps into white below), "See what you'll discover" map+ProviderCardCompact section, stripped features (no cards/circles — just icons+text), editorial alternating testimonials, minimal FAQ accordion, simplified CTA. Multiple iteration rounds on hero positioning.
  • New types: TrustStat, FeaturedProvider, ProviderDetail, RecommendedProvider

Decisions made:

  • "Service tradition" as the label for religion/faith filter — neutral, user-centric, covers religious and non-religious
  • 'None' as first tradition option
  • Location uses Autocomplete chip-in-input pattern (Domain/realestate.com.au style)
  • Funeral type chips wrap (stack) rather than horizontal scroll — clearer at dialog width
  • DialogShell px: 5 is a global change — all dialogs get more breathing room
  • FilterPanel footer: "Reset filters" + "Apply" replaces header "Clear all" + footer "Done"
  • List/Map toggle lives on the map panel (floating overlay), not in the control bar — frees space, contextually placed
  • Sort uses compact menu button (not TextField select) — same visual weight as Filters button
  • Unverified providers use list-detail layout (same as PackagesStep) for consistency
  • Unverified CTA is "Make an Enquiry" (not "Make an Arrangement") — honest framing
  • HomePage V2 hero: text top-aligned, FuneralFinder inside hero with negative mb overlap
  • Features section: no cards/circles — just icons + text for authentic feel
  • Hero gradient: 55% opacity at top for readability on bright sky

Open questions:

  • HomePage V2 still a first pass — may iterate further on discover section, testimonials, etc.
  • UnverifiedProviderStep paused — may revisit for enquiry form fields

Next steps:

  • Continue user feedback on remaining pages
  • Phase 4 remaining: /typeset sample, /preflight full codebase
  • Add progress bar + cart stories to more page stories
  • Update component-registry.md with UnverifiedProviderStep

Session 2026-03-31c — PaymentStep/ConfirmationStep review + progress bar + CartButton

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Organism normalize pass (Phase 3): Scanned all 6 organisms. Fixed FuneralFinderV3 transition timing (200ms → 150ms ease-in-out), added autodocs tag to V3 stories. Navigation audit 18/20, ServiceSelector 20/20.
  • Navigation P1 fix: Logo wrapper changed from div[role=link] to proper <a> tag for keyboard accessibility.
  • PaymentStep feedback (5 items): Divider under subheading, lock icon alignment, ToggleButtonGroup align + direction props (centre-aligned payment options, vertical payment method stack), checkbox alignment fix.
  • ToggleButtonGroup atom enhanced: New align (start/center) and direction (row/column) props. Description text bumped from text.secondary to text.primary.
  • SummaryStep: Save button → text variant (matches other pages), centred.
  • Cross-page heading spacing: All wizard pages mb: 1mb: 2 on display3 heading.
  • ConfirmationStep redesign: Animated SVG tick (circle draws then checkmark), "What happens next" warm card with bullet points, contact phone as prop, link-based secondary actions.
  • VenueStep + ProvidersStep: Sticky search bar padding fix, off-white bg behind card lists.
  • IntroStep, CemeteryStep, CrematoriumStep, DateTimeStep: Dividers under subheadings.
  • CoffinsStep: h4 heading (matches list layouts), sidebar headings h5 → h6.
  • CartButton molecule (new): Outlined pill trigger with receipt icon + "Your Plan" + formatted total in brand colour. Click opens DialogShell with items grouped by section via LineItem, total row, empty state. Mobile collapses to icon + price.
  • WizardLayout: Removed STEPPER_VARIANTS whitelist — stepper bar now renders on all variants when props provided. Stepper centred at 700px max-width, cart hugs right.
  • StepIndicator: Desktop label fontSize bumped to 0.875rem for readability.
  • Prop threading: progressStepper + runningTotal added to DateTimeStep, VenueStep, SummaryStep, PaymentStep.
  • Style Dictionary: Auto-generate tokens.d.ts on build, fixed TS unused imports.

Decisions made:

  • ToggleButtonGroup align and direction are opt-in props — existing usages unchanged
  • ConfirmationStep contact phone is now a prop (default 1800 987 888), no more hardcoded placeholder
  • WizardLayout stepper bar shows on ANY variant when props provided (StepperBar returns null when empty)
  • CartButton is a molecule (not organism) — self-contained trigger + dialog, same pattern as FilterPanel
  • Stepper bar layout: stepper centred at maxWidth 700px, cart hugs right edge

Open questions:

  • PaymentStep CTA is right-aligned — all other pages use full-width. Should it match?
  • Progress bar stories should be added to more page stories (currently only DateTimeStep has them)

Next steps:

  • Continue user feedback/edits on remaining pages
  • Add progress bar + cart to remaining page stories
  • Retroactive review Phase 3 organisms: Navigation P2s still pending (hardcoded fontSize, drawer width)
  • Update component-registry.md with CartButton

Session 2026-03-31b — CoffinDetailsStep rewrite + AdditionalServicesStep split

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • CoffinDetailsStep rewrite — two-panel detail-toggles layout with gallery + specs (left), name + description + colour swatches + allowance-aware pricing + CTA (right). Colour selection doesn't affect price. Allowance logic: fully covered / partially covered / no allowance.
  • SummaryStep rewrite — visual cart layout replacing accordion text lists:
    • Arrangement details section at top (arranger, deceased, tradition, dates, times)
    • Compact image cards for provider, venue, crematorium, coffin with location pins
    • Allowance display: "Included in your package" (fully covered) vs price/allowance/remaining breakdown
    • Checklist for included services, priced list for extras (consistent tick logic)
    • Share dialog: "Share this plan" opens DialogShell with multi-email input + confirmation
    • Full-width CTA, deposit deferred to PaymentStep
  • AdditionalServicesStep split into two pages:
    • IncludedServicesStep (new) — services included in the package at no cost. Dressing, viewing (with same-venue sub-option inside card), prayers/vigil, funeral announcement.
    • ExtrasStep (new) — optional paid extras for lead generation. Catering, music (flat inline live musician toggle + musician type), coffin bearing (toggle + bearer preference radio), newspaper notice. POA support via priceLabel. Tally of priced selections.
  • AddOnOption molecule enhanced:
    • children prop — sub-options render inside the card boundary (below divider) when checked, eliminating nested card "Russian doll" pattern
    • priceLabel prop — custom text like "Price on application" in brand copper italic
  • AdditionalServicesStep removed — replaced by the two new pages
  • All quality checks passing (TypeScript, ESLint, Prettier)
  • Playwright visual verification of all key scenarios

Decisions made:

  • Split AdditionalServicesStep into two pages for clearer UX distinction between free inclusions and paid extras
  • Sub-options render inside parent card (flat hierarchy) instead of nested cards
  • Coffin bearing changed from always-visible radio to toggle + sub-options (consistent with other items)
  • bearing field split into bearing: boolean + bearerType for toggle pattern
  • Extras page is lead-gen: signals interest, not firm commitment. Director follows up.
  • POA items show "Price on application" in brand copper italic
  • Copy refined through brand lens — no transactional language ("toggle on"), warm professional tone

Open questions:

  • None

Next steps:

  • Continue page feedback: PaymentStep, ConfirmationStep
  • Retroactive review Phase 3 (organisms) still pending
  • Batch a11y fix (aria-describedby + aria-invalid) deferred

Session 2026-03-31b — CoffinDetailsStep rewrite: product detail layout

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • CoffinDetailsStep complete rewrite — transformed to match VenueDetailStep two-panel pattern:
    • Left panel: ImageGallery (hero + thumbnails), product details as semantic dl list (bold label above value)
    • Right panel (sticky): coffin name (h1), description, colour swatch picker, price with allowance-aware display, CTA, save-and-exit
    • Colour picker: circular swatches (36px), aria-pressed, controlled via selectedColourId/onColourChange, does not affect price
    • Allowance pricing logic: fully covered (allowance >= price) → "Included in your package allowance — no change to your plan total." / partially covered → shows "$X package allowance applied" + "+$Y to your plan total" in brand colour / no allowance → price only with extra spacing to CTA
    • Removed: info bubble (redundant with allowance impact text), priceNote prop, termsText prop, old horizontal specs grid, CoffinAllowance type
    • Added: CoffinColour type, allowanceAmount prop, onAddCoffin callback (replaces onContinue)
    • A11y: fixed heading hierarchy (price as <p> not <h5>, Product details as <h2>) — 0 violations
  • Stories: FullyCovered, PartiallyCovered, NoAllowance, NoColours, Minimal, PrePlanning, Loading
  • Playwright visual verification of all key scenarios
  • All quality checks passing (TypeScript, ESLint, Prettier)

Decisions made:

  • Allowance impact is computed from allowanceAmount vs coffin.price — no remaining balance tracking (out of scope)
  • Info bubble removed from detail page (redundant) — kept on CoffinsStep browsing page
  • Product details as single-column stacked dl (label above value) — more readable than grid

Open questions:

  • None

Next steps:

  • Continue page feedback: AdditionalServicesStep, SummaryStep, PaymentStep, ConfirmationStep
  • Retroactive review Phase 3 (organisms) still pending
  • Batch a11y fix (aria-describedby + aria-invalid) deferred

Session 2026-03-31a — CoffinsStep rewrite: grid-sidebar ecommerce layout

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • CoffinsStep complete rewrite — transformed from wide-form card grid into a two-column ecommerce layout matching the live site pattern:
    • Layout: grid-sidebar with viewport-locked independent scrolling (sidebar scrollbar on hover, grid always scrollable)
    • Sidebar: heading, conditional allowance info bubble, category menu with expandable subcategories (Collapse animation, chevron rotation), dual-knob price range slider with editable text inputs (commit on blur/Enter), sort by dropdown (popularity, price asc/desc), clear all filters link, save-and-exit link at bottom
    • Grid: 3-col coffin card grid with CoffinCard internal component — thumbnail hover preview (hover swaps main image, leave reverts), equal-height cards via flex layout, subtle background for white-bg product photos, "Most Popular" badge, colour count text
    • Navigation: card click fires onSelectCoffin(id) to navigate to CoffinDetailsStep — no Continue button
    • Pagination: 20 coffins per page max (pageSize prop, enforced via slice)
  • WizardLayout grid-sidebar update — wider sidebar (28%), overflowX: hidden (no horizontal scroll), overflowY: auto at all breakpoints, responsive viewport-lock (desktop only)
  • Removed: selectedCoffinId, CoffinsStepErrors, CoffinPriceRange, onContinue, loading props
  • Added: onSelectCoffin, CoffinSortBy, pageSize, minPrice/maxPrice, thumbnails/colourCount on Coffin, children on CoffinCategory

Decisions made:

  • Card click navigates directly to details (no selection + continue pattern) — matches ecommerce browsing UX
  • Categories support one level of subcategories — parent click loads all and expands children, child click refines
  • Price slider with editable text inputs — commits on blur/Enter to avoid per-keystroke re-renders
  • 20 coffins per page default — enforced in component via slice
  • Allowance info bubble is conditional (omit prop = no bubble) — some packages don't have coffin allowances

Open questions:

  • None

Next steps:

  • Continue page feedback: AdditionalServicesStep, SummaryStep, PaymentStep, ConfirmationStep
  • Retroactive review Phase 3 (organisms) still pending
  • Batch a11y fix (aria-describedby + aria-invalid) deferred

Session 2026-03-30e — Page feedback: VenueServices, Crematorium, Cemetery rewrites

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • VenueServicesStep (new page): Built step 7c — venue-specific service toggles after VenueDetailStep. Compact venue card at top, availability notices (info cards for unavailable services), AddOnOption toggles with maxDescriptionLines for "Read more" expansion, conditional tally showing total of selected priced services. Flow: VenueStep → VenueDetailStep → VenueServicesStep.
  • AddOnOption price colour fix: Changed from text.secondary (grey) to color="primary" (copper) for consistency with all other price displays in the system.
  • CrematoriumStep rewrite: Completely rewritten to match live site. Two variants based on funeral type:
    • Service & Cremation: stacked crematorium card (image top, details below) + witness Yes/No/Decide later ToggleButtonGroup
    • Cremation Only: stacked card + "Cremation Only" badge + "Included in Package" info notice, no witness question
    • Removed: multi-card selection grid, priority dropdown, special instructions, crematoriums array prop
    • Card iterated from compact horizontal → more generous horizontal → stacked layout based on user feedback
  • CemeteryStep rewrite: Rewritten to match live site:
    • RadioGroups replaced with ToggleButtonGroups (Yes/No/Not sure)
    • Card selection grid replaced with freetext search input (Input atom with search icon)
    • searchSlot prop for parent to inject Google Places or geocoding autocomplete
    • Removed static cemeteries array prop and CemeteryOption type
    • Progressive disclosure preserved: own plot → locate it; no plot → preference? → search

Decisions made:

  • Venue services belong on a separate page (not inline on VenueDetailStep or in a dialog) due to content volume per service (long descriptions, future images)
  • Crematorium is always pre-selected by provider — no multi-select needed, just confirmation
  • "Decide later" / "Not sure" options added as explicit third choice on questions where users may be uncertain — grief-sensitive pattern
  • Cemetery search should use real location search (Google Places style), not a static dropdown — we don't maintain a cemetery database
  • Stacked card layout (image top) works better than compact horizontal for single confirmation cards with long text

Open questions:

  • None

Next steps:

  • Continue page feedback: CoffinsStep, AdditionalServicesStep, SummaryStep, PaymentStep, ConfirmationStep
  • Retroactive review Phase 3 (organisms) still pending
  • Batch a11y fix (aria-describedby + aria-invalid) deferred

Session 2026-03-30d — Steps 8-15 consistency pass

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Consistency pass across Steps 8-15 — aligned all remaining wizard steps with the conventions established during the Steps 1-6 feedback iteration:
    • Subheading variant: CrematoriumStep, CoffinDetailsStep changed from body2body1 (match IntroStep/DateTimeStep centered-form convention)
    • Subheading spacing: CrematoriumStep, CemeteryStep, CoffinDetailsStep, AdditionalServicesStep, SummaryStep, PaymentStep all changed from mb: 4mb: 5
    • Section dividers: CrematoriumStep gained 3 <Divider> between question sections (crematorium → witness, conditional priority, special instructions)
    • Link atom: PaymentStep terms links and ConfirmationStep phone number changed from Box component="a"Link atom
    • CoffinsStep: No changes needed (wide-form layout has its own spacing convention)
  • Audited CrematoriumStep (15/20) and PaymentStep (16/20) — findings are pre-existing, not introduced by the pass
  • Visual verification: Playwright screenshots of CrematoriumStep, PaymentStep, ConfirmationStep, AdditionalServicesStep, SummaryStep — all pass

Decisions made:

  • Centered-form heading convention now formally: display3body1 subheading → mb: 5 gap (documented via consistent implementation across all 10+ centered-form steps)
  • Wide-form layout (CoffinsStep) keeps its own tighter spacing convention (body2 + caption, mb: 3)

Open questions:

  • Checkbox atom: PaymentStep uses MUI Checkbox directly — only form control without an FA atom wrapper. Create Checkbox atom?
  • aria-describedby/aria-invalid: All wizard steps lack error field association. Batch normalize fix recommended.
  • Retroactive review Phase 2.2 still pending (audit priority molecules: ServiceOption, AddOnOption, ProviderCardCompact)

Also completed (same session):

  • Checkbox atom created: New FA wrapper (forwardRef, displayName, stories: Default/States/TermsAgreement/Checklist). MuiCheckbox theme overrides (warm gold checked, focus ring, disabled muted). PaymentStep updated to import from atom.
  • Retroactive review Phase 2.2 complete: Audited ServiceOption (13/20), AddOnOption (14/20), ProviderCardCompact (15/20). Fixed:
    • AddOnOption: added aria-disabled when disabled (P1)
    • ProviderCardCompact: added maxLines={1} on name (P2)
    • ServiceOption: price uses labelLg variant instead of h6 + hardcoded fontWeight (P1)
  • Note: audit agents flagged false P0s (AddOnOption "double handler", ServiceOption "role collision") — verified both are correctly handled by existing stopPropagation and prop spread order in Card atom

Also completed (continued same session):

  • DateTimeStep: Swapped date TextField to Input atom (was missed in session 30b)
  • VenueStep rewrite: Simplified to click-to-navigate (like ProvidersStep) — removed Continue, selection state, inline detail, service toggles
  • VenueDetailStep (new page): Detail-toggles layout with scrollable left panel (ImageGallery, description, features grid, location map placeholder, address, informational service cards) and right panel (name, meta icons, price + offset note, Add Venue CTA, address, religion chips)
  • CoffinDetailsStep: Switched from centered-form to detail-toggles layout (image left, specs/price/CTA right)
  • WizardLayout detail-toggles overhaul: Viewport-locked with independent panel scroll, maxWidth 1200px centered, generous padding (px:5), back link inside left panel
  • WizardLayout list-map scroll fix: Removed top padding gap between nav and sticky search, heading scrolls away while search/filters stay pinned with divider border
  • ImageGallery molecule (new): Hero image + landscape thumbnail strip (4:3 ratio), hover to preview, click to select, keyboard accessible, wired into VenueDetailStep and CoffinDetailsStep
  • Checkbox atom (new): FA wrapper with warm gold theming, PaymentStep updated
  • Retroactive review Phase 2.2: Audited ServiceOption (13/20), AddOnOption (14/20), ProviderCardCompact (15/20), fixed P0/P1s

Next steps:

  • Continue page feedback from user: CrematoriumStep, CemeteryStep, CoffinsStep, AdditionalServicesStep, SummaryStep, PaymentStep, ConfirmationStep
  • Venue service selection step (new page — toggles moved from VenueDetailStep)
  • Batch a11y fix: aria-describedby + aria-invalid across all wizard steps (deferred per user)
  • Retroactive review Phase 3: organisms normalize + audit
  • Update component registry with new components (ImageGallery, Checkbox, VenueDetailStep)

Session 2026-03-30c — HomePage build + Chromatic + Story to Design setup

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Story to Design agent: Helped user install and run the story.to.design agent on Arch Linux (extracted from .deb, installed webkit2gtk dependency). Agent connects Storybook to Figma plugin.
  • Chromatic setup: Installed chromatic, published Storybook to Chromatic CDN. Set visibility to public for shareable links. Updated package.json chromatic script with --build-script-name=build:storybook.
  • HomePage component built: Full marketing landing page at src/components/pages/HomePage/
    • 7 sections: Hero, FuneralFinder widget (overlapping card), Partner Logos carousel, Features (4-card grid), Reviews/Testimonials, CTA Banner, FAQ accordion
    • Composes FuneralFinderV3 organism, Navigation + Footer via props
    • CSS-only infinite scroll logo carousel with prefers-reduced-motion
    • Feature cards with warm circular icon backgrounds
    • Full a11y: section landmarks, aria-labelledby, heading hierarchy
    • Grief-sensitive copy (quieter pass applied)
  • Quality gates run: /critique (32/40), /audit (16/20), /quieter (all pass)
  • Design iteration: Multiple rounds of visual refinement using Playwright screenshots
    • Reduced oversized typography (display1 → display3 for hero, display2 → h2 for sections)
    • Reserved serif fonts for hero + CTA only, sans-serif for all other section headings
    • Tightened spacing, reduced section padding
    • Feature cards: compact padding, warm circular icon backgrounds
    • Fixed unicode rendering bugs in copy

Decisions made:

  • Serif display fonts reserved for hero heading and CTA banner only — all other section headings use Montserrat (h2/h3)
  • Feature card icons wrapped in warm circular backgrounds (brand.50 bg, 56px diameter)
  • HomePage is a page component (not a template) — does not use WizardLayout
  • Chromatic published URL is the shareable Storybook link for stakeholders

Open questions:

  • Homepage is first-pass only — user will return to iterate later
  • Partner logo placeholder images need real assets
  • Hero image slot needs real photography
  • CTA banner click destination needs clarification (scroll to widget vs wizard entry)

Next steps:

  • Resume arrangement wizard page feedback from VenueStep (Step 7) onwards — Steps 1-6 done in previous session
  • Remaining pages: VenueStep, CrematoriumStep, CemeteryStep, CoffinsStep, CoffinDetailsStep, AdditionalServicesStep, SummaryStep, PaymentStep, ConfirmationStep

Session 2026-03-30b — Page feedback iteration (Steps 1-6) + DialogShell

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • IntroStep (Step 1): Static subheading (removed dynamic text change on selection), ToggleButtonGroup top-left alignment fix
  • ProvidersStep (Step 2): Heading reduced to h4 "Find a funeral director", SearchBar → location TextField with pin icon, FilterPanel moved below search right-aligned, map fill fix (height:100% → flex:1), hover scrollbar on left panel
  • VenueStep (Step 7): Same consistency fixes as ProvidersStep (h4, location icon, filter layout, map fill, results count format)
  • PackagesStep (Step 3): Removed budget filter + "Most Popular" badge + mobile Continue button. Added grouped packages pattern ("Matching your preferences" / "Other packages from [Provider]" with brand accent bars). onArrange replaces onContinue. Clickable provider card (onProviderClick). Heading to h4.
  • DateTimeStep (Step 6): display3 heading (centered-form consistency). Name fields swapped from MUI TextField to Input atom (external label, no clipping). Multiple preferred dates (up to 3, progressive disclosure). Removed service tradition/religion field. Dividers between question sections.
  • DialogShell atom: New shared dialog container (header + optional back + close, scrollable body, optional footer). Audited 17→19/20, P1s fixed (focus management, sx pattern).
  • FilterPanel refactored: Popover → DialogShell (centered Dialog with backdrop)
  • ArrangementDialog refactored: Now composes DialogShell for consistent chrome
  • PreviewStep + AuthGateStep removed: Consolidated into ArrangementDialog (D-E)
  • WizardLayout: ListMapLayout left panel gets thin scrollbar visible on hover

Decisions made:

  • Heading convention: display3 for centered-form pages, h4 for narrow panels (list-map, list-detail)
  • DialogShell is the standard popup container — all site dialogs compose it
  • Service tradition removed from DateTimeStep — flows from provider/package selection, confirmed on summary
  • "Service tradition" is the preferred terminology (not "religion" or "religious style")
  • Package grouping pattern: matched vs other, with brand accent bar section labels
  • FilterPanel uses Dialog (not Popover) for filter controls

Open questions:

  • Provider profile popup contents — user will provide details later
  • Heading convention (display3 vs h4) should be documented as a decision

Next steps:

  • Resume page feedback from VenueStep (Step 7) onwards — Steps 1-6 are done
  • Remaining pages to review: VenueStep, CrematoriumStep, CemeteryStep, CoffinsStep, CoffinDetailsStep, AdditionalServicesStep, SummaryStep, PaymentStep, ConfirmationStep
  • Homepage layout work (user priority — separate task)
  • Input atom clipping: audit remaining pages for MUI TextField → Input atom swap where labels clip

Session 2026-03-30a — Tooling upgrades + workflow evaluation

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Storybook addons: Added @storybook/addon-storysource (story source code panel) and @storybook/addon-a11y (real-time axe-core accessibility checks)
  • Playwright MCP: Installed @playwright/mcp and registered as MCP server. Enables Claude Code to take screenshots of Storybook for visual verification. Available from next session.
  • Workflow evaluation: Comprehensive analysis of current capabilities, identified core gap (Claude Code cannot see visual output), researched solutions
  • Antigravity research: Investigated Google Antigravity IDE for visual polish workflow. Installed on system. Created GEMINI.md project rules file. User decided to pause on Antigravity for now and continue with Claude Code + Playwright MCP approach.
  • GEMINI.md: Created project rules file for Antigravity IDE, mirroring key conventions from CLAUDE.md (token access, atomic tiers, no hardcoded values)

Decisions made:

  • Playwright MCP is the primary solution for visual verification gap — enables Claude Code to screenshot Storybook stories and self-correct visual issues
  • Antigravity available as secondary tool for visual tweaks but not primary workflow
  • Claude Code remains the primary tool for architectural/structural work due to custom skills, memory system, and accumulated project context

Open questions:

  • User has refinement feedback from previous session that was partially overlooked — to be addressed next session
  • Some items from the user's original feedback across 15 steps were missed when compressed into D-A through D-H decisions

Next steps:

  • Restart session to activate Playwright MCP
  • User to provide refinement feedback
  • Use Playwright MCP to visually verify changes as they're made
  • Address remaining P1s from previous session (radiogroup keyboard nav, dialog mobile fullscreen)

Session 2026-03-29e — Feedback iteration Batches 1 & 2

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Batch 1: Atom + Template Foundation
    • ToggleButtonGroup: label-to-options spacing mb: 1→2, top-align content flex-start, fixed selected border CSS specificity (added &.Mui-selected in grouped selector)
    • Heading standardisation: all 6 split-layout steps changed from h4display3 (ProvidersStep, PackagesStep, PreviewStep, VenueStep, CoffinsStep, CoffinDetailsStep) per D-A
    • DateTimeStep: normalised section gaps (scheduling fieldset mb: 3→4)
    • CrematoriumStep: added subheading for consistency, normalised witness section mb: 3→4
    • PackagesStep + DateTimeStep: fixed input label clipping (pt: 0.5 on TextField containers)
  • Batch 2: List-Map Layout Rework (D-B)
    • WizardLayout ListMapLayout: 420px fixed left column, flex: 1 right panel
    • Back link rendered inside left panel (not above split) — eliminates gap above map
    • LAYOUT_MAP type updated to accept backLink prop for list-map variant
    • ProvidersStep + VenueStep: sticky header (heading + search + filters pinned at top of scrollable left panel)
  • Batch 3: FilterPanel molecule + integration (D-C, D-F)
    • New FilterPanel molecule: Popover trigger with active count badge, Clear all, Done actions
    • ProvidersStep: inline chips → FilterPanel Popover alongside search bar
    • VenueStep: same pattern, filter chips in Popover
    • CoffinsStep (D-F): grid-sidebar → wide-form (full-width 4-col grid), filters in Popover
    • WizardLayout: added wide-form variant (maxWidth lg, single column) for card grids
    • FilterPanel stories: Default, WithActiveFilters, SelectFilters, CustomLabel
  • Batch 4: Step Simplifications (D-D, D-G)
    • ProvidersStep (D-D): removed selection state, Continue button, radiogroup pattern. Clicking a provider card triggers navigation directly. Stories updated.
    • CoffinDetailsStep (D-G): removed all customisation (handles, lining, nameplate, OptionSection, ProductOption/CoffinDetailsStepValues types). Simplified to coffin profile + Continue CTA. Changed from detail-toggles to centered-form layout. Stories simplified.
    • Updated CoffinDetailsStep index.ts re-exports
  • Batch 5: ArrangementDialog organism (D-E)
    • New ArrangementDialog organism: MUI Dialog with two internal steps
    • Step 1 (preview): ProviderCardCompact, package summary with sections/items/total, "What happens next" checklist
    • Step 2 (auth): SSO buttons, email, progressive disclosure for details, verification code, terms
    • Parent controls step state + auth form values; dialog has back arrow and close button
    • Stories: Default (full flow), AuthStep, AuthDetails, PrePlanning
    • PreviewStep + AuthGateStep kept for now, to be deprecated once dialog is wired in

Decisions made:

  • All 8 iteration decisions now implemented: D-A through D-H

Quality passes:

  • Ran 3 parallel audits (FilterPanel, ArrangementDialog, all reworked steps)
  • FilterPanel: 4 P0 + 2 P1 fixed (forwardRef, useId, aria-controls, dialog role, label prop, scroll)
  • ArrangementDialog: 3 P0 + 2 P1 fixed (forwardRef, focus management, aria-live, semantic tokens, Link atom)
  • Sticky headers: responsive padding match + top breathing room (pt: 2)
  • ProvidersStep: role="list", semantic map token
  • VenueStep: aria-label on search, semantic map token
  • CoffinDetailsStep: form wrapper added

Remaining P1/P2 (for next session):

  • VenueStep/CoffinsStep radiogroup arrow-key navigation (P1)
  • ArrangementDialog step number badge hardcoded font size/weight (P1)
  • ArrangementDialog mobile fullscreen treatment (P1)
  • CoffinDetailsStep centered-form width may be tight for coffin images (P2)
  • CTA alignment inconsistency VenueStep vs CoffinsStep/CoffinDetailsStep (P2)
  • ArrangementDialog missing story variants: verify, loading, errors, overflow (P2)

Open questions:

  • PreviewStep + AuthGateStep: deprecate/remove once ArrangementDialog fully replaces them in the flow
  • User to review all steps in Storybook and report any visual issues still present

Next steps:

  • User review in Storybook — note any remaining visual issues
  • Fix remaining P1s (radiogroup keyboard nav, dialog mobile fullscreen)
  • Wire ArrangementDialog into PackagesStep flow
  • Component registry updates for FilterPanel + ArrangementDialog

Session 2026-03-29c — Grooming pass: critique/harden/polish all 15 steps

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Full grooming pass across all 15 wizard steps — applied critique (UX design review), harden (edge cases/error states), and polish (visual alignment/copy/code) frameworks.
  • [P0] CrematoriumStep bug fix: <option> elements replaced with <MenuItem> — the MUI <TextField select> requires MenuItem children, not native option elements. The priority dropdown was non-functional.
  • [P1] Double-submit prevention: Added if (!loading) onContinue() guard to all form onSubmit handlers across steps 1, 5, 6, 7, 8, 9, 10, 11, 12, 14 (10 steps with forms). Prevents keyboard Enter re-submission during async loading.
  • [P1] Accessibility — aria-busy: Added aria-busy={loading} to all form elements so screen readers announce processing state.
  • [P1] Error colour normalisation: Replaced color="error" (red #BC2F2F) with var(--fa-color-text-brand) (copper #B0610F) across 7 steps (ProvidersStep, PackagesStep, VenueStep, CrematoriumStep, CemeteryStep, CoffinsStep, PaymentStep). Per D024, FA uses warm copper for errors rather than aggressive red.
  • [P2] Grief-sensitive copy: "Has the person died?" → "Has this person passed away?" (IntroStep). "About the person who died" → "About the person who has passed" (DateTimeStep).
  • [P2] Results count copy: "Showing results from X providers" → "X providers found" (ProvidersStep).
  • [P2] Empty state guidance: Added actionable guidance text to empty states in ProvidersStep, PackagesStep, VenueStep, CoffinsStep ("Try adjusting..." copy).
  • Steps that passed with no issues: PreviewStep (4), SummaryStep (13), ConfirmationStep (15) — all clean.
  • IntroStep critique score: 35/40 (Good) — full heuristic scoring completed as reference.
  • D034 implemented: Form error styling unified to copper end-to-end. MuiOutlinedInput error border/ring, MuiFormHelperText error text, MuiFormLabel error state (neutral per D024), ToggleButtonGroup error border — all now use copper (#B0610F). palette.error.main remains red for non-form uses.
  • Tagged v0.1-wizard-groomed — safe rollback point before user review checkpoint.
  • Roadmap documented in persistent memory (project_roadmap.md) with 9 phases, checkpoint gates, and guiding principles.

Decisions made:

  • D034: Form error styling uses copper across theme (MuiOutlinedInput, MuiFormHelperText, MuiFormLabel, ToggleButtonGroup). palette.error.main stays red for destructive buttons/system alerts.
  • User review checkpoint required before integration — tagged v0.1-wizard-groomed as rollback point.

Open questions:

  • None

Next steps:

  • Feedback iteration Batch 1 (next session)

Session 2026-03-29d — User review checkpoint + iteration planning

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • D034 implemented: Form error copper styling unified across theme (MuiOutlinedInput, MuiFormHelperText, MuiFormLabel, ToggleButtonGroup).
  • Tagged v0.1-wizard-groomed — safe rollback point before feedback iteration.
  • Roadmap documented in persistent memory (project_roadmap.md).
  • User review checkpoint completed. User reviewed all 15 steps in Storybook and provided detailed feedback across every step.
  • Iteration plan created at .claude/plans/zany-jingling-lynx.md — 7 batches, ~6-8 sessions estimated.
  • All 8 design decisions resolved (D-A through D-H): display3 headings everywhere, 420px fixed left column, Popover filters, click-to-navigate (no Continue buttons on selection steps), two-step modal for preview+auth, full-width coffin grid, remove coffin customisation for now, defer map pins.

Key feedback themes:

  • ToggleButtonGroup: spacing tight, content misaligned, selected border not showing (CSS specificity bug)
  • List-map layout: needs fixed-width left column, scrollable list with fixed header, no gap above map
  • New FilterPanel component needed (reusable across providers, venues, coffins)
  • PreviewStep + AuthGateStep consolidation into two-step modal
  • CoffinDetailsStep: remove customisation, simplify to details + CTA
  • Several copy and spacing issues across steps

Decisions made:

  • D-A: display3 heading everywhere (brand warmth)
  • D-B: 420px fixed left column in list-map layout
  • D-C: FilterPanel uses Popover (MVP), Drawer for mobile later
  • D-D: Click-to-navigate on ProvidersStep (remove selection state + Continue)
  • D-E: Remove standalone PreviewStep + AuthGateStep, use two-step ArrangementDialog
  • D-F: CoffinsStep full-width grid with FilterPanel button
  • D-G: Remove coffin customisation, note as future enhancement
  • D-H: Map pins deferred

Open questions:

  • None

Next steps:

  • Start Batch 1: Atom + Template Foundation (ToggleButtonGroup fixes, heading standardisation, spacing normalisation)
  • Plan file: .claude/plans/zany-jingling-lynx.md

Session 2026-03-29b — Wizard steps 5-15 (complete flow)

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Step 5 — AuthGateStep: Centered-form, 3 progressive sub-steps (SSO/email → details → verify). Phone optional when email-only. Benefit framing ("Save your plan"). Audit 18/20, P1 fixed (responsive name fields).
  • Step 6 — DateTimeStep: Centered-form, two fieldset sections (name + scheduling). Grief-sensitive labels ("Their first name"). Date/time radios + Autocomplete for religion (22 options). Save-and-exit CTA.
  • Step 7 — VenueStep: Venue card grid (1-2 col) with search/filters. Inline venue detail (Collapse) with features + religions. Service toggles (photo, streaming, recording) via AddOnOption. VenueCard extended with selected + ARIA passthrough props.
  • Step 8 — CrematoriumStep: Single crematorium confirmation card (most common) or multi-card grid. Witness question personalised with deceased name. Special instructions via progressive disclosure textarea.
  • Step 9 — CemeteryStep: Triple progressive disclosure (have plot? → choose cemetery? → card grid). Dependent field resets. "Provider can arrange this" shortcut.
  • Step 10 — CoffinsStep: 3-col card grid with category/price filters. "Most Popular" badge (Rec #10). Pagination support. Australian terminology ("coffin").
  • Step 11 — CoffinDetailsStep: Coffin profile (image + specs grid) + 3 product option RadioGroups (handles, lining, nameplate) with branded selected state. Price: "Included" or "+$X".
  • Step 12 — AdditionalServicesStep: Merged from optionals + extras (Rec #2). Section 1: complimentary (dressing, viewing, prayers, announcement). Section 2: paid (catering, music, bearing, newspaper). Multi-level progressive disclosure.
  • Step 13 — SummaryStep: Accordion sections with edit buttons, dl/dt/dd definition lists, total bar (aria-live), share button. Pre-planning: "Save your plan" / at-need: "Confirm".
  • Step 14 — PaymentStep: ToggleButtonGroup for plan (full/deposit) + method (card/bank). PayWay iframe slot. Bank transfer details display. Terms checkbox. Security reassurance.
  • Step 15 — ConfirmationStep: Terminal page, no back/progress. At-need: "submitted" + callback info. Pre-planning: "saved" + return-anytime. Muted success icon, next-steps links.

Approach: Same as previous session — first pass build + quick audit + P0/P1 fixes only. Grooming pass comes later.

  • Layout fixes: Corrected 3 steps that had wrong WizardLayout variants:
    • VenueStep: centered-form → list-map (venue cards left, map slot right — matches ProvidersStep)
    • CoffinsStep: centered-form → grid-sidebar (filter sidebar left, card grid right)
    • CoffinDetailsStep: centered-form → detail-toggles (coffin profile left, option selectors right)

Decisions made:

  • VenueCard extended with selected/ARIA props (same pattern as ProviderCard from step 2)
  • CoffinDetailsStep uses OptionSection helper component for DRY RadioGroup rendering
  • SummaryStep uses Accordion not Paper for sections (per spec's collapsible requirement)
  • PaymentStep uses slot pattern for PayWay iframe (cardFormSlot prop)

Open questions:

  • None

Next steps:

  • All 15 wizard steps are now first-pass complete (steps 1-15) with correct layouts
  • Grooming pass: /critique, /harden, /polish, /normalize across all steps
  • Retroactive review backlog still pending
  • Integration: routing, state management, GraphQL queries

Session 2026-03-29 — Wizard Phase 0 through Phase 4 (foundation + steps 1-4)

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Phase 0 — Foundation components:
    • WizardLayout template: 5 layout variants, nav slot, sticky help bar, back link, progress stepper + running total, <main> landmark
    • Collapse atom: thin MUI Collapse wrapper with unmountOnExit
    • ToggleButtonGroup atom: exclusive single-select with fieldset/legend a11y, FA brand styling
  • Phase 1 — IntroStep (step 1):
    • Centered-form layout, forWhom + hasPassedAway with progressive disclosure
    • Auto-sets hasPassedAway="no" for "Myself", grief-sensitive copy adapts
    • Audit 18→20/20 after fixes (<main> landmark, <form> wrapper)
  • Phase 2 — ProvidersStep (step 2):
    • List-map split: provider cards w/ radiogroup + search + filter chips (left), map slot (right)
    • ProviderCard extended with selected prop + HTML/ARIA passthrough
    • Audit 18/20, P1 fixed (radio on focusable Card)
  • Phase 3 — PackagesStep (step 3):
    • List-detail split: ProviderCardCompact + budget filter + ServiceOption list (left), PackageDetail (right)
    • "Most Popular" badge, mobile Continue button
  • Phase 4 — PreviewStep (step 4):
    • Informational review, "What happens next" numbered checklist
    • Pre-planning variant shows "Explore other options" tertiary CTA

Approach: First pass of all steps (build + quick audit + P0/P1 fixes only), then grooming pass (critique/harden/polish/normalize) across the full flow.

Decisions made:

  • Templates at src/components/templates/, Pages at src/components/pages/
  • ProviderCard/ServiceOption: ARIA passthrough for radiogroup patterns
  • First-pass approach: build all steps, then groom — user confirmed

Open questions:

  • None

Next steps:

  • Step 5 (Auth Gate) — centered-form layout, account creation/login
  • Continue through remaining steps (6-18)
  • Retroactive review backlog still pending

Session 2026-03-27b — Retroactive review Phase 1 + 2.1, wizard planning

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Retroactive review (morning housekeeping):
    • Phase 1.1: Atoms normalize — 3 fixes (Input spacing, Card focus token, unused import)
    • Phase 1.2: Atom audits — Button 20/20, Input 20/20, Card 18→20/20 (2 P1 fixes: keyboard activation + responsive padding)
    • Phase 2.1: Molecules normalize — 7 fixes (StepIndicator timing/spacing/font, ProviderCardCompact star colour + icon size)
  • Wizard flow documentation review:
    • Read flow-spec.md (854 lines), flow-definition.yaml (616 lines), 3 representative step YAMLs
    • Saved reference memories for flow documentation structure and project context
  • Layout analysis:
    • Analysed 5 layout reference images in /documentation/layouts/
    • Identified 5 layout variants: Centered Form, List+Map, List+Detail, Grid+Sidebar, Detail+Toggles
    • Mapped each layout to specific wizard steps
    • Saved layout reference to memory
  • Implementation plan:
    • Created comprehensive plan at .claude/plans/validated-brewing-matsumoto.md
    • Phase 0 (foundation): WizardLayout template, Collapse atom, ToggleButtonGroup atom
    • Phases 17: Steps in build order with component dependencies
    • ~11 new components, ~16 sessions estimated
    • Plan approved by user, updated with layout variants

Decisions made:

  • Step pages live at src/components/pages/ (new atomic tier)
  • CardSelectionGrid extracted after step 3 (not upfront)
  • Build from specs, Figma for structural shape only
  • Steps are pure presentation (props in, callbacks out) — no routing/state/GraphQL yet

Open questions:

  • Hardcoded fontWeight on price typography varies across molecules — still unresolved from normalize

Next steps:

  • Start Phase 0: Build WizardLayout template (5 variants), Collapse atom, ToggleButtonGroup atom
  • Then Phase 1: Step 1 (Intro) — first wizard step

Session 2026-03-27b (earlier) — Retroactive review Phase 1 + 2.1

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Phase 1.1 — Atoms normalize: Scanned all 11 atoms across 7 dimensions. Fixed 3 issues: Input raw px spacing → MUI units, Card focus-visible token → CSS var, removed unused Theme import.
  • Phase 1.2 — Atom audits (high priority): Button 20/20, Input 20/20, Card 18/20 → fixed 2 P1s → 20/20.
    • Card P1 fix: Added Enter/Space keyboard activation for interactive cards (WCAG 2.1.1)
    • Card P1 fix: Responsive padding — 16px mobile / 24px desktop (convention alignment)
  • Phase 2.1 — Molecules normalize: Scanned all 8 molecules across 7 dimensions. Fixed 7 issues:
    • StepIndicator: transition 300ms → 150ms, raw px → MUI spacing, borderRadius → token, mobile font 10px → 11px (D020 floor)
    • ProviderCardCompact: star colour → warning.main (match ProviderCard), meta icon 16px → 14px (match tier)

Decisions made:

  • None (all fixes are convention enforcement, no new decisions)

Open questions:

  • Hardcoded fontWeight on price typography varies across molecules (500 vs 600) — intentional design variation or normalize?
  • Remaining atom audits (Typography, Badge, Chip, Switch, Radio, IconButton, Divider, Link) — all low/medium priority wrappers, likely 20/20. Run if time permits.

Next steps:

  • Phase 2.2: Audit priority molecules (ServiceOption, AddOnOption, ProviderCardCompact)
  • Phase 3: Organisms normalize + audit (Navigation, ServiceSelector, FuneralFinderV3)
  • Phase 4: Cross-cutting (adapt, typeset, preflight)
  • New component work as user directs

Session 2026-03-27a — Workflow improvement (5-phase infrastructure upgrade)

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Phase 1A: Archived session log — moved 25 old sessions to docs/memory/archive/sessions-through-2026-03-26.md, trimmed active log from 1096 to 91 lines
  • Phase 1B: Documented token access convention as D031 — theme.palette.* for semantic tokens, var(--fa-*) for component tokens. Fixed Badge.tsx colour map inconsistency (unified to CSS vars). Updated component-conventions.md.
  • Phase 2: Added ESLint v9 (flat config) + Prettier. Installed eslint, @eslint/js, typescript-eslint, eslint-plugin-react, eslint-plugin-react-hooks, eslint-plugin-jsx-a11y, eslint-config-prettier, prettier. Ran initial format pass across all 54 .tsx files. Fixed 5 empty interface warnings (Switch, Radio, Divider, IconButton, Link). Added lint, lint:fix, format, format:check scripts.
  • Phase 3: Created 7 new impeccable-adapted skills: /polish, /harden, /normalize, /clarify, /typeset, /quieter, /adapt. Downloaded Vercel reference docs (web-design-guidelines, react-best-practices). Updated /audit and /review-component with optional Vercel references. Total skills: 19.
  • Phase 4: Added Husky v9 + lint-staged for pre-commit automation. ESLint + Prettier auto-run on staged files. Updated /preflight skill with ESLint and Prettier checks (now 8 checks total).
  • Phase 5: Added Vitest v4 + jsdom + @testing-library/react. Created vitest.config.ts and test setup. Created /write-tests skill for test generation guidance. Added test and test:watch scripts. Note: @storybook/test-runner deferred — requires Storybook 10+ (we have 8).

Decisions made:

  • D031: Token access convention — theme accessors for semantic, CSS vars for component (see decisions-log)
  • ESLint story files exempt from react-hooks/rules-of-hooks and no-console (Storybook render pattern)
  • Empty interface extends changed to type = for ESLint compliance (5 wrapper atoms)
  • Storybook test-runner deferred until Storybook upgrade to v10
  • Prettier printWidth set to 100 (matching observed code style)

Also completed:

  • Created docs/reference/component-lifecycle.md — 10-stage quality gate sequence (build → QA → polish → present → iterate → normalize → preflight → commit)
  • Created docs/reference/retroactive-review-plan.md — plan to review 30+ existing components (~3.5 sessions)
  • Updated /build-atom, /build-molecule, /build-organism to include internal QA stages automatically
  • Added lifecycle reference as CLAUDE.md critical rule #8

Open questions:

  • Storybook upgrade to v10 — would unlock @storybook/test-runner for CI test execution
  • FuneralFinder version decision (v1/v2/v3) — needed before retroactive review of organisms
  • Review depth — P0/P1 only (faster) or include P2?

Next steps (pick up in next session):

  • Start retroactive review: /normalize atoms/audit high-priority atoms
  • Interleave with new component work if preferred
  • Use /write-tests on interactive components as time permits

Session 2026-03-26f — FuneralFinder v3 build + polish

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Created FuneralFinderV3 — clean vertical form based on user's Figma layout (5919:29445), restyled to FA design system
  • Two side-by-side StatusCards (Immediate Need default-selected / Pre-planning), stack on mobile
  • Standard card container (surface.raised, card shadow) — initially built with glassmorphism, user redirected to standard palette
  • Overline section labels (text.secondary) for "How Can We Help", "Funeral Type", "Location"
  • Standard outlined fields (white bg, neutral border, brand border on focus, no focus ring per user request)
  • Location input with LocationOnOutlined pin icon
  • CTA "Find Funeral Directors →" always active — validates on click, scrolls to first missing field
  • Dividers after header and before CTA for visual rhythm
  • Funeral type options: same as V2 + "Show all options"
  • WAI-ARIA roving tabindex on radiogroup, aria-labelledby via React.useId()
  • Semantic tokens throughout (border-brand, surface-warm, text-brand, interactive-focus, text-disabled)
  • Error messages conditionally rendered in aria-live regions (brand copper tone — gentle validation)
  • First pass scored Critique 33/40, Audit 13/20 → iterated on all findings → re-audit 18/20
  • Polish pass: revised copy, fixed spacing, defaults, label rename

Decisions made:

  • Status cards replace V2's step-circle + dropdown — simpler, more visual, side-by-side on desktop
  • Standard design system styling (user clarified Figma was for structure only, not colour scheme)
  • "Immediate Need" selected by default — most common use case, status error essentially unreachable
  • Section renamed from "Current Status" (programmatic) to "How Can We Help" (warm, human)
  • Copy: "A recent loss or one expected soon" — "expected soon" differentiates from pre-planning gently
  • Copy: "Planning ahead for yourself or a loved one" — "planning ahead" reinforces non-urgency
  • No sequential unlock — all fields accessible immediately
  • CTA always active — validates on click, scrolls to missing field
  • Form simplified to 3 fields (status, funeral type, location) vs V2's 4
  • Focus ring suppressed on Select/Input per user requirement — status cards retain focus-visible
  • Error colour uses text.brand (copper) not feedback.error (red) — intentional for funeral context

Open questions:

  • Location autocomplete integration still pending across all versions
  • Decision: V1 vs V2 vs V3 for production

Next steps:

  • User to review final V3 in Storybook
  • If V3 chosen: location autocomplete, possible further refinements

Session 2026-03-26e — FuneralFinder v2 polish + consistency fixes

Agent(s): Claude Opus 4.6 (1M context)

Work completed:

  • Fixed location input styling to match select fields (border color, hover, disabled, error states)
  • Added active prop to StepCircle — step 1 uses brand-500 primary fill by default
  • Aligned Input with full selectSx overrides (bgcolor, disabled opacity+dashed, error border)
  • Investigated systemic Input vs Select visual differences — confirmed issue is component-local overrides, not theme

Decisions made:

  • Custom field styling kept within FuneralFinderV2 (intentional divergence from theme for this component's design)
  • Step 1 circle uses primary fill since it's always active — visual "start here" signal

Next steps:

  • v2 is feature-complete and polished — ready for user review in Storybook
  • Decision pending: v1 vs v2 for production
  • If v2 chosen: add location autocomplete, write flow logic reference doc

Session 2026-04-20 — Demo slice hosting end-to-end

Agent(s): Claude Opus 4.7 (1M context) + workstation server agent

Work completed:

  • Scaffolded src/demo/ (shared fixtures + Zustand basket + URL sync) and src/demo/apps/arrangement/ (Providers → Packages → Comparison flow) consuming existing page components with mocked data
  • Per-slice Vite build via vite.demo.config.ts (--mode <slice> selects app folder + base prefix + outDir)
  • Wrote nginx/parsons-demos.conf (server agent fixed regex ordering bug — asset cache regex must come before SPA fallback regex per nginx first-match rule)
  • Wrote scripts/deploy-demo.sh (rsync wrapper, slice-aware, gitignored due to server-specific paths)
  • Wrote docs/reference/client-demo-deploy.md (server runbook)
  • Wired assetUrl() helper at src/demo/shared/assets.ts — wraps import.meta.env.BASE_URL so public-asset URLs resolve under /<slice>/ in production but stay flat in dev
  • Added new-demo-slice skill at .claude/skills/new-demo-slice/SKILL.md
  • Added npm scripts: demo:dev, demo:build, demo:publish
  • 7 demo providers across verified/tier3/tier2 with real venue photography from brandassets/images/venues/
  • Verified-provider packages restructured to share canonical 9-item Essentials per FA convention (factory helpers in packages.ts); only Optionals + Extras vary per provider/package
  • ProviderCard logo objectFit: cover → contain so wide logos don't crop
  • Demo deployed and live at https://parsons.tensordesign.com.au/arrangement/ behind basic auth (user: client, htpasswd at /config/nginx/.htpasswd-parsons)

Decisions made:

  • Use TrueNAS /mnt/config/docker/parsons-demos/ as document root (boot-pool /srv/ not persistable on TrueNAS SCALE)
  • Single htpasswd covering all slices (split per-slice if/when client-specific auth is needed)
  • nginx auth covers root path too — change to auth_basic off; if landing page needs to be public
  • Did NOT prune publicDir bloat (1.1GB build) — user waved off, storage isn't constrained
  • Server-side: rsync target uses TrueNAS truenas SSH alias as user truenas_admin (UID 950 = swag's container user, :ro mount)

Open questions:

  • None blocking — everything live and working

Next steps:

  • ProviderMap on ProvidersStep — currently empty mapPanel slot. Build as new organism via /build-organism ProviderMap (needs Leaflet/MapLibre + lat/lng added to provider fixtures + verified-vs-unverified pin treatment), then wire into demo
  • PackageDetail "Added ✓" state — Compare button silently dedupes when basket already contains current selection; should show stateful feedback. New session via /build-molecule or /polish on PackageDetail
  • (Low priority) Asset prune — switch publicDir to allowlist if deploy size becomes annoying