Add package comparison feature: CompareBar, ComparisonTable, ComparisonPage

New components for side-by-side funeral package comparison:

- CompareBar molecule: floating bottom pill with fraction badge (1/3, 2/3, 3/3),
  contextual copy, Compare CTA. For ProvidersStep and PackagesStep.
- ComparisonTable organism: CSS Grid comparison with info card, floating verified
  badges, separate section tables (Essentials/Optionals/Extras) with left accent
  borders, row hover, horizontal scroll on narrow desktops, font hierarchy.
- ComparisonPage: WizardLayout wide-form with Share/Print actions. Desktop shows
  ComparisonTable, mobile shows mini-card tab rail + single package card view.
  Recommended package as separate prop (D038).

Also fixes PackageDetail: adds priceLabel pass-through (D039), updates stories
to Essentials/Optionals/Extras section naming (D035).

Decisions: D035-D039 logged. Audits: CompareBar 18/20, ComparisonTable 17/20.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 01:17:34 +10:00
parent eb26242ece
commit 52fd0f199a
14 changed files with 2359 additions and 81 deletions

View File

@@ -53,6 +53,7 @@ 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. |
## Organisms
@@ -60,7 +61,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 (before total) + total + extras (after total, with subtext). T&C grey footer. Audit: 19/20. Maps to Figma Package Select (5405:181955). |
| 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 + Button + Badge + Link + Tooltip | Side-by-side package comparison CSS Grid. Sticky header cards with provider info + price + CTA. Row-merged sections (union of all items). 7 cell value types (discriminated union D036). Recommended column with warm bg + badge. Verified → "Make Arrangement", unverified → "Make Enquiry". ARIA table roles. Desktop only (mobile in ComparisonPage). Audit: 17/20. |
| 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. |
@@ -100,6 +102,7 @@ duplicates) and MUST update it after completing one.
| ConfirmationStep | done | WizardLayout (centered-form) + Button | Wizard step 15 — confirmation. Terminal page. At-need: "submitted" + callback. Pre-planning: "saved" + return-anytime. Muted success icon. |
| UnverifiedProviderStep | done | WizardLayout (list-detail) + ProviderCardCompact + ProviderCard + Badge + Button + Divider + Typography | Unverified provider detail. Left: compact card + "Listing" badge + available info (conditional dl) + verified recommendations. Right: warm header band + detail rows + "Make an Enquiry" CTA. Graceful degradation (no data → straight to enquiry). 4 story variants. |
| HomePage | done | FuneralFinderV3/V4 (via finderSlot) + ProviderCardCompact + Button + Typography + Accordion + Divider + Navigation (prop) + Footer (prop) | Marketing landing page. 4 archived versions: V1 (split hero), V2 (full-bleed parsonshero.png), V3 (hero-3.png + updated copy + logo bar + venue photos + warm CTA gradient), V4 (same as V3 but with FuneralFinderV4 stepped form via finderSlot). `finderSlot` prop allows swapping finder widget. Light grey footer (surface.subtle). |
| ComparisonPage | done | WizardLayout (wide-form) + ComparisonTable + Chip + Card + LineItem + Typography + Button + Divider | Package comparison page. Desktop: full ComparisonTable with sticky headers. Mobile: tabbed card view with horizontal chip rail (role="tablist") + single package card (role="tabpanel"). Recommended package as additional column/tab (separate prop D038). Back link, help bar. |
## Future enhancements

View File

