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>
This commit is contained in:
2026-04-23 14:23:45 +10:00
parent a3d4427190
commit 826f888e87
5 changed files with 94 additions and 206 deletions

View File

@@ -54,12 +54,15 @@ duplicates) and MUST update it after completing one.
| LineItem | done | Typography + Tooltip + InfoOutlinedIcon | Name + optional info tooltip + optional price. Supports allowance asterisk, total variant (bold + top border). Font weight 500 (D019), prices text.secondary for readability hierarchy. Audit: 19/20. |
| ProviderCardCompact | done | Card (outlined) + Typography | Horizontal compact provider card — image left, name + location + rating right. Used at top of Package Select page. Separate from vertical ProviderCard. |
| CartButton | done | Button + DialogShell + LineItem + Divider + Typography | Outlined pill trigger: receipt icon + "Your Plan" + formatted total in brand colour. Click opens DialogShell with items grouped by section via LineItem, total row. Mobile: icon + price only. Lives in WizardLayout `runningTotal` slot. |
| CompareBar | done | Badge + Button + IconButton + Typography + Paper + Slide | Floating comparison basket pill. Fixed bottom, slide-up/down. Package count badge + provider names + remove buttons + Compare CTA. Max 3 user packages. Disabled CTA when <2. Inline error for max-reached. Mobile: compact count + CTA only. Audit: 18/20. |
| CompareBar | done | Badge + Button + IconButton + Typography + Paper + Slide + Chevron{Left/Right}RoundedIcon | Floating comparison basket pill. Fixed bottom, slide-up via MUI Slide on initial appearance (count: 0 → ≥1). Package count badge (`N/3`) + status text + Compare CTA. Max 3 user packages; disabled CTA when <2; inline error for max-reached. **Sizing**: Badge large / body1 text / Button medium on md+; Badge medium-circle / body2 / Button small on xs. **Mobile collapse (D049)**: single Paper right-anchored via `right: t.spacing(4); left: auto`; grey-filled right-chevron retracts the pill by animating the middle content Box to `max-width: 0` — the pill's right edge stays pinned, so the whole thing appears to shrink into the corner as one unit. Left-chevron expands. Collapsed fraction-badge shows just `count` (not `count/3`) with pinned min-width for circular look. Auto-peek: new add while collapsed re-expands the full bar for 3s, then re-collapses. Desktop stays permanently expanded. **Centering**: uses `left: 0; right: 0; mx: auto; width: fit-content` (the `left:50%; translateX(-50%)` centering trick is clobbered by Slide's own transform). **z-index**: `theme.zIndex.drawer` (1200) — below MapProviderDrawer's `modal` (1300) so the drawer covers it on mobile map view. `bottom: theme.spacing(16)` (64px, FA 4px base) clears the sticky HelpBar with ~25px breathing. See D049. |
| ComparisonPackageCard | done | Card + Button + Divider + Typography + Tooltip + LocationOnOutlinedIcon + VerifiedOutlinedIcon + StarRoundedIcon + CheckCircleOutlineIcon + InfoOutlinedIcon | Mobile full-width package card for ComparisonPage tabpanels. Provider header (inline VerifiedOutlinedIcon left of name when verified, name, location + rating, divider, package name, price block, full-width CTA) + itemised sections with left-accent headings. **Warm tint confined to header only** (not Card body) — Card is white (`background.paper`), header has `surface-warm` (recommended) or `surface-subtle` (verified) bg. **2px brand-600 border** when recommended (matches desktop ComparisonColumnCard). Header `px: 3, pt: 3, pb: 4`. Package-info subgroup (name/label/price) in tight nested flex columns. Generous section spacing (`mb: 5` between sections, `py: 2` per item). Recommended banner at top. Shadow (shadow-sm). Medium full-width button. Reuses `ComparisonPackage` type from ComparisonTable. Shared by ComparisonPage V2 and V1 (extracted 2026-04-09). |
| ComparisonColumnCard | done | Card + Badge + Button + Divider + Typography + Tooltip + Link + StarRoundedIcon + VerifiedOutlinedIcon | Desktop column header card for ComparisonTable. Floating badge: **medium** (26px) filled brand + StarRoundedIcon for recommended; soft brand + VerifiedOutlinedIcon for verified. Provider name **wraps to 2 lines** (`WebkitLineClamp: 2`) in a reserved 36px minHeight slot bottom-aligned so 1-line names anchor with location/rating/price at a consistent baseline. Recommended card: 2px brand-600 border + warm `selected` Card state + inline VerifiedOutlinedIcon left of name. `pt: 5` (40px breathing above name), uniform regardless of verified/recommended. Remove link always renders as the same Link element (visibility-hidden when not applicable) so CTA+footer align across all cards. Per-column wrapper in ComparisonTable is `display: flex` with `flex: 1` passed to the card root so all cards stretch to row height. Extracted from ComparisonTable (2026-04-12). |
| ComparisonTabCard | done | Card + Badge + Typography + StarRoundedIcon | Mobile tab rail card for ComparisonPage. Provider name + package name + price. Recommended badge in normal flow with negative margin overlap — **filled brand + StarRoundedIcon** (matches desktop ComparisonColumnCard treatment, size="small" at 14px icon). **Fixed 235px width** (was 210). Border `brand-600` when recommended (consistent with primary). No glow — uses standard `shadow-sm` like other cards. `pt: 3.5` inside card. Shared by V1 and V2 (extracted 2026-04-12). |
| NearbyPackageCard | done | Card (outlined, interactive) + Typography + StarRoundedIcon + LocationOnOutlinedIcon | Compact card representing a package offered by a nearby verified provider — package name + price + provider + rating + location. Used in the "Similar packages from verified providers nearby" section of PackagesStep for unverified tiers. Click is a route change to that verified provider's PackagesStep with this package loaded. Extracted from UnverifiedPackageT2/T3 during 2026-04-17 consolidation. |
| ClusterPopup | done | Paper + Typography + IconButton + ButtonBase + inline provider-rows (fixed verified-icon slot + name + location + rating) | Cluster list popup — appears when a cluster marker is clicked. Header bar ("N providers in this area" + close X), scrollable stack of image-free provider rows (reserved verified-icon slot so titles align across tiers, name in copper for verified / neutral for unverified, location + rating meta). Verified-first sort order. 320px wide, matches MapPopup's card + nub + drop-shadow. Click on a row calls `onSelectProvider(id)`; in the ProviderMap flow that pans+zooms the map to the provider's coords (zoom 15) and opens their single-provider popup — there's no back-to-list. See D043, D044. |
| MapProviderDrawer | done | Paper + IconButton + ProviderCard + inline cluster-rows (verified-slot + name + location + rating + "From $X") | Bottom drawer surfacing ProviderMap's popup content outside the map (used by the mobile map-first layout on ProvidersStep). Two content states driven by `active: ProviderMapActiveState \| null`: (a) `active.provider` → full-width ProviderCard edge-to-edge, entire card clickable → `onSelectProvider(id)`; (b) `active.cluster` → verified-first list of rows with verified-icon slot aligned to name top-line (`alignItems: flex-start` + `1.25em` slot height + `lineHeight: 1.25` on name), name copper for verified, right-aligned "From $X" price column; row click → `onDrillIntoProvider(id)` (typically wired to map's imperative `drillIntoProvider` — pans + zooms + swaps `active` to that provider). 40px header strip holds the close X + cluster count heading; `px: 2` aligns with row content beneath. Slides up via `transform: translateY()` + 220ms transition; stays in DOM during exit via `visibility`. z-index `theme.zIndex.modal` (1300) so it covers the floating CompareBar on mobile map view. Requires a relatively-positioned parent container. See D045, D047. |
| LocationSearchInput | done | Autocomplete (multiple + freeSolo capped to 1) + TextField + IconButton + Chip + LocationOn/Search icons | Committed-chip location search. Typing produces a local draft; Enter or the primary-filled circular magnifying-glass commit button promotes the draft to an FA Chip rendered inside the input. Tap the chip's X → clears to empty. Molecule owns the non-obvious correctness CSS: the end-adornment is absolute-anchored via selector `.MuiAutocomplete-inputRoot .MuiInputAdornment-positionEnd` (MUI's stock Autocomplete does this on `.MuiAutocomplete-endAdornment` but overriding `InputProps.endAdornment` bypasses that, leaving the button sliding left as content fills); `pr: 5` reserves the right-edge lane so chips can't run under. Chrome (bgcolor, shadow, border, radius) is caller-controlled via `sx` — internal selector keys use `.MuiAutocomplete-inputRoot` so they don't collide with caller sx for `.MuiOutlinedInput-root`. API: `value`, `onChange` (fires on commit OR chip-delete), `onCommit?` (explicit commit only), `placeholder?`, `aria-label?`, `sx?`. Extracted from ProvidersStep (D046, D048). |
| HelpBar | done | Box + Typography + Link + PhoneIcon | Sticky help footer — phone-icon prefix + "Need help? Call us on" + tel-linked support number. White fill, top border, `position: sticky; bottom: 0; z-index: 10`. Responsive px (2 / 4) and centered text. Used by WizardLayout and by pages that bypass WizardLayout's chrome (ProvidersStep mobile-map branch). Promoted from a WizardLayout-internal component so both sources render an identical footer — preventing drift if the phone number or style ever changes. API: `phone?` (defaults to '1800 987 888', spaces preserved in label, stripped in tel href), `sx?`. See D048. |
| SortMenu | done | Button (outlined, secondary, small) + Menu + MenuItem + SwapVertIcon (verbose only) | Dropdown sort control. Tap the trigger Button → anchored Menu opens at bottom-right; pick an option → menu closes and `onChange(value)` fires. Two label variants: `compact` (default) renders just "Sort by" — current value surfaces only in the menu's selected-state + aria-label (best for narrow surfaces); `verbose` renders `Sort: <current label>` with a leading swap-vertical icon (best for desktop). Non-generic string-typed API so `forwardRef` stays clean; callers with typed sort unions cast at the boundary. `aria-haspopup="listbox"` + `aria-label` carries the current sort for SR users. API: `value`, `onChange`, `options: SortOption[]`, `variant?: 'compact' \| 'verbose'`, `sx?`. Extracted from ProvidersStep (D048); intended for VenueStep, CoffinsStep reuse. |
## Organisms
@@ -67,8 +70,8 @@ duplicates) and MUST update it after completing one.
|-----------|--------|-------------|-------|
| ServiceSelector | done | ServiceOption × n + Typography + Button | Single-select service panel for arrangement flow. Heading + subheading + ServiceOption list (radiogroup) + optional continue Button. Manages selection state via selectedId/onSelect. maxDescriptionLines pass-through. |
| PricingTable | planned | PriceCard × n + Typography | Comparative pricing display |
| PackageDetail | done | LineItem × n + Typography + Button + Divider | Right-side package detail panel. Warm header band (surface.warm) with "Package" overline, name, price (brand colour), Make Arrangement + Compare (with loading) buttons. Sections: Essentials + Optionals (before total) + total + Extras (after total, with subtext). `priceLabel` pass-through to LineItem (D039). T&C grey footer. Audit: 19/20. |
| ComparisonTable | done | Typography + Card + Tooltip + ComparisonColumnCard | Side-by-side package comparison. **Fixed column widths** (300px row-label + 300px per package, exported as `COMPARISON_TABLE_COL_WIDTH`). Natural width = `300 × (n+1)`. **Sticky-left** on row-label column across every per-section mini-table. **Tiered hover**: base cells → `surface-subtle`, recommended column cells → `surface-warm` (resting 50% opacity, promotes to full on row hover via color-mix). Per-section mini-tables with left-accent brand heading. **"Not Included"** rendering: `unavailable` cell values in `Optionals`/`Extras` sections render "Not Included" (neutral-500 weight 500); `Essentials` keeps em-dash. `CellIconText` local helper applies `lineHeight: 1` to icon+text rows for optical centre alignment. ARIA table roles. Desktop only (mobile in ComparisonPage). Audit: 17/20 (pre-restructure). |
| PackageDetail | done | LineItem × n + Typography + Button + Divider + CheckRoundedIcon | Right-side package detail panel. Warm header band (surface.warm) with "Package" overline, name, price (brand colour), Make Arrangement + Compare buttons. Sections: Essentials + Optionals (before total) + total + Extras (after total, with subtext). `priceLabel` pass-through to LineItem (D039). T&C grey footer. **Compare toggle (D050)**: `inCart?: boolean` prop controls the Compare button state. Both states share the same soft/secondary chrome and "Compare" label; when `inCart=true`, a trailing `CheckRoundedIcon` is added (endIcon). Click fires `onCompare` in both states — caller wires it to a toggle (e.g. `basket.toggle(key)`). `aria-pressed={inCart}` + aria-label ("Add to comparison" / "Remove from comparison") for SR users. Rejected alternatives: inert brand-tinted pill (too much space); "Added" label swap (cognitive overhead). **CTAs responsive (2026-04-23c)**: `flexDirection: 'row'` on all viewports — never stacks. `size: isMobile ? 'medium' : 'large'` (40/48px) via `useMediaQuery(theme.breakpoints.down('sm'))` so Make Arrangement + Compare fit side-by-side in a ~360px mobile column. Audit: 19/20 (pre-D050). |
| ComparisonTable | done | Typography + Card + Tooltip + ComparisonColumnCard | Side-by-side package comparison. **Fixed column widths** (300px row-label + 300px per package, exported as `COMPARISON_TABLE_COL_WIDTH`). Natural width = `300 × (n+1)`. **Sticky-left** on row-label column across every per-section mini-table. Info card (top-left "Package Comparison" card) **scrolls** with the package columns (was sticky-left — that pinned it over the recommended column per D040). **Tiered hover**: base cells → `surface-subtle`, recommended column cells → `surface-warm` (resting 50% opacity, promotes to full on row hover via color-mix). Per-section mini-tables with left-accent brand heading. **Tier-aware missing-cell rendering (D054)**: `lookupValue` branches on `pkg.provider.verified` — unverified packages render missing cells as `{ type: 'unknown' }` ("Unknown" + trailing info icon, neutral-500); verified packages render as `{ type: 'unavailable' }` → "Not Included" in Optionals/Extras, em-dash in Essentials (safety net — canonical-essentials rule means verified providers itemise all 9, so this path shouldn't fire in practice). No disclaimer footer. `CellIconText` local helper applies `lineHeight: 1` to icon+text rows for optical centre alignment. ARIA table roles. Desktop only (mobile in ComparisonPage). Audit: 17/20 (pre-restructure). |
| FuneralFinder (V3) | done | Typography + Button + Divider + Select + MenuItem + OutlinedInput + custom StatusCard/SectionLabel | **Production version.** Hero search widget — clean form with status cards. Standard card container (surface.raised, card shadow). "How Can We Help" section: two side-by-side StatusCards (Immediate Need default-selected / Pre-planning) — white bg, neutral border, brand border + warm bg when selected, stack on mobile. "Funeral Type" Select + "Location" OutlinedInput with pin icon — standard outlined fields, no focus ring (per design). Overline section labels (text.secondary). CTA "Find Funeral Directors →" always active — validates on click, scrolls to first missing field. Required: status + location. Funeral type defaults to "show all". Dividers after header and before CTA. WAI-ARIA roving tabindex on radiogroup. aria-labelledby via useId(). Critique: 33/40 (Good). Audit: 18/20 (Excellent). |
| FuneralFinder V1 | archived | Typography + Button + Chip + Input + Divider + Link + custom ChoiceCard/TypeCard/CompletedRow/StepHeading | Archived — viewable in Storybook under Archive/. Stepped conversational flow. Audit: 14/20. Critique: 29/40. |
| FuneralFinder V2 | archived | Typography + Button + Input + Divider + Select + MenuItem + custom StepCircle | Archived — viewable in Storybook under Archive/. Quick-form with step circles. Audit: 18/20. Critique: 33/40. |
@@ -89,8 +92,8 @@ duplicates) and MUST update it after completing one.
| Component | Status | Composed of | Notes |
|-----------|--------|-------------|-------|
| IntroStep | done | WizardLayout (centered-form) + ToggleButtonGroup × 2 + Collapse + Typography + Button + Divider | Wizard step 1 — entry point. forWhom (Myself/Someone else) + hasPassedAway (Yes/No) with progressive disclosure. Auto-sets hasPassedAway="no" for "Myself". `<form>` wrapper, aria-live subheading, grief-sensitive copy. Pure presentation. Audit: 18/20 → 20/20 after fixes. |
| ProvidersStep | done | WizardLayout (list-map) + ProviderCard + ProviderMap + FilterPanel + Autocomplete (chip search) + Chip + Typography + Button + ToggleButtonGroup | Wizard step 2 — provider selection. **Desktop + mobile-list**: list-map WizardLayout — provider cards (click-to-navigate) + sticky bar with committed-chip search (Autocomplete multiple+freeSolo capped to 1, primary-filled search commit button — D046) + Filters dialog + `Sort: <value>` button + mobile-only `List|Map` toggle. Results count bolded. **Mobile + viewMode=map (D045)**: custom layout — full-bleed map + floating card-shaped control strip (search + Filters + `Sort by` + `List|Map` toggle, all white-fill/neutral-300/shadow-sm/32px/14px-600, matching chrome across modes) + bottom drawer (slides up on pin/cluster tap, close X in a 40px header strip). Single-pin drawer renders ProviderCard edge-to-edge with top-only rounded corners; cluster drawer renders inline list of verified-slot + name + location + rating + "From $X" rows, tap a row → drill-in via `mapRef.drillIntoProvider`. Drawer close fully clears via `mapRef.clearActive`. Header + subhead hidden on mobile map. Filter dialog children extracted into a shared `filterDialogChildren` JSX used by both desktop + mobile FilterPanel instances; Location field removed, Funeral-type chips size=medium, Reset filters always visible (disabled when 0 active), provider-feature switches align to first text line. ProviderMap instantiated internally for mobile map view (with `externalisePopups` + `onActiveChange` + `ref`); desktop continues to use the `mapPanel` slot. Audit: 18/20 (pre-2026-04-23 expansion). |
| PackagesStep | done | WizardLayout (list-detail) + ProviderCardCompact + ServiceOption + NearbyPackageCard + PackageDetail + Divider + Link + Typography | Wizard step 3 — package selection. **Tier-aware unified page** (replaces the old PackagesStep + UnverifiedPackageT2 + UnverifiedPackageT3 trio, 2026-04-17). `providerTier: 'verified' \| 'tier3' \| 'tier2'` drives heading, subhead, `arrangeLabel`, `priceDisclaimer`, and `itemizedUnavailable` via a `TIER_COPY` map. Discriminated `secondaryList`: `same-provider-more` (ServiceOption list, verified) or `nearby-verified` (NearbyPackageCard list, unverified). Same-provider-more **shows top 3 inline**; at >3 shows 3 + `See all N packages from [Provider] →` Link that fires `onSeeAllPackages`. `showAllFromProvider` prop renders a flat "All packages from [Provider]" variant. Primary list suppresses the "Matching your preferences" accent-bar heading when no secondary list is present. **Mobile drill-in (2026-04-23 fix):** local `hasDrilledIn` flag — the mobile layout only swaps to the detail view after an explicit user tap on a package, so parent-seeded `selectedPackageId` (common on desktop for auto-display) doesn't force mobile users straight into detail. Back resets the flag. |
| ProvidersStep | done | WizardLayout (list-map) + ProviderCard + ProviderMap + FilterPanel + LocationSearchInput + SortMenu + HelpBar + MapProviderDrawer + Chip + Typography + ToggleButtonGroup | Wizard step 2 — provider selection. **Desktop + mobile-list**: list-map WizardLayout — provider cards (click-to-navigate) + sticky bar with LocationSearchInput (committed-chip search — D046/D048) + FilterPanel trigger + SortMenu (`variant: isMobile ? 'compact' : 'verbose'` — D048) + mobile-only `List\|Map` toggle. Results count bolded. **Mobile + viewMode=map (D045)**: custom layout — full-bleed map + floating control strip (search + Filters + Sort by + `List\|Map` toggle) + MapProviderDrawer + shared HelpBar molecule. Header + subhead hidden on mobile map. **Control chrome**: page-local `CONTROL_CHROME` const (height 32 / neutral-300 border / button-radius / paper fill / shadow-sm) + derived `controlButtonSx` / `controlToggleSx` / `controlInputSx` / `filterTriggerSx` objects applied across Search + Filters + SortMenu + ToggleGroup so all four controls read as one coherent chip set. Filter dialog children in a shared `filterDialogChildren` JSX used by both desktop + mobile FilterPanel instances; Location field removed (sticky search is primary), Funeral-type chips size=medium, Reset filters always visible (disabled when 0 active), provider-feature switches align to first text line. ProviderMap instantiated internally for mobile map view (with `externalisePopups` + `onActiveChange` + imperative ref); desktop continues to use the `mapPanel` slot. Audit: 18/20 (pre-2026-04-23 expansion). |
| PackagesStep | done | WizardLayout (list-detail) + ProviderCardCompact + ServiceOption + MiniCard + PackageDetail + Divider + Link + Typography | Wizard step 3 — package selection. **Tier-aware unified page** (replaces the old PackagesStep + UnverifiedPackageT2 + UnverifiedPackageT3 trio, 2026-04-17). `providerTier: 'verified' \| 'tier3' \| 'tier2'` drives heading, subhead, `arrangeLabel`, `priceDisclaimer`, and `itemizedUnavailable` via a `TIER_COPY` map. Discriminated `secondaryList`: `same-provider-more` (ServiceOption list, verified, `packages` field) or `nearby-verified` (MiniCard grid, unverified, `providers` field). **Nearby-verified (D052)**: 2-col `repeat(2, 1fr)` grid on sm+, 1-col on xs, capped at 4 via `NEARBY_VERIFIED_LIMIT`. Each card is a verified provider (image + `verified={true}` implicit + location + rating + "From $X"); click routes to that provider's PackagesStep via `onNearbyProviderClick(id)`. Heading: "Similar packages from verified providers" (`VerifiedOutlinedIcon` aligned to top line via `alignItems: flex-start` + `mt: '3px'`). Same-provider-more **shows top 3 inline**; at >3 shows 3 + `See all N packages from [Provider] →` Link that fires `onSeeAllPackages`. `showAllFromProvider` prop renders a flat "All packages from [Provider]" variant. Primary list suppresses the "Matching your preferences" accent-bar heading when no secondary list is present. `selectedPackage` lookup falls back from primary `packages` to `same-provider-more` secondary list so tapping a package in "Other packages from X" surfaces PackageDetail correctly (2026-04-23c fix). **Mobile drill-in (2026-04-23 fix):** local `hasDrilledIn` flag — the mobile layout only swaps to the detail view after an explicit user tap on a package, so parent-seeded `selectedPackageId` (common on desktop for auto-display) doesn't force mobile users straight into detail. Back resets the flag. |
| ~~PreviewStep~~ | removed | — | Replaced by ArrangementDialog organism (D-E). Package preview + "what's next" checklist now in the dialog's preview step. |
| ~~AuthGateStep~~ | removed | — | Replaced by ArrangementDialog organism (D-E). SSO/email auth flow now in the dialog's auth step. |
| DateTimeStep | done | WizardLayout (centered-form) + Input + TextField (date) + RadioGroup + Collapse + Divider + Button + Link | Wizard step 6 — details & scheduling. Deceased name (Input atom, external label) + preferred dates (up to 3, progressive disclosure) + time-of-day radios. Service tradition removed (flows from provider/package). Dividers between sections. Grief-sensitive labels. Save-and-exit CTA. |