The top-left "Package Comparison" info card was sticky-left, originally
to mirror the row-label column. On horizontal scroll it pinned over the
leftmost package column — which after D040 is the recommended one —
hiding its badge and CTA.
Drop the sticky positioning on the info card; let it scroll naturally
with the package headers. The row-label column below stays sticky on
its own, so scanning "Allowance for Coffin" etc. while scrolling right
still works. Removed the now-unused Z_HEADER_ROW constant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Absence of data means different things for verified vs unverified:
- Verified providers itemise everything; a missing Optional/Extra is an
explicit "Not Included" and a missing Essential is an (unlikely) gap.
- Unverified providers have scraped/estimated data; a missing cell just
means "we don't know."
Route the two via `pkg.provider.verified` in lookupValue — unverified
packages now render missing cells as the existing "Unknown" + info-icon
treatment (already used by explicit `type: 'unknown'` cells). Verified
paths unchanged.
Drop the "Some providers have not provided an itemised pricing
breakdown" footer — the "Unknown" treatment is self-explanatory, and
the disclaimer was tied to the em-dash convention that no longer applies.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Make Arrangement + Compare buttons stacked vertically on xs because
flexDirection was responsive. At size="large" (48px) the labels didn't
fit a ~360px mobile column side-by-side, which forced the stack. Drop
to size="medium" (40px) on xs and keep flexDirection fixed to row — the
two CTAs now sit beside each other on every viewport.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ComparisonPage's `recommendedPackage` prop was never wired in the
demo — users only saw their basket contents. Now always surface a
default recommended package (parsons:deluxe) as an extra column, deduped
against the basket so it never appears twice.
Basket mechanics are unchanged: the 3-package cap counts user selections
only, and the recommended is layered on top as an editorial suggestion.
The empty state only renders when there is genuinely nothing to show —
since the recommended is static, it's effectively defensive for a future
state where resolution could fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vite's default envDir is the `root` option, which here points into
`src/demo/apps/<slice>/` — no env files live there, so the Google Maps
API key from `.env.local` never made it into the production bundle and
ProviderMap silently fell back to its "no API key" empty state on
parsons.tensordesign.com.au. Set envDir to the repo root so `.env` and
`.env.local` are picked up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clicking a package in the "Other packages from [Provider]" section set
the selectedPackageId correctly but the detail panel stayed on the empty
state — `selectedPackage` was derived only from the primary `packages`
array, so secondary-list ids never resolved. Now falls back to the
secondary list when the primary miss.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dash fix: PackageDetail no longer renders em-dash placeholder rows for
Optionals. The 10 fixture entries that used `treatment: 'unknown'` are
removed; the Optional type narrows to IncludedTreatment only; the dead
'unknown' branch in optionalsForStep/optionalsForComparison is gone.
The rule going forward: an Optional/Extra exists on a package when the
package actually offers it. ComparisonTable already handles absence
correctly via buildMergedSections + lookupValue → 'Not Included'.
Package distribution expanded (max 5 per provider, randomised):
parsons: 3 → 5 (+ traditional-burial, memorial-service)
rankins: 2 → 3 (+ direct-cremation)
killick: 2 → 3 (+ traditional-burial)
mackay: 1 → 4 (+ premium, simple, memorial-service)
mannings: 1 → 4 (+ premium, simple, direct-cremation)
Each new package follows the canonical-essentials rule: same 9 Essentials
line items, only prices/treatments vary. Optionals + Extras composed per
package. Mackay + Mannings comparison maps rewritten from single-package
to the indexed-array pattern used by parsons/rankins/killick, and their
bundle entries now slice(0,1)/slice(1) so an "Other packages from this
provider" section appears.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The unverified-tier "similar packages" section previously rendered a list
of NearbyPackageCards — one per package. Swap to MiniCard, showing the
provider itself: image, verified badge, location, rating, "From $X".
2-col on sm+, 1-col on xs, capped at 4. Heading dropped "nearby" to
"Similar packages from verified providers".
Data shape renamed NearbyVerifiedPackage → NearbyVerifiedProvider;
`verified` is implicit (the section is verified-only by definition).
Callback renamed onNearbyPackageClick → onNearbyProviderClick, routing
directly on provider id. Demo fixture now derives the list from the
main providers fixture (filtered to verified + imageUrl).
NearbyPackageCard is now orphaned — kept in place pending registry
cleanup in a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CompareBar: add Mobile and MobileSingle stories (viewport: mobile1)
so the xs-only collapse / auto-peek behaviour is discoverable in
Storybook without setting up a live basket.
- PackageDetail InCart story description updated to match the final
toggle pattern (was stale from the earlier inert-pill attempt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Single-Paper collapse: dropped the two-Slide scheme for a single
right-anchored Paper on mobile. The middle content (status text +
Compare button) animates to max-width:0 while the pill's right edge
stays fixed, so the whole thing appears to retract to the corner as
one unit rather than two stacked transitions.
- Collapse chevron: grey-filled circle (neutral-200 bg, neutral-300
hover) that swaps between right-chevron (collapse) and left-chevron
(expand) based on state. Always rendered — the IconButton stays in
the layout so the icon swap happens in place.
- Collapsed badge: shows just the count ("1") instead of "1/3" so it
reads as a circle at mini size. Min-width pinned to badge-height-md
so any digit (1–3) renders circular. Expanded state keeps "N/3".
- z-index fix: CompareBar dropped from snackbar (1400) → drawer (1200);
MapProviderDrawer raised from 3 → modal (1300). The drawer now
visually covers the CompareBar when a pin or cluster is active on
the mobile map view. CompareBar returns as soon as the drawer is
dismissed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mobile users can now tap a right-chevron on the expanded bar to slide
it out; a mini peek-pill anchored bottom-right replaces it, showing
just the fraction badge (N/3) + a left-chevron to expand. Tap
anywhere on the mini-pill to bring the full bar back.
Packages-being-tallied feedback: when a new package is added while
the bar is collapsed, the full bar auto-peeks back in for 3 seconds,
then slides out again. The user sees the count update register
without having to tap to expand.
Two stacked Slide wrappers handle the direction-aware transitions:
- Full bar slides up from below (initial show + peek re-entry).
- Mini-pill slides in from the right (on user-triggered collapse).
Collapse state resets to expanded when the basket empties, so the
next fresh fill starts with the friendly default visible.
Desktop (md+) stays permanently expanded — the collapse chevron
doesn't render; there's plenty of space. Collapsing is a mobile-only
affordance.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous offset used theme.spacing(9) assuming 8px MUI default base —
but the FA theme uses a 4px base, so spacing(9) was only 36px and
the pill still sat 3px below the ~40px HelpBar. Bumped to spacing(16)
(64px) for a clean 25px gap above HelpBar on both mobile and desktop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Raise bottom offset from theme.spacing(3) (24px) → theme.spacing(9)
(72px) so the pill clears the sticky HelpBar with ~16px breathing.
- Centering: swap `left:50%; transform: translateX(-50%)` for
`left:0; right:0; mx:auto; width:fit-content`. Slide (the wrapper)
animates via transform, which was clobbering our centering transform
and leaving the bar's left edge at the viewport centre instead of
its centre (measured 171px off-centre pre-fix). Auto-margin
centering doesn't fight Slide's animation.
- Mobile sizing: responsive step-down on xs —
- Badge: large (32px) → medium (26px)
- Typography: body1 (16px) → body2 (14px)
- Button: medium (40px) → small (32px)
- Container: gap 2→1.5, px 3→2, py 1.5→1
md+ keeps the larger sizes from the earlier bump.
Rejected alternatives: slide/peek collapsed-state (adds interaction
cost and hides state behind a tap — bad for FA's grief-sensitive
audience); full-width bottom bar (loses the "floating reminder" pill
character).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useBasketUrlSync was treating every searchParams change as a URL→Store
authority event. In practice this meant Back from PackagesStep to the
providers map landed on `/` (no `?compare=...`) and the hook called
setAll([]) — wiping the basket.
Changed the semantics so that when an in-app navigation drops the
`?compare=` param but the store still has items, we re-attach the
store's keys to the new URL rather than clearing the store. Shared
links still hydrate the store on initial mount, and the subscribe
that writes store→URL on basket changes is untouched.
With this, a user can:
- Add a package on Provider A's page.
- Back to the providers map (CompareBar stays, URL still shows
`?compare=parsons:everyday`).
- Navigate into Provider B's page (URL carries the Parsons item forward).
- Add B's package (URL now `?compare=parsons:everyday,rankins:standard`).
- Hit Compare with 2/3 basket.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Cluster rows: mirror desktop ClusterPopup's alignment recipe — add
justifyContent: 'center' on the verified-icon slot and an explicit
lineHeight: 1.25 on the name Typography. Before: the slot's 1.25em
computed off the inherited 16px while the body2 name computed off
14px, producing a ~2.5px mismatch that put the tick slightly above
the name's top line. Now the slot's vertical centre matches the
name's line-box.
- Drawer header padding: px 1.5 → 2 so the "N providers in this area"
heading aligns horizontally with the row content beneath it
(rows use px: 2). Previously the heading sat slightly further left.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Label stays "Compare" regardless of inCart. Only change between
default and added: a trailing check icon (endIcon) appears when the
package is in the basket. aria-pressed + aria-label still carry the
state for screen readers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the earlier inert selected-state treatment. Now:
- Button keeps its default soft/secondary chrome in both states — no
separate brand-tinted visual.
- When `inCart=true`, a leading CheckRoundedIcon is added and the
label swaps from "Compare" to "Added".
- Button remains clickable; `onCompare` is invoked in both states.
Caller treats it as a toggle — add when absent, remove when present.
- aria-pressed reflects the state for SR users; aria-label spells
"Add to comparison" / "Remove from comparison" explicitly.
Demo route swaps `basket.add()` for `basket.toggle()` on the handler
so a second click removes the package from the comparison basket.
Simpler visual (less space, one chrome to maintain) and a clearer
interaction — the user can undo directly from the detail panel
rather than hunting for CompareBar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a package is already in the comparison basket, the Compare button
swaps to a "In comparison" selected-state: soft brand-50 fill +
brand-300 border + brand-700 text + leading check icon. Technically
disabled (aria-disabled + no onClick) but sx-overrides the default
greyed Mui-disabled look so it reads as "selected/added," not
"unavailable."
Pattern: e-commerce "Added to cart" state. Removal happens via the
floating CompareBar (already owns basket mutation), not this button —
keeps the responsibility split clean.
API:
- PackageDetail: new `inCart?: boolean` prop.
- PackagesStep: forwarded as `isSelectedPackageInCart?: boolean`.
- Demo route (Packages.tsx): computes `basket.has(key)` for the
current selection and passes it through.
Storybook: new PackageDetail story `InCart` alongside the existing
`Default` and `CompareLoading` states.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Badge: medium (26px) → large (32px) — matches the visual weight of
the now-body1 status text and medium Compare button. Badge atom's
large variant uses the --fa-badge-*-lg tokens (height 32px, font-size
and icon-size stepped up together).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Badge: small (22px) → medium (26px)
- Typography: body2 (14px) → body1 (16px)
- Compare Button: small (32px) → medium (40px)
- Container padding: px 2.5 → 3, py 1.25 → 1.5 for proportional breathing
- maxWidth: md 420 → 460 to accommodate larger Button + padding
- gap: 1.5 → 2 between elements
- Drop CompareArrowsIcon from the Compare button — the label alone
reads clearly at the new size and removes visual noise
Positioning unchanged: the fixed bottom-centre pill defaults stay in
the component (it IS a floating compare pill — that's definitional),
with the caller's `sx` merged after so any page can override
(`sx={{ bottom: 96 }}`) when it needs to dodge another fixed element.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts the sort Button + anchored Menu pattern into a reusable molecule.
ProvidersStep previously had the same trigger-button + menu-items-with-
selected-state inline twice (mobile-map floating strip + desktop sticky
bar) with a minor variant split — "Sort by" compact label on mobile vs
"Sort: <label>" verbose label on desktop with a swap-vertical icon.
API: value (controlled string) + onChange + options array +
variant ('compact' | 'verbose') + sx (trigger chrome). Non-generic
string typing keeps the forwardRef clean; callers with typed unions
cast at the boundary (trivial, one line). Anchor state is fully
internal to the molecule.
Four Storybook stories (Compact, Verbose, Bare, TwoOptions) exercise
both variants, the bare-no-chrome default, and a non-provider options
set to demonstrate reuse.
Not tied to a product-specific sort domain — intended for VenueStep,
CoffinsStep, or any future page needing a sort menu. ProvidersStep's
SORT_OPTIONS stays in the page as the caller's domain data; the
molecule just renders whatever options it's given.
ProvidersStep cleanup: drops the Button, Menu, MenuItem, SwapVertIcon
imports and the sortAnchor state that only supported the inline
version.
Verified: compact label on mobile ("Sort by"), verbose label on
desktop ("Sort: Recommended" + swap icon), menu anchoring,
selected-state, aria-label all match pre-extraction behaviour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts HelpBar out of WizardLayout's internal scope into
src/components/molecules/HelpBar/ so both WizardLayout and pages that
bypass WizardLayout's chrome (currently: ProvidersStep's mobile
map-first branch) render an identical sticky-footer.
Before: WizardLayout had an internal HelpBar with the right styling
(sticky, responsive px, phone format helper); ProvidersStep's
mobile-map branch hand-rewrote the footer inline and had drifted —
missing position: sticky, missing the md:4 responsive px, hard-coded
phone number bypassing the prop default. This consolidates both to
one source of truth.
Props: `phone?` (defaults to FA's support number, spaces preserved
in label, stripped in tel href) + `sx?` for caller chrome overrides.
Two Storybook stories (Default, CustomNumber).
Verified: footer text / height / sticky position identical between
mobile-list (WizardLayout) and mobile-map (direct HelpBar).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts the committed-chip location search pattern out of ProvidersStep
(two identical inline call sites, ~60 lines each) into a reusable
molecule. Behaviour unchanged: draft-typing → commit on Enter or the
primary-filled search button → chip render → tap X to clear.
The molecule owns the non-obvious correctness CSS (endAdornment
absolute-anchoring + right-side padding lane) internally so future
callers don't have to rediscover it. Chrome (bgcolor, shadow, border,
radius) stays caller-controlled via the `sx` prop — selector keys for
internal vs caller rules are kept distinct (.MuiAutocomplete-inputRoot
vs .MuiOutlinedInput-root) to avoid sx-merge collisions.
API: value (committed, chip-rendered) + onChange (fires on commit OR
chip-delete) + optional onCommit (fires only on explicit commit, for
side effects beyond state).
ProvidersStep trims ~160 lines net, drops searchDraft/commitSearch/the
SearchIcon/LocationOnOutlinedIcon/IconButton imports that only existed
to power the two inline instances.
Four Storybook stories: Empty, WithCommittedValue, Unstyled,
WithOnCommit — enough to iterate the molecule without a live page.
Verified: delta=0px on the search button position (empty→draft→chip)
at both mobile and desktop widths — matches pre-extraction behaviour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New molecule MapProviderDrawer lifts the mobile-map bottom drawer out
of ProvidersStep (~120 lines): Paper + close-X header + single-pin
ProviderCard content / cluster-list content + slide-up animation.
Props: `active: ProviderMapActiveState | null`, `onClose`,
`onSelectProvider`, `onDrillIntoProvider`. Three Storybook states
(SingleProvider, Cluster, ClusterPair, Closed) so the drawer can be
iterated without a live map. ProvidersStep now consumes it as a
single line wired to mapRef.clearActive + mapRef.drillIntoProvider.
- Shared visual tokens for the control cluster (Search, Filters, Sort by,
List/Map toggle) factored into a CONTROL_CHROME constant and three
typed sx objects (controlButtonSx, controlToggleSx, controlInputSx,
filterTriggerSx) so all four controls share the same outline, radius,
fill, and shadow across mobile list, mobile map, and desktop. Desktop
map-panel floating toggle also re-threaded through controlToggleSx.
- Mobile list control order now matches mobile map: Sort by is grouped
left next to Filters (not pushed right with a ml:auto wrapper), and
the List/Map toggle is right-pinned via ml:auto on xs. Desktop keeps
Sort pushed right (no toggle rendered on desktop in this slot).
- Fix: the magnifying-glass commit button was drifting 19–30px left as
the input filled with chips / draft text. Root cause: overriding
`InputProps.endAdornment` on Autocomplete bypasses MUI's
`.MuiAutocomplete-endAdornment` absolute positioning, leaving our
`.MuiInputAdornment-positionEnd` as `position: static` in flex flow.
controlInputSx now re-absolutely-anchors the end adornment at the
right edge and reserves `pr: 5` so input content can't slide under it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Controls
- Mobile list view's Filters/Sort/view-toggle now match the mobile map
view's floating-chip treatment: white fill, neutral-300 border,
shadow-sm. On mobile the sort label switches from "Sort: <value>" to
a compact "Sort by"; desktop keeps its verbose label.
- List/Map toggle font bumped to 14px / 600 (button-small token), so
it reads on the same line as Filters + Sort by both on mobile and
on the desktop floating pill over the map panel.
PackagesStep drill-in
- Added a local hasDrilledIn flag so the mobile layout only swaps to
the detail view after an explicit user tap on a package. Previously
any pre-selection (the demo route seeds selectedPackageId to the
first matching package for desktop auto-display) also forced mobile
into the detail view, so users arriving from the map drawer saw a
single package instead of the list. Back/forward from the detail
resets the flag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MapPin (both tiers)
- Verified providers now get an inline verified tick on the left of
the name, matching the tick colour to the name so it reads as part
of the label. Inline SVG (not @mui/icons-material) because MapPin
is mounted via createRoot outside the ThemeProvider.
- Max label width bumped 180 → 210px to accommodate the icon without
aggressively truncating long names.
Mobile cluster drawer rows
- Verified icon aligns with the name's top line (flex-start + 1.25em
icon slot) — matches the desktop ClusterPopup layout.
- New right-aligned "From $X" price column, copper for verified.
Controls
- Mobile List/Map toggle: text labels (List, Map) instead of icons.
- Desktop List/Map toggle: resized to 32px height matching Filters +
Sort buttons; bigger type, more padding.
- Search input corner radius now matches the button radius (8px)
instead of the input radius (4px) so it reads as part of the chip
set rather than a separate control.
Filter dialog (desktop + mobile)
- Remove the Location field — the sticky search bar is primary.
- Funeral-type chips bump small → medium.
- Reset filters button always renders; disabled when no filters are
active (was previously hidden), so the affordance is discoverable.
- Provider-feature switches (Verified only, Online arrangements)
align to the first text line so wrapped labels don't sink the
switch visually below the second line.
Mobile drawer close
- drawerOpen now excludes the `exiting` phase, so tapping the X slides
the drawer down immediately instead of lingering with a stale
opacity fade. Visibility flips to hidden only after the slide ends.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Search, Filters, Sort by, and the List/Map toggle now share a
neutral-300 1px border and a shadow-sm drop, so the strip reads as a
coherent set of floating chips over the map (not a mix of different
button chromes)
- Drawer card now runs edge-to-edge inside the drawer with its own
border + shadow stripped; the drawer Paper provides the top radius
and the bottom is explicitly squared (no stray MUI default radius
leaking through)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Mobile-map search input gets an explicit white fill so it reads
cleanly against map tiles (desktop unchanged)
- Sort trigger on mobile-map is now a compact "Sort by" outlined
Button instead of a lone icon — clearer affordance than the swap
glyph, still tight on horizontal space
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the Paper container around the mobile-map floating controls —
each control (Filters, Sort, view toggle) now carries its own white
fill and reads over any map tile without a shared box
- Sort button becomes icon-only on mobile-map (the current sort is
still communicated via the aria-label and the menu) — saves the
row's horizontal budget
- Align all three controls to 32px height so Filters, Sort, and the
List/Map toggle sit on a common baseline
- Move the drawer close X out of the image overlay area into a
dedicated 40px drawer header bar; cluster header text ("N providers
in this area") now lives in the same strip. No more overlap with the
Verified badge on the card image.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On xs + viewMode=map, render a map-first layout: full-bleed map,
floating card-shaped control strip at the top (search + Filters +
Sort + compact List/Map toggle), and a bottom drawer that slides up
when a pin or cluster is tapped. The desktop list-map layout is
unchanged.
On xs + viewMode=list, the List/Map toggle now appears in the sticky
control bar (icon-only) so users can reach the map from the list view.
On desktop the toggle stays on the map panel as before.
Drawer content:
- Single pin → the existing ProviderCard molecule, entire card
clickable (navigates to packages)
- Cluster → a list of image-free rows (verified icon slot + name +
location + rating), tap a row to pan+zoom into the provider
- Close X on the drawer clears the active state
To support externalising popups, ProviderMap gains two opt-in props
(`externalisePopups`, `onActiveChange`) and an imperative handle
(`clearActive`, `drillIntoProvider`). Desktop behaviour unchanged
when these aren't used. The forwardRef now exposes the handle rather
than the DOM element; no existing callsite passed a DOM ref.
The filter-dialog children are now defined once as a shared JSX
fragment used by both desktop and mobile FilterPanel instances.
Header + subhead are suppressed on the mobile map view (per concept
reference); they remain on desktop and mobile list for orientation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Suppress Autocomplete's own popup/clear indicators (forcePopupIcon,
clearIcon) so the search IconButton stays anchored in the same spot
across empty, draft, and chip states
- Search button is a primary-filled circle at default strength in every
state (no disabled dimming) — a clear affordance, handler already
guards for empty drafts
- Drop the brand-gold focus ring on the search bar; keep the default
neutral border on focus
- Drop the copper 2px focus outline on Filters and Sort (outline: none
under :focus-visible)
- Committed location chip now uses the default neutral tonal fill
instead of the promoted brand colour
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sticky search now uses Autocomplete (multiple+freeSolo capped to 1)
instead of a plain TextField:
- Pin icon tightened to the left edge and to the placeholder
- Committed location renders inside the input as an FA Chip with an
X delete (clears the committed filter)
- Primary-coloured magnifying-glass IconButton on the right commits
the draft; disabled while the draft is empty
- Typing no longer filters live — Enter or the search button promotes
the draft to a chip, matching the chip mental model
The FilterPanel dialog's Location autocomplete already read from the
same searchQuery state, so it continues to display the committed chip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Sort button now reads "Sort: <value>" so it's distinguishable from
a filter; aria-label spells out the current sort. Price sort labels
dropped their internal colons (avoids double-colon rendering).
- Results count bolds the number in primary text so it registers as
the subject rather than incidental metadata.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces a full Google-Maps-backed provider map for the arrangement
wizard's ProvidersStep. Clicking a pin morphs it into a MapPopup at
the same coord; pins within 70px of each other collapse into a cluster
(ceiling at zoom 13) that opens a ClusterPopup list on click. Row
clicks pan + zoom the map to the provider and open their MapPopup.
Map-background click routes through an exit transition that fades the
popup out before reappearing the pin, via a matching fade-in keyframe
on the atom markers.
Key additions:
- @vis.gl/react-google-maps + @googlemaps/markerclusterer deps
- ClusterMarker atom (count badge; verified / unverified palettes)
- ClusterPopup molecule (image-free rows; verified icon aligned to
name; right-aligned "From $X" column; verified-first sort)
- ProviderMap organism (APIProvider + Map + imperative AdvancedMarker
layer via createRoot for clusterer compatibility)
Component changes:
- MapPin: promoted verified palette (brand-700); name now required;
name-only and price-only variants dropped; active prop removed in
favour of organism-level state; SVG nub with fill+stroke replaces
the CSS border-triangle trick so the outline is continuous
- MapPopup: `exiting` prop drives close animation; click events stop
propagation so the map's onClick can't clear state mid-interaction
- ProviderData type gains optional `coords`; demo fixtures populated
with real NSW/QLD lat/lng for all 7 providers
- ProvidersStep demo route wires ProviderMap into the mapPanel slot
Memory:
- docs/memory/component-registry updated (ClusterMarker, ClusterPopup,
ProviderMap added; MapPin + MapPopup refined; MapCard retired)
- docs/memory/session-log captures arc across 2026-04-21/22 and flags
next-session work: ProvidersStep polish, mobile layout for list-map
WizardLayout, and demo deploy
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes images 404'ing under /arrangement/ — Vite's publicDir copies assets
to the build root, but the base prefix is only applied to bundled assets
(JS/CSS), not to runtime URL strings. assetUrl() helper resolves paths
against import.meta.env.BASE_URL so '/images/foo.png' becomes
'/arrangement/images/foo.png' in production while staying '/images/foo.png'
in dev.
- src/demo/shared/assets.ts — assetUrl() helper
- providers.ts + DemoNav.tsx — wrap all public asset paths
- nginx/parsons-demos.conf — swag site-conf for parsons.tensordesign.com.au
(asset cache regex above SPA fallback regex per nginx first-match rule)
- docs/reference/client-demo-deploy.md — server runbook (DNS, swag
SUBDOMAINS, mount, htpasswd, deploy loop)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a self-contained demo build target for the Providers → Packages →
Comparison flow, deployable as a static SPA at /arrangement/.
- vite.demo.config.ts: per-slice build via --mode, base path flips for
dev vs prod, output to dist-demo/<slice>/
- src/demo/: shared fixtures (7 providers across verified/tier3/tier2
with real venue photography from brandassets) + Zustand basket store
with ?compare= URL persistence
- Verified-provider packages now share the nine canonical Essentials
line items per FA convention; only Optionals/Extras vary
- App-level CompareBar surfaces "Already added" / "Maximum 3" feedback
via transient store error
- ProviderCard logo objectFit cover→contain so wide logos don't crop
- npm scripts demo:dev / demo:build, deps zustand + react-router-dom
Consolidate the three tier pages (PackagesStep, UnverifiedPackageT2,
UnverifiedPackageT3) into a single tier-aware PackagesStep with
providerTier: 'verified' | 'tier3' | 'tier2'. Copy, CTA label, price
disclaimer, and itemised-unavailable state all derive from tier via
an internal TIER_COPY map.
Extract NearbyPackageCard as a molecule (was duplicated inline in T2
and T3). Inherits Card atom's default elevated variant so shadow
matches the primary ServiceOption cards in the same column.
Add showAllFromProvider variant for the "See N more packages from
this provider" flow — flat list, no grouping, no secondary list,
preference filter dropped.
Polish pass on PackagesStep + PackageDetail:
- PackageDetail header band warm → white; added card drop-shadow.
- onCompare prop wire-through (button was built in but never exposed).
- Price disclaimer info-box: padding/gap/line-height tuned, icon
alignment fixed (mt: '3px' matches codebase convention for 16px
icons paired with body2 text).
- Left-column vertical rhythm: 48px gaps between provider card /
subheading / list; 128px gap (Divider my: 8) between primary and
secondary sections to separate groupings.
- Mobile drill-in navigation via useMediaQuery + display toggles.
onSelectPackage widened to accept string | null; Back button
swaps to "Back to packages" when a package is selected on mobile.
Scrolls to top on drill-in.
- "See all" link copy: "See N more packages from this provider →"
(overflow count, no provider name — sidesteps long-name wrapping).
- Verified provider image: placeholder URL → real local asset
(hparsonsvenue.jpg, resized 2048×1366/591KB → 640×427/52KB).
Delete legacy PackageSelectPage story in PackageDetail.stories.tsx
(predated the real page components).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ComparisonTabCard: width 210 → 235; recommended badge switched to
filled brand + StarRoundedIcon matching the desktop
ComparisonColumnCard treatment; removed glow + active glow shadow in
favour of the standard shadow-sm; border colour brand-500 → brand-600;
pt 2.4 → 3.5.
- ComparisonPackageCard: verified badge replaced with inline
VerifiedOutlinedIcon to the left of the provider name (matches
desktop pattern); warm tint confined to the header (Card body now
explicitly white); 2px brand-600 border when recommended; header
padding px 2.5 → 3, pt 2.5 → 3, pb 2 → 4; spacing pass across the
provider identity / package info / sections groups — Divider my
1.5 → 3, section mb 3 → 5, item py 1.5 → 2, heading→first-item 1.5
→ 2.5.
- ComparisonPage mobile: Divider between page header and tab rail;
"Choose a package to view" h2 heading (user-centric copy), used as
aria-labelledby for the tablist; dot indicator below the rail
(8px grey, active 24×8 brand-600 pill) — aria-hidden and tabIndex=-1
so the tab rail above remains the canonical accessible navigation.
Also leaves the Figma capture script in preview-head.html for future
page captures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adopts a single-scroll-container layout for the desktop Comparison page
(motivated by a Figma Make exploration). The page header sits in a
max-width container matching the table's natural width, with flex
spacers either side of the table — when the viewport is wider than
the table, spacers centre it; when a 4th+ package pushes the table
wider than viewport, spacers collapse and the table extends rightward
from the page header's left edge.
- New WizardLayout variant `bleed` — viewport-locked, no inner Container,
main is the single scroll host, back link routed into children,
`data-wizard-scroll` marker for descendants.
- ComparisonTable: fixed 300px column widths exposed as
COMPARISON_TABLE_COL_WIDTH; sticky-left on row-label column across
every per-section mini-table; tiered hover (surface-subtle base /
surface-warm recommended column); recommended column carries a
resting 50%-opacity warm tint; "Not Included" copy replaces em-dash
for unavailable cells in Optionals/Extras sections; CellIconText
helper applies lineHeight: 1 so icon+text rows align optically.
- ComparisonColumnCard: uniform pt: 5 (40px); medium badge (26px) with
star/verified icon; 2px brand-600 border for recommended; provider
name wraps to 2 lines in a reserved 36px bottom-aligned slot so
1-line names keep subsequent content on a consistent baseline; Remove
link always rendered as the same Link element (visibility-hidden when
not applicable) so CTA+footer align across all cards.
- Mackay test data extended to exercise 2-line wrap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace recommended banner with floating badge (star + primary fill)
so CTA buttons align across recommended and non-recommended columns.
- Inline verified icon on recommended cards only (left of provider name,
brand-600). Non-recommended verified providers keep the top badge alone.
- Override recommended card border to brand-600 for consistency with
the rest of the primary system (token default is brand-500).
- Show dash in rating slot when provider has no rating — keeps heights
consistent across the row.
- Invisible Remove placeholder on recommended card so primary CTAs align
with cards that have a visible Remove link.
- Remove link dropped from body2 to caption (12px).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Navigation: add NavItem.children support for desktop dropdown + mobile collapsible sections. Wire Locations (Melbourne, Brisbane, Sydney, South Coast NSW, Central Coast NSW) before FAQ in all HomePage stories.
- HomePage hero: add italic "Trusted by thousands of families across Australia" tagline above h1, widen text container to 990px, use hero-couple.jpg background.
- HomePage features: rename "How it works" to "4 Reasons to use Funeral Arranger", add "Why Use Funeral Arranger" overline, remove placeholder body copy.
- HomePage testimonials: add "Funeral Arranger Reviews" overline above "What families are saying".
- HomePage CTA: promote "Start planning" to primary contained button (medium).
- FuneralFinderV3: remove header + divider so the form starts directly at "How can we help".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hero heading was display3 (same as every section h2), giving no visual
distinction. Now display2 (52px/24px) vs display3 (40px/22px). Still
renders as semantic h1 for SEO.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Increase hero top padding, heading-to-subheading gap, and finder card
side padding on mobile. Scale body1 text to 14px on mobile under display3
headings for better hierarchy contrast. Centre-align "Why Use FA" section
on mobile. Remove arrow from "Start exploring" button.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate Gitea remotes to git.tensordesign.com.au. Add assetUrl() utility
that resolves image paths from Gitea ParsonsAssets repo when
STORYBOOK_ASSET_BASE is set, enabling images on Chromatic-published
Storybook while keeping local dev unchanged via staticDirs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pushes to backup (full), fa-dev + sheffield (stripped via worktree),
and Chromatic in one command. Replaces ad-hoc push workflow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Homepage: add "Why Use FA" text+image section and "Three Ways" feature cards,
reorder sections (logos carousel above discover), apply warm-grey alternating
backgrounds from Figma, unify all section headings to display3 serif, increase
section padding, fix heading hierarchy for SEO, and left-align testimonials.
FuneralFinder V3: responsive CTA (medium on mobile), shorten button to "Search",
bump reassurance text to caption variant, tighten location pin-to-text gap.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New molecule: ComparisonColumnCard — desktop column header card extracted
from ComparisonTable (~150 lines removed from organism)
- New molecule: ComparisonTabCard — mobile tab rail card extracted from
ComparisonPage (shared by V1 and V2)
- CellValue "unknown" restyled: icon+text in neutral grey (was Badge),
InfoOutlinedIcon on right at 14px matching item info icons
- Unverified provider story data: all items set to unknown across all
story files (no dashes in essentials)
- Mobile tab rail: recommended badge (replaces star), package price,
shadow/glow, center-on-select scroll, overflow clipping fixed
- ComparisonPackageCard: added shadow, reduced CTA button to medium
- ComparisonTable first column: inline info icon pattern (non-breaking
space + nowrap span) prevents icon orphaning on line wrap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The mobile package card was previously duplicated inline in both
ComparisonPage (V2) and ComparisonPageV1 — same ~250-line component
pasted twice. Extract it as a proper molecule so card-level tweaks
land in one file and both pages stay in sync.
New molecule: src/components/molecules/ComparisonPackageCard/ with
component, stories (Verified, Unverified, Recommended,
ItemizedUnavailable), and index. API reuses the existing
ComparisonPackage type from ComparisonTable.
Both pages drop their inline MobilePackageCard + MobileCellValue
helpers and a handful of now-unused imports (Tooltip, Badge, Divider,
several icons, ComparisonCellValue type).
The desktop column header inside ComparisonTable is left inline —
it's tightly coupled to the grid/sticky behaviour and has a floating
verified badge + Remove link that differ meaningfully from the mobile
card. Extracting both variants into one molecule would need an
awkward variant prop for marginal gain.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Archive the current ComparisonPage as V1 (viewable under Archive/ in
Storybook) and build V2 as the new production version. In V2, the
recommended package is prepended instead of appended: it appears as the
first column on desktop and the first tab in the mobile rail. On mobile
the initially active tab is the first user-selected package, not the
recommendation — the recommended tab is surfaced as a visible suggestion
rather than the default view, which felt too upsell-y for the audience.
Both V1 and V2 now use a StarRoundedIcon (brand-600) in the mobile tab
label instead of a text star, so the "recommended" marker reads cleanly
against both selected and unselected tab backgrounds.
See decisions-log D040 for rationale.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>