Unify PackagesStep across tiers + polish pass

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>
This commit is contained in:
2026-04-20 12:45:57 +10:00
parent 312a77aeb9
commit e67872cb6a
17 changed files with 1048 additions and 1553 deletions

View File

@@ -57,6 +57,7 @@ duplicates) and MUST update it after completing one.
| 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. |
## Organisms
@@ -86,7 +87,7 @@ duplicates) and MUST update it after completing one.
|-----------|--------|-------------|-------|
| 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 + SearchBar + Chip + Typography + Button | Wizard step 2 — provider selection. List-map split: provider cards w/ radiogroup + search + filter chips (left), map slot (right). aria-live results count, back link. ProviderCard extended with HTML/ARIA passthrough. Audit: 18/20. |
| PackagesStep | done | WizardLayout (list-detail) + ProviderCardCompact + ServiceOption + PackageDetail + Badge + TextField + Typography + Button | Wizard step 3 — package selection. List-detail split: compact provider + budget filter + package list w/ radiogroup (left), PackageDetail breakdown (right). "Most Popular" badge. Mobile Continue button. |
| 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 (no grouping, no secondary list, preserves `selectedPackageId`). Primary list suppresses the "Matching your preferences" accent-bar heading when no secondary list is present (so the label only appears when there's something to contrast against). Desktop polished; mobile polish pending. |
| ~~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. |

View File

@@ -26,6 +26,58 @@ Each entry follows this structure:
## Sessions
### 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-warm``background.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: 3``mb: 6` (24 → 48px).
- Subheading → "Matching your preferences" heading: `mb: 3``mb: 6` (24 → 48px).
- Primary list → Divider → secondary section: primary list `mb: 3``mb: 4`, Divider `mb: 2.5``my: 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)
@@ -96,9 +148,59 @@ Each entry follows this structure:
- None blocking.
**Next steps:**
- Commit today's work (2 commits: Phase A+B desktop restructure, then card refinements + mobile polish).
- Optional: `/audit` on refreshed ComparisonPage + ComparisonTable; `/critique` on the mobile and desktop views.
- User flagged next focus areas (not started this session): package select page refinements; map pins / map cards (MapCard molecule is still "planned" in the registry).
- 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 navigation** — `role="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.ts``ProviderTier`, `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.
---