# Design decisions log

Every design decision that affects tokens, components, or system architecture
MUST be logged here with rationale. This ensures consistency across sessions
and agents. Agents MUST check this file before making any decision that could
contradict a previous one.

## Format

```
### [Decision ID] — [Short title]
**Date:** [date]
**Category:** token | component | architecture | convention
**Decision:** [What was decided]
**Rationale:** [Why this decision was made]
**Affects:** [Which tokens/components/files this impacts]
**Alternatives considered:** [What else was considered and why it was rejected]
```

---

## Decisions

### D001 — Primary brand colour is warm gold #BA834E
**Date:** 2025-03-25
**Category:** token
**Decision:** Primary brand colour (color.brand.500) is #BA834E — warm gold.
**Rationale:** Extracted from Parsons brand swatches. Matches the existing FA 1.0 CTA button colour ("Add to package"). Warm gold conveys trust and professionalism appropriate for funeral services. The colour has a 3.7:1 contrast ratio on white, suitable for large text and interactive elements (buttons with white text).
**Affects:** color.brand.500, color.interactive.default, MUI palette.primary.main
**Alternatives considered:** #B0610F (copper) was considered for primary but it's darker and more aggressive. Placed at brand.600 for hover/emphasis instead.

### D002 — Primary text colour is charcoal #2C2E35 (not pure black)
**Date:** 2025-03-25
**Category:** token
**Decision:** Primary text colour is #2C2E35 (color.neutral.800) — charcoal with cool blue undertone.
**Rationale:** From brand swatch. Softer than pure black for extended reading comfort while maintaining 13.2:1 contrast ratio on white (well exceeds WCAG AA). The cool undertone complements the sage secondary palette.
**Affects:** color.text.primary, MUI palette.text.primary
**Alternatives considered:** Pure black (#000000) — higher contrast but causes eye fatigue on white backgrounds. Added as color.black for rare use.

### D003 — Brand link/accent colour uses copper #B0610F
**Date:** 2025-03-25
**Category:** token
**Decision:** Brand-coloured text (links, inline emphasis) uses #B0610F (color.brand.600 / copper) not the base gold.
**Rationale:** The base gold #BA834E has only 3.7:1 contrast on white which doesn't meet WCAG AA for normal text (4.5:1 required). The copper #B0610F provides 4.8:1 contrast, meeting AA. It's also visually more assertive for link text.
**Affects:** color.text.brand, color.interactive.hover
**Alternatives considered:** brand.700 (#8B4E0D) at 6.7:1 — meets AAA but is too dark and loses the warm "brand" feel.

### D004 — Sage palette as secondary colour family
**Date:** 2025-03-25
**Category:** token
**Decision:** Secondary palette uses cool sage/slate tones (#4C5B6B as sage.700) rather than a second warm colour.
**Rationale:** From brand swatches (cool row). The sage provides visual contrast against the warm brand palette and adds a calming, professional quality appropriate for the funeral services context. Used for secondary buttons and less prominent actions.
**Affects:** color.sage.*, color.interactive.secondary, MUI palette.secondary
**Alternatives considered:** Using a lighter warm tone for secondary — rejected as it wouldn't provide enough visual distinction from primary.

### D005 — Display font is Noto Serif SC for H1-H2 headings
**Date:** 2025-03-25
**Category:** token
**Decision:** Display/heading font is Noto Serif SC for Display, H1, and H2 levels. H3+ use Montserrat (body font).
**Rationale:** User specified Noto Serif SC as display font and Montserrat as primary. The serif at H1-H2 adds warmth and gravitas suitable for funeral services. H3+ switches to Montserrat to create a clear hierarchy break.
**Affects:** fontFamily.display, fontFamily.body, typography.display/h1/h2/h3/h4
**Alternatives considered:** Using serif for all headings — rejected as it would make H3/H4 feel too formal at smaller sizes.

### D006 — Warning text uses amber.700 for AA compliance
**Date:** 2025-03-25
**Category:** token
**Decision:** Warning text colour is amber.700 (#A36B00) not amber.600 (#CC8500).
**Rationale:** amber.600 (3.6:1 contrast on white) only passes AA for large text. For warning text that must be readable at body size, amber.700 at 5.1:1 meets WCAG AA for normal text.
**Affects:** color.text.warning
**Alternatives considered:** amber.800 — passes AAA but is too dark to read as "amber/warning".

### D007 — Style Dictionary requires usesDtcg: true
**Date:** 2025-03-25
**Category:** architecture
**Decision:** Added `usesDtcg: true` to Style Dictionary v4.4.0 config.
**Rationale:** Without this flag, SD v4 doesn't properly populate `allTokens` for the CSS `css/variables` format, resulting in empty CSS output. JS and JSON formats work without it but CSS requires it for DTCG token format.
**Affects:** style-dictionary/config.js, CSS output
**Alternatives considered:** Custom format — rejected as the built-in format works correctly with the flag.

### D008 — Responsive typography via @media in MUI theme
**Date:** 2025-03-25
**Category:** architecture
**Decision:** Mobile font sizes are handled via `@media (max-width:600px)` queries in the MUI theme typography config, not via `responsiveFontSizes()`.
**Rationale:** Explicit media queries allow precise control over mobile sizes (28px, 24px, 20px, 18px) matching the token definitions. MUI's `responsiveFontSizes()` uses its own scaling algorithm that can't be configured to match our exact values.
**Affects:** src/theme/index.ts, typography.*.fontSizeMobile tokens
**Alternatives considered:** `responsiveFontSizes()` utility — rejected due to lack of precise size control.

### D009 — Merge Text Button into Button atom
**Date:** 2026-03-25
**Category:** component
**Decision:** Text Button is not a separate atom. It is implemented as `<Button variant="text">` with an optional `underline` prop for link-style appearance.
**Rationale:** In MUI, text buttons are a variant of Button (`variant="text"`), not a separate component. Having both Button and TextButton creates API confusion. The Figma "ghost" variant and "text button" both map to MUI's text variant. The `underline` prop handles the design's "default.underline" state.
**Affects:** Button component API, Storybook story structure
**Alternatives considered:** Separate TextButton component — rejected as it duplicates Button logic and creates ambiguity for developers.

### D010 — Button sizes adjusted for touch targets
**Date:** 2026-03-25
**Category:** component
**Decision:** Button sizes are xs: 28px, sm: 32px, md: 40px, lg: 48px — all multiples of 4px. Large size (48px) meets the 44px minimum touch target for mobile.
**Rationale:** Original Figma sizes (sm: 26px, md: 37px) don't meet the 44px minimum touch target specified in the design system conventions. Adjusted to a clean 4px-grid scale that includes a large size suitable for mobile CTAs. FA's audience may be older or in emotional distress — generous touch targets are important.
**Affects:** tokens/component/button.json, MUI theme MuiButton overrides
**Alternatives considered:** Keeping original Figma sizes — rejected because neither met the 44px touch target minimum.

### D011 — Use @mui/icons-material over Font Awesome
**Date:** 2026-03-25
**Category:** architecture
**Decision:** Primary icon source is `@mui/icons-material`. Custom SVGs wrapped in MUI's `SvgIcon` for any FA-specific icons.
**Rationale:** MUI icons extend `SvgIcon`, so they work natively with MUI Button's `startIcon`/`endIcon`, inherit theme colours, and are tree-shakeable. Font Awesome requires an adapter layer to work with MUI's icon API. All standard UI icons from the Figma designs exist in the MUI icon set.
**Affects:** Icon approach, Button icon integration, dependencies
**Alternatives considered:** `@fortawesome/react-fontawesome` — rejected due to adapter complexity and extra dependency. User was using FA icons in Figma but agreed MUI icons are more pragmatic for the code implementation.

### D012 — Button responsive sizing is a composition concern
**Date:** 2026-03-25
**Category:** architecture
**Decision:** Button size is an explicit prop (`size="xs" | "small" | "medium" | "large"`), not responsive by default. Layout components choose the appropriate size per breakpoint.
**Rationale:** Keeps the Button component simple and predictable. Responsive behaviour is a layout decision — a card might use `size="large"` on mobile for touch targets and `size="medium"` on desktop. This avoids the complexity of Figma variable modes for mobile/desktop scaling inside components.
**Affects:** Button component API, consumer usage patterns
**Alternatives considered:** Figma variable modes for responsive scaling within the component — rejected as over-engineered. MUI's `useMediaQuery` or responsive `sx` props handle this at the composition level.

### D013 — Soft variant for tonal/muted buttons
**Date:** 2026-03-25
**Category:** component
**Decision:** Added `variant="soft"` custom MUI Button variant for tonal/muted fill buttons. Maps to Figma's "Secondary/Brand" and "Secondary/Grey" columns.
**Rationale:** Figma review revealed that "Secondary/Brand" (bg #e4cdb3, text #845830) and "Secondary/Grey" (bg #dcdde0, text #434b52) are NOT outlined buttons — they are filled buttons with softer, tonal colours. This pattern is common in modern design systems (Material Design 3 calls it "tonal", Radix calls it "soft"). Primary soft uses brand.200 bg + brand.700 text. Secondary soft uses neutral.200 bg + neutral.700 text.
**Affects:** MUI theme MuiButton variants, Button component API, Storybook stories
**Alternatives considered:** Separate color options for each Figma column — rejected as it doesn't map to MUI's color+variant model. The soft variant is more extensible — any future color automatically gets a soft treatment.

### D014 — Secondary palette changed from sage to neutral grey
**Date:** 2026-03-25
**Category:** token
**Decision:** MUI secondary palette changed from sage (#4C5B6B sage.700) to neutral grey (#525252 neutral.600). Sage remains available in the token system for surfaces but is no longer the secondary button colour.
**Rationale:** User feedback that secondary buttons should match Figma's "Secondary/Grey" which uses neutral tones, not the blue-green sage. The sage palette is still valuable for cool surfaces (color.surface.cool) but shouldn't be the primary secondary interactive colour.
**Affects:** MUI palette.secondary, all MuiButton secondary overrides (outlined, text, soft)
**Alternatives considered:** Adding a third "neutral" custom colour and keeping sage as secondary — rejected as unnecessarily complex when no other components use the sage secondary yet.

### D015 — Loading spinner positioned on the right
**Date:** 2026-03-25
**Category:** component
**Decision:** Button loading spinner (CircularProgress) appears after the label text, on the right side of the button.
**Rationale:** User preference. Right-side positioning follows the convention of trailing indicators (like endIcon) and feels more natural for async state feedback.
**Affects:** Button component render order
**Alternatives considered:** Left-side spinner (before text) — rejected per user feedback.

### D016 — Primary button colour confirmed as copper (brand.600)
**Date:** 2026-03-25
**Category:** token
**Decision:** Primary button fill (palette.primary.main) stays as brand.600 copper (#B0610F), not brand.500 warm gold (#BA834E). User confirmed.
**Rationale:** Brand.600 provides 4.8:1 contrast ratio with white text, meeting WCAG AA for all text sizes. Brand.500 only achieves 3.7:1 (AA for large text only). Since button labels can vary in size, the stronger contrast is preferred. The visual warmth is preserved through the soft variant (brand.200 bg) and hover states.
**Affects:** palette.primary.main, all contained primary buttons, interactive token
**Alternatives considered:** Brand.500 warm gold — rejected as it doesn't meet AA for normal-sized text.

### D017 — Headings use Montserrat Bold, not Noto Serif SC
**Date:** 2026-03-25
**Category:** token
**Decision:** All headings (h1-h6) use Montserrat Bold (700), not Noto Serif SC. Noto Serif SC is reserved exclusively for display variants (displayHero through displaySm).
**Rationale:** User's FA 2.0 Figma design separates display (serif, for marketing/hero) from headings (sans-serif, for content structure). This is cleaner than the previous approach where h1-h2 used serif. Montserrat Bold provides strong hierarchy without the formality of serif at content-level sizes.
**Affects:** MUI theme h1-h6 fontFamily, semantic typography tokens. Supersedes D005.
**Alternatives considered:** Keep D005 (serif for h1-h2) — rejected per user's updated Figma design.

### D018 — Display weight is Regular (400), not Bold
**Date:** 2026-03-25
**Category:** token
**Decision:** Display text (Noto Serif SC) uses Regular weight (400) across all 5 display levels, not Bold (700).
**Rationale:** Serif fonts carry inherent visual weight. At 40-80px, Regular Noto Serif SC has enormous presence. Bold at these sizes would be overwhelming and less elegant.
**Affects:** typography.displayHero/1/2/3/Sm fontWeight tokens
**Alternatives considered:** Bold (700) — the previous implementation. Rejected per user's Figma which uses Regular.

### D019 — Body text weight is Medium (500)
**Date:** 2026-03-25
**Category:** token
**Decision:** Body text uses Montserrat Medium (500) instead of Regular (400). User found Regular too light for comfortable reading.
**Rationale:** User feedback that Montserrat Regular is "just a little bit too light." The Figma specified weight 450 which isn't available in Google Fonts, so 500 (Medium) is the closest available weight that provides the desired visual density.
**Affects:** typography.body/bodyLg/bodySm/bodyXs fontWeight tokens
**Alternatives considered:** 400 Regular — too light per user. 450 — not available in Google Fonts Montserrat.

### D020 — Expanded type scale: 21 variants across 6 categories
**Date:** 2026-03-25
**Category:** token
**Decision:** Typography system expanded from ~12 to 21 variants organized in 6 categories: Display (5 levels), Heading (6 levels), Body (4 sizes), Label (3 sizes), Caption (2 sizes), Overline (2 sizes). Caption/sm and overline/sm floored at 11px for accessibility.
**Affects:** primitive typography tokens, semantic typography tokens, MUI theme, Typography component
**Alternatives considered:** Keeping the simpler scale — rejected as user explicitly wanted more variety matching their FA 2.0 Figma type system.

### D021 — Input uses external label pattern, not MUI floating label
**Date:** 2026-03-25
**Category:** component
**Decision:** Input label sits above the input field as a separate element, not as MUI's floating/outlined label that sits on the border. Uses MUI's `InputLabel` with `position: static` and `shrink: true`.
**Rationale:** The Figma design shows the label as a distinct element above the input with a 6px gap. The floating label pattern is harder to read and interact with, especially for FA's audience who may be elderly or in distress. External labels have better usability research support for form comprehension.
**Affects:** Input component structure, MUI InputLabel styling, no notch in OutlinedInput
**Alternatives considered:** MUI's default floating label (outlined variant) — rejected as it doesn't match the Figma design and has usability concerns for the target audience.

### D022 — Input sizes: medium (48px) and small (40px)
**Date:** 2026-03-25
**Category:** component
**Decision:** Input has two sizes: medium (48px, default) and small (40px). Medium aligns with Button large (48px) for search bar pairing. Small aligns with Button medium (40px) for compact layouts.
**Rationale:** Figma designed inputs at 64px which is too tall relative to other components and creates misalignment with buttons. 48px meets the 44px touch target minimum and aligns with our Button large. Both sizes sit on the 4px grid.
**Affects:** tokens/component/input.json, MUI theme MuiOutlinedInput size overrides
**Alternatives considered:** Single size at 64px (Figma original) — rejected as it dwarfs adjacent buttons. Three sizes (sm/md/lg) — rejected as premature; two sizes cover all current form patterns.

### D023 — Input focus ring uses brand.500 warm gold, not brand.600 copper
**Date:** 2026-03-25
**Category:** component
**Decision:** Input focus ring uses brand.500 (#BA834E warm gold) via a double box-shadow (2px white + 4px brand), not the brand.600 copper used for Button focus outlines.
**Rationale:** The Figma FA 2.0 design explicitly specifies brand.500 for the input focus ring as an `elevation-special/focus-ring` effect token. The warm gold is softer and more appropriate for form inputs where the focus indicator should be visible but not aggressive. Buttons use brand.600 outline because they need to stand out against coloured backgrounds.
**Affects:** MUI theme MuiOutlinedInput focus styles
**Alternatives considered:** Using brand.600 (same as Button) — rejected as the Figma explicitly uses brand.500 for inputs.

### D024 — Input label stays neutral on error/success states
**Date:** 2026-03-25
**Category:** component
**Decision:** Input label colour remains `text.secondary` regardless of error/success state. Only the border and helper text change colour.
**Rationale:** Per the Figma design. A full-red label would feel alarming, which is inappropriate for FA's sensitive context. The subtle approach (border + helper text colour change) communicates the issue without distress. Users who are already in an emotional state don't need aggressive visual feedback.
**Affects:** InputLabel Mui-error and Mui-focused style overrides
**Alternatives considered:** Colouring the label red on error — rejected as too aggressive for the audience.

### D025 — Added startIcon/endIcon convenience props and success state
**Date:** 2026-03-25
**Category:** component
**Decision:** Input component adds three features beyond the Figma design: (1) `startIcon` prop for leading icons, (2) `endIcon` prop for trailing icons (convenience wrapper around MUI adornments), (3) `success` boolean prop for validation success state (green border + green helper text). Error-state icons (ErrorOutline) and success-state icons (CheckCircleOutline) are recommended patterns in stories.
**Rationale:** Leading icons (email, phone, dollar) are essential for FA's arrangement forms. Success state provides positive feedback after validation. The Figma only had trailing icon, but leading icons are a near-universal production need. The `startIcon`/`endIcon` props are simpler than MUI's `InputAdornment` pattern while remaining compatible with raw adornments via `startAdornment`/`endAdornment`.
**Affects:** Input component API, InputAdornment usage, Storybook stories
**Alternatives considered:** Only supporting MUI's raw adornment API — rejected as too verbose for the common case. The convenience props are ergonomic while the raw props remain available for complex cases (e.g., password toggle with IconButton).

### D026 — ProviderCard establishes the molecule pattern
**Date:** 2026-03-25
**Category:** architecture
**Decision:** ProviderCard is the first molecule. Molecules compose existing atoms via `sx` props — no MUI theme overrides. All styling from token CSS variables and theme accessors.
**Rationale:** Keeps molecules lightweight and composable. Theme overrides are reserved for atoms where global consistency matters. Molecules are higher-level compositions with more context-specific layouts.
**Affects:** All future molecules (VenueCard, MapCard, ServiceOption, etc.)
**Alternatives considered:** Adding MuiProviderCard theme overrides — rejected as molecules are page-specific compositions, not reusable primitives.

### D027 — ProviderCard image is a URL string, not a ReactNode
**Date:** 2026-03-25
**Category:** component
**Decision:** `imageUrl` prop accepts a string rendered as CSS `background-image` with `cover`. Logo is also a URL string rendered as `<img>`.
**Rationale:** Simpler API for the common case (CDN URLs). CSS background-image handles aspect ratio cropping automatically. `<img>` for logo supports `alt` text natively for accessibility.
**Affects:** ProviderCard, VenueCard, MapCard APIs
**Alternatives considered:** ReactNode for image — rejected as over-flexible. A ReactNode slot would allow video/carousel but adds API complexity for a feature we don't need yet.

### D028 — Logo is 48px (not 75px from Figma)
**Date:** 2026-03-25
**Category:** component
**Decision:** Logo overlay is 48px diameter, not the 75px shown in Figma.
**Rationale:** On a ~340-400px card in a list panel, a 75px logo dominates the content area. 48px provides clear brand visibility while leaving sufficient space for the provider name and meta row.
**Affects:** providerCard.logo.size token
**Alternatives considered:** 75px from Figma — too large for the card width. 64px — still large relative to content.

### D029 — Footer is built into ProviderCard, not a slot
**Date:** 2026-03-25
**Category:** component
**Decision:** The "Packages from $X >" footer is rendered via a `startingPrice` number prop, not a children/slot pattern.
**Rationale:** The footer is structurally identical across all provider cards — warm background, "Packages from" text, price, chevron. Only the price value changes. A slot would add API complexity for no flexibility gain. If footer is omitted (no startingPrice), the bar is simply absent.
**Affects:** ProviderCard API, VenueCard may use a similar pattern
**Alternatives considered:** children/render prop for footer — rejected as over-engineered for a fixed layout.

### D030 — Verified is an explicit boolean, not derived from imageUrl
**Date:** 2026-03-25
**Category:** component
**Decision:** `verified` boolean prop controls the visual treatment independently from `imageUrl`/`logoUrl`.
**Rationale:** A provider could be verified but have a broken/missing image URL. The boolean is the source of truth from the business layer. Image and logo are only rendered when `verified` is also true, preventing accidental display of unverified providers with images.
**Affects:** ProviderCard API, data model expectations
**Alternatives considered:** Deriving verified from imageUrl presence — rejected as it couples business logic (partner status) to content availability (image uploaded).

### D031 — Token access convention: theme accessors for semantic, CSS vars for component
**Date:** 2026-03-27
**Category:** architecture
**Decision:** Two token access methods are both valid, used by tier:
- **`theme.palette.*`, `theme.typography.*`, `theme.spacing()`** — for semantic tokens that MUI maps into its theme (palette colours, typography variants, spacing). Used when the MUI theme callback is already available.
- **`var(--fa-*)`** CSS variables — for component-tier tokens (badge sizes, card shadows, input dimensions) generated by Style Dictionary but not mapped into the MUI theme. Also acceptable for semantic tokens when the CSS variable form is more ergonomic (e.g., in colour maps, static styles).
**Rationale:** The codebase naturally evolved this dual-path pattern. Semantic tokens are mapped into the MUI theme by `src/theme/index.ts`, making `theme.palette.*` the idiomatic MUI access path. Component-tier tokens exist only as CSS variables (Style Dictionary output). Forcing all access through one path would either require mapping every component token into MUI (over-engineering) or abandoning MUI's type-safe theme accessors (losing DX).
**Affects:** All component files, `docs/conventions/component-conventions.md`, Badge.tsx (normalized to CSS vars for colour maps)
**Alternatives considered:** CSS vars only — rejected because it loses MUI's TypeScript-safe theme API. Theme accessors only — rejected because component tokens aren't in the MUI theme.

### D032 — FuneralFinder V3 is the production version
**Date:** 2026-03-27
**Category:** component
**Decision:** FuneralFinderV3 is the production widget. V1 and V2 are archived — their Storybook stories moved to `Archive/FuneralFinder V1` and `Archive/FuneralFinder V2`. Code remains in the repo for reference and client walkthroughs.
**Rationale:** User chose V3 after reviewing all three. V3 scored highest on usability (clean form, no sequential lock, always-active CTA) while maintaining audit/critique scores (18/20, 33/40). V1 and V2 must remain viewable for client presentations showing the design evolution.
**Affects:** FuneralFinder stories (title changes), component-registry (V3 = done, V1/V2 = archived), retroactive review (only V3 gets full treatment)
**Alternatives considered:** Deleting V1/V2 — rejected because user needs them for client walkthroughs.

### D034 — Form error styling uses copper, not red
**Date:** 2026-03-29
**Category:** token
**Decision:** Form error borders and helper text use copper (#B0610F / ColorTextBrand) instead of MUI's default red (#BC2F2F). `palette.error.main` remains red for non-form uses (destructive buttons, system alerts).
**Rationale:** FA's grief-sensitive context makes aggressive red inappropriate for form validation. D024 established labels stay neutral on error; D034 extends this to borders and helper text for a fully warm error experience. Copper at 4.8:1 contrast on white meets WCAG AA.
**Affects:** src/theme/index.ts (MuiOutlinedInput, MuiFormHelperText, MuiFormLabel overrides), ToggleButtonGroup atom, all page-level error Typography elements
**Alternatives considered:** Changing `palette.error.main` globally to copper — rejected because destructive action buttons and system alerts may genuinely need red.

### D033 — Retroactive review uses P0/P1 only, interleaved with new work
**Date:** 2026-03-27
**Category:** architecture
**Decision:** Existing components are reviewed using a condensed process (normalize → audit → fix P0/P1 only). Review runs as a proactive "morning housekeeping" pass at the start of each session (~30-60 min), then shifts to new component work.
**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.