@@ -293,3 +293,43 @@ contradict a previous one.
**Rationale:** P0/P1 are the issues that affect usability and accessibility. P2/P3 are cosmetic — not worth the risk of changing approved components. Interleaving ensures the foundation is solid before building on it, without dedicating entire sessions to review.
**Affects:** Session workflow, CLAUDE.md startup procedure, docs/reference/retroactive-review-plan.md
**Alternatives considered:** Dedicated review sessions — rejected as less efficient. Full P0-P3 fixes — rejected as too risky for approved components.
### D035 — Package sections standardised to Essentials / Optionals / Extras
**Date:** 2026-04-06
**Category:** component
**Decision:** Package data uses three sections: **Essentials** (priced core items), **Optionals** (complimentary inclusions), **Extras** (additional-cost items after the total). Replaces the previous "Complimentary Items" naming.
**Rationale:** Matches the real-world package structure from FA's provider data (see reference image). "Optionals" better communicates that these are included-but-not-mandatory items, while "Complimentary" is a price label on individual items, not a section name.
**Affects:** PackageDetail stories, ComparisonTable sections, ComparisonPage mobile cards
**Alternatives considered:** "Inclusions" instead of "Optionals" — rejected as it overlaps with Essentials (which are also inclusions).
### D036 — ComparisonCellValue uses discriminated union type
**Date:** 2026-04-06
**Category:** architecture
**Decision:** Cell values in ComparisonTable use a tagged union type (`{ type: 'price' | 'allowance' | 'complimentary' | 'included' | 'poa' | 'unknown' | 'unavailable' }`) rather than flat optional props.
**Rationale:** Ensures exhaustive pattern matching in CellValue renderer — the TypeScript compiler catches missing cases. Clearer than a flat `{ price?: number; priceLabel?: string; isAllowance?: boolean }` which has ambiguous combinations. Each value type maps to a distinct visual treatment.
**Affects:** ComparisonTable, ComparisonPage mobile card view
**Alternatives considered:** Reusing PackageLineItem from PackageDetail — rejected as it conflates "how data is stored" with "how data is displayed". The comparison needs explicit cell state (e.g. "unavailable" vs "unknown").
### D037 — Mobile comparison uses chip tabs, not horizontal scroll table
**Date:** 2026-04-06
**Category:** component
**Decision:** ComparisonPage renders a chip-based tab rail + single card view on mobile, rather than a horizontally scrollable table.
**Rationale:** Wide comparison tables on small screens create "hidden column" problems — users can't see all packages at once and may miss columns. Card view with tabs matches mental model of reviewing one option at a time. Lower cognitive load for FA's grief-sensitive audience. Tab rail provides quick switching. ARIA tablist/tabpanel semantics.
**Affects:** ComparisonPage mobile layout
**Alternatives considered:** Horizontal scroll table — rejected for poor usability on small screens. Accordion per package — rejected as it hides content behind extra taps.
### D038 — Recommended package is a separate prop, not mixed into packages array
**Date:** 2026-04-06
**Category:** architecture
**Decision:** ComparisonPage accepts `recommendedPackage` as a separate prop from `packages`. The page merges it as the last column with `isRecommended: true`.
**Rationale:** Keeps the user-selected array clean and unambiguous. The recommendation source is explicit (server-side logic). The page controls placement (always last column/tab). Prevents accidental removal of the recommended package by the user (no Remove button).
**Affects:** ComparisonPage props, ComparisonTable isRecommended column
**Alternatives considered:** Including recommended in the packages array with a flag — rejected as it mixes user selections with system recommendations.
### D039 — PackageLineItem gains priceLabel for consistency with LineItem
**Date:** 2026-04-06
**Category:** component
**Decision:** Added `priceLabel?: string` to `PackageLineItem` interface in PackageDetail, passed through to LineItem molecule.
**Rationale:** LineItem already supports `priceLabel` for custom price text ("Complimentary", "Price On Application", "TBC"). PackageDetail's type was missing this field, forcing workarounds. Adding it enables the Optionals section to display "Complimentary" labels and Extras to show "Price On Application".
**Affects:** PackageDetail component + stories, any consumer of PackageLineItem type
**Alternatives considered:** None — this was a straightforward type parity fix.

View File

@@ -26,6 +26,81 @@ Each entry follows this structure:
## Sessions
### 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)