Add external asset hosting for Chromatic image rendering

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>
This commit is contained in:
2026-04-13 15:28:14 +10:00
parent 5b2a41f4e4
commit ab25af2e67
8 changed files with 142 additions and 43 deletions

View File

@@ -11,7 +11,7 @@ Publish the project to all three targets: backup, dev remotes, and Chromatic.
### Step 2 — Push to backup (everything) ### Step 2 — Push to backup (everything)
Push main directly to the `backup` remote (git.richie-snitch.online/richie/ParsonsFA). This is a private repo that receives the full repo including AI tooling, memory, and working docs. Push main directly to the `backup` remote (git.tensordesign.com.au/richie/ParsonsFA). This is a private repo that receives the full repo including AI tooling, memory, and working docs.
```bash ```bash
git push backup main git push backup main
@@ -50,23 +50,43 @@ git branch -D dev-clean
**Important:** The `--force` is safe here because the stripped branch is always regenerated from main. Dev remotes never have unique commits. **Important:** The `--force` is safe here because the stripped branch is always regenerated from main. Dev remotes never have unique commits.
### Step 4 — Deploy to Chromatic ### Step 4 — Sync assets to Gitea
Push the `brandassets/` directory to the dedicated assets repo so Chromatic can load images via external URLs.
```bash ```bash
npm run chromatic cd /tmp
rm -rf ParsonsAssets
git clone https://richie:151fdccacf11b6190d066a7e07f6f5310b2227dd@git.tensordesign.com.au/richie/ParsonsAssets.git
rsync -a --delete <project-root>/brandassets/ /tmp/ParsonsAssets/ --exclude='.git'
cd /tmp/ParsonsAssets
git add -A
git diff --cached --quiet || git commit -m "Sync assets from main project"
git push origin main
cd <project-root>
rm -rf /tmp/ParsonsAssets
``` ```
Run this in the background. Report the Storybook URL and build link when complete. This step is idempotent — if nothing changed, no commit is created. Skip if you know no images were added or changed since last publish.
### Step 5 — Report ### Step 5 — Deploy to Chromatic
```bash
STORYBOOK_ASSET_BASE=https://git.tensordesign.com.au/richie/ParsonsAssets/raw/branch/main npm run chromatic
```
The `STORYBOOK_ASSET_BASE` env var tells `assetUrl()` to resolve image paths from the Gitea assets repo instead of local static files. Run this in the background. Report the Storybook URL and build link when complete.
### Step 6 — Report
Summarise what was pushed: Summarise what was pushed:
``` ```
Published to all targets: Published to all targets:
- backup → git.richie-snitch.online/richie/ParsonsFA (full) - backup → git.tensordesign.com.au/richie/ParsonsFA (full)
- fa-dev → git.richie-snitch.online/richie/Parsons (stripped) - fa-dev → git.tensordesign.com.au/richie/Parsons (stripped)
- sheffield → sheffield.sapiente.casa/richie/ParsonsFA (stripped) - sheffield → sheffield.sapiente.casa/richie/ParsonsFA (stripped)
- assets → git.tensordesign.com.au/richie/ParsonsAssets (synced)
- Chromatic → [build link] - Chromatic → [build link]
``` ```

3
.gitignore vendored
View File

@@ -28,6 +28,9 @@ docs/reference/how-to-work-with-both-tools.md
docs/reference/mcp-setup.md docs/reference/mcp-setup.md
docs/reference/retroactive-review-plan.md docs/reference/retroactive-review-plan.md
# Deploy scripts (contain credentials)
scripts/
# Build logs # Build logs
build-storybook.log build-storybook.log

View File

@@ -26,6 +26,68 @@ Each entry follows this structure:
## Sessions ## Sessions
### Session 2026-04-13 — Chromatic image hosting + Gitea domain migration
**Agent(s):** Claude Opus 4.6 (1M context)
**Work completed:**
- **Gitea domain migration** — updated all remotes (backup, fa-dev) and references from `git.richie-snitch.online` to `git.tensordesign.com.au`. Updated publish skill and memory files.
- **Chromatic image hosting solution** — created `src/utils/assetUrl.ts` utility that resolves image paths via `STORYBOOK_ASSET_BASE` env var. When set (Chromatic builds), images load from Gitea ParsonsAssets repo. When empty (local dev), paths resolve via staticDirs as before.
- **Fixed path inconsistency** — story files used `/brandassets/images/...` which only worked locally via Vite root serving, not in builds. Changed to `/images/...` (correct for staticDirs) wrapped in `assetUrl()`.
- **Updated 4 files:** HomePage.tsx, HomePageV2.stories.tsx, HomePageV3.stories.tsx, HomePageV4.stories.tsx
- **Updated publish skill** — added Step 4 (sync assets to Gitea) and Step 5 (Chromatic with STORYBOOK_ASSET_BASE env var)
- **Created init script** — `scripts/init-assets-repo.sh` for one-time setup of ParsonsAssets repo on Gitea
**Decisions made:**
- ParsonsAssets Gitea repo must be **public** so Chromatic clients can load images without auth
- `/brandlogo/` SVG paths left as-is — they're small, tracked in git, and work via staticDirs in builds
- Only `/brandassets/images/...` paths converted to `assetUrl()` — these are the large untracked binaries
- Scripts directory gitignored (contains embedded credentials)
**Open questions:**
- User needs to create ParsonsAssets repo on Gitea and run `scripts/init-assets-repo.sh` (server not reachable from dev machine)
**Next steps:**
- Create public ParsonsAssets repo on git.tensordesign.com.au
- Run init script to push 1.1GB of brand assets
- Test `/publish` end-to-end to verify Chromatic images render
---
### Session 2026-04-12b — Homepage V3 redesign + FuneralFinder refinements
**Agent(s):** Claude Opus 4.6 (1M context)
**Work completed:**
- **HomePage V3 promoted to production** — story title changed from `Archive/HomePage V3` to `Pages/HomePage`
- **FuneralFinder V3 refinements:** responsive CTA (medium on mobile, large on desktop), button text "Search Local Providers" → "Search", "Free to use" bumped from captionSm to caption, tightened location pin-to-text gap
- **New section: "Why Use Funeral Arranger"** — text left + image right (people.png), overline + display3 heading + body copy
- **New section: "Three ways we can help you today"** — 3 feature cards with placeholder images, headings, descriptions, outlined CTA buttons
- **Section reorder:** partner logos carousel moved above Discover section, inherits overlap padding from FuneralFinder
- **Background colour scheme from Figma Make:** warm-grey alternating — `#f3efea` (darkest), `#fdfbf9` (lightest), `#f8f5f1` (mid), white sections with `#f3efea` borders
- **Overline headings** ("WHY USE FUNERAL ARRANGER", "WHAT YOU CAN DO HERE") styled in copper brand-600
- **Card drop shadows** on three feature cards (shadow-md, removed border)
- **Heading consistency:** all section headings unified to display3 serif; "Why Use FA" was h3, FAQ was h2 — both fixed
- **SEO fixes:** h1 grammar ("funeral directors pricing" → "funeral director pricing"), logo section heading `<p>``<h2>`, `aria-labelledby` on partner section
- **Section padding** increased from py 8/12 to 10/14 across all sections
- **Testimonials** left-aligned with consistent 560px max-width, centred block
- **"See more" button** added below FAQ accordion
- **FAQ heading** changed to "Frequently Asked Questions"
- **Logo section** changed to white bg, overline style heading, increased heading-to-logos gap
- **Figma capture** sent to Parsons Figma file (node 6049-25005)
**Decisions made:**
- Page-level warm-grey backgrounds use direct hex values (#f3efea, #fdfbf9, #f8f5f1) rather than brand tokens — brand-100 (#f7ecdf) is too golden; the Figma tones are neutral-warm
- All section headings use display3 (Noto Serif SC) visually, rendered as semantic h2 tags
- Three feature card buttons are outlined, text-only (no arrows)
**Next steps:**
- Replace feature card placeholder images with real screenshots/illustrations
- Wire up onClick handlers for feature card CTAs and FAQ "See more" when routing is in place
- Consider meta title/description for production deployment
---
### Session 2026-04-12 — ComparisonPage refinements + new molecules ### Session 2026-04-12 — ComparisonPage refinements + new molecules
**Agent(s):** Claude Opus 4.6 (1M context) **Agent(s):** Claude Opus 4.6 (1M context)

View File

@@ -13,6 +13,7 @@ import type { SxProps, Theme } from '@mui/material/styles';
import { Typography } from '../../atoms/Typography'; import { Typography } from '../../atoms/Typography';
import { Button } from '../../atoms/Button'; import { Button } from '../../atoms/Button';
import { ProviderCardCompact } from '../../molecules/ProviderCardCompact'; import { ProviderCardCompact } from '../../molecules/ProviderCardCompact';
import { assetUrl } from '../../../utils/assetUrl';
import { Divider } from '../../atoms/Divider'; import { Divider } from '../../atoms/Divider';
import { FuneralFinderV3, type FuneralFinderV3SearchParams } from '../../organisms/FuneralFinder'; import { FuneralFinderV3, type FuneralFinderV3SearchParams } from '../../organisms/FuneralFinder';
@@ -638,7 +639,7 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
}} }}
> >
<img <img
src="/brandassets/images/Homepage/people.png" src={assetUrl('/images/Homepage/people.png')}
alt="Family planning together with care and confidence" alt="Family planning together with care and confidence"
/> />
</Box> </Box>

View File

@@ -8,6 +8,7 @@ import { HomePage } from './HomePage';
import type { FeaturedProvider, TrustStat } from './HomePage'; import type { FeaturedProvider, TrustStat } from './HomePage';
import { Navigation } from '../../organisms/Navigation'; import { Navigation } from '../../organisms/Navigation';
import { Footer } from '../../organisms/Footer'; import { Footer } from '../../organisms/Footer';
import { assetUrl } from '../../../utils/assetUrl';
// ─── Shared helpers ────────────────────────────────────────────────────────── // ─── Shared helpers ──────────────────────────────────────────────────────────
@@ -231,7 +232,7 @@ export const Default: Story = {
args: { args: {
navigation: nav, navigation: nav,
footer, footer,
heroImageUrl: '/brandassets/images/heroes/parsonshero.png', heroImageUrl: assetUrl('/images/heroes/parsonshero.png'),
stats: trustStats, stats: trustStats,
featuredProviders, featuredProviders,
onSelectFeaturedProvider: (id) => console.log('Featured provider:', id), onSelectFeaturedProvider: (id) => console.log('Featured provider:', id),

View File

@@ -9,6 +9,7 @@ import type { FeaturedProvider, TrustStat, PartnerLogo } from './HomePage';
import React from 'react'; import React from 'react';
import { Navigation } from '../../organisms/Navigation'; import { Navigation } from '../../organisms/Navigation';
import { Footer } from '../../organisms/Footer'; import { Footer } from '../../organisms/Footer';
import { assetUrl } from '../../../utils/assetUrl';
// ─── Shared helpers ────────────────────────────────────────────────────────── // ─── Shared helpers ──────────────────────────────────────────────────────────
@@ -177,8 +178,8 @@ const featuredProviders: FeaturedProvider[] = [
name: 'H.Parsons Funeral Directors', name: 'H.Parsons Funeral Directors',
location: 'Wollongong, NSW', location: 'Wollongong, NSW',
verified: true, verified: true,
imageUrl: '/brandassets/images/venues/hparsons-funeral-home-kiama/01.jpg', imageUrl: assetUrl('/images/venues/hparsons-funeral-home-kiama/01.jpg'),
logoUrl: '/brandassets/images/providers/hparsons-funeral-directors/logo.png', logoUrl: assetUrl('/images/providers/hparsons-funeral-directors/logo.png'),
rating: 4.6, rating: 4.6,
reviewCount: 7, reviewCount: 7,
startingPrice: 900, startingPrice: 900,
@@ -188,8 +189,8 @@ const featuredProviders: FeaturedProvider[] = [
name: 'Rankins Funerals', name: 'Rankins Funerals',
location: 'Wollongong, NSW', location: 'Wollongong, NSW',
verified: true, verified: true,
imageUrl: '/brandassets/images/venues/rankins-funeral-home-warrawong/01.jpg', imageUrl: assetUrl('/images/venues/rankins-funeral-home-warrawong/01.jpg'),
logoUrl: '/brandassets/images/providers/rankins-funerals/logo.png', logoUrl: assetUrl('/images/providers/rankins-funerals/logo.png'),
rating: 4.8, rating: 4.8,
reviewCount: 23, reviewCount: 23,
startingPrice: 1200, startingPrice: 1200,
@@ -199,8 +200,8 @@ const featuredProviders: FeaturedProvider[] = [
name: 'Easy Funerals', name: 'Easy Funerals',
location: 'Sydney, NSW', location: 'Sydney, NSW',
verified: true, verified: true,
imageUrl: '/brandassets/images/venues/lakeside-memorial-park-chapel/01.jpg', imageUrl: assetUrl('/images/venues/lakeside-memorial-park-chapel/01.jpg'),
logoUrl: '/brandassets/images/providers/easy-funerals/logo.png', logoUrl: assetUrl('/images/providers/easy-funerals/logo.png'),
rating: 4.5, rating: 4.5,
reviewCount: 42, reviewCount: 42,
startingPrice: 850, startingPrice: 850,
@@ -209,30 +210,30 @@ const featuredProviders: FeaturedProvider[] = [
const partnerLogos: PartnerLogo[] = [ const partnerLogos: PartnerLogo[] = [
{ {
src: '/brandassets/images/providers/hparsons-funeral-directors/logo.png', src: assetUrl('/images/providers/hparsons-funeral-directors/logo.png'),
alt: 'H.Parsons Funeral Directors', alt: 'H.Parsons Funeral Directors',
}, },
{ src: '/brandassets/images/providers/rankins-funerals/logo.png', alt: 'Rankins Funerals' }, { src: assetUrl('/images/providers/rankins-funerals/logo.png'), alt: 'Rankins Funerals' },
{ src: '/brandassets/images/providers/easy-funerals/logo.png', alt: 'Easy Funerals' }, { src: assetUrl('/images/providers/easy-funerals/logo.png'), alt: 'Easy Funerals' },
{ src: '/brandassets/images/providers/lady-anne-funerals/logo.png', alt: 'Lady Anne Funerals' }, { src: assetUrl('/images/providers/lady-anne-funerals/logo.png'), alt: 'Lady Anne Funerals' },
{ {
src: '/brandassets/images/providers/killick-family-funerals/logo.png', src: assetUrl('/images/providers/killick-family-funerals/logo.png'),
alt: 'Killick Family Funerals', alt: 'Killick Family Funerals',
}, },
{ {
src: '/brandassets/images/providers/kenneallys-funerals/logo.png', src: assetUrl('/images/providers/kenneallys-funerals/logo.png'),
alt: "Kenneally's Funerals", alt: "Kenneally's Funerals",
}, },
{ {
src: '/brandassets/images/providers/wollongong-city-funerals/logo.png', src: assetUrl('/images/providers/wollongong-city-funerals/logo.png'),
alt: 'Wollongong City Funerals', alt: 'Wollongong City Funerals',
}, },
{ {
src: '/brandassets/images/providers/hparsons-funeral-directors-shoalhaven/logo.png', src: assetUrl('/images/providers/hparsons-funeral-directors-shoalhaven/logo.png'),
alt: 'H.Parsons Shoalhaven', alt: 'H.Parsons Shoalhaven',
}, },
{ {
src: '/brandassets/images/providers/mackay-family-funerals/logo.webp', src: assetUrl('/images/providers/mackay-family-funerals/logo.webp'),
alt: 'Mackay Family Funerals', alt: 'Mackay Family Funerals',
}, },
]; ];
@@ -257,13 +258,13 @@ export const Default: Story = {
args: { args: {
navigation: nav, navigation: nav,
footer, footer,
heroImageUrl: '/brandassets/images/heroes/hero-3.png', heroImageUrl: assetUrl('/images/heroes/hero-3.png'),
heroHeading: 'Compare funeral director pricing near you and arrange with confidence', heroHeading: 'Compare funeral director pricing near you and arrange with confidence',
heroSubheading: 'Transparent pricing \u00B7 No hidden fees \u00B7 Arrange 24/7', heroSubheading: 'Transparent pricing \u00B7 No hidden fees \u00B7 Arrange 24/7',
stats: trustStats, stats: trustStats,
featuredProviders, featuredProviders,
discoverMapSlot: React.createElement('img', { discoverMapSlot: React.createElement('img', {
src: '/brandassets/images/placeholder/map.png', src: assetUrl('/images/placeholder/map.png'),
alt: 'Map showing provider locations', alt: 'Map showing provider locations',
style: { width: '100%', height: '100%', objectFit: 'cover' }, style: { width: '100%', height: '100%', objectFit: 'cover' },
}), }),

View File

@@ -10,6 +10,7 @@ import { FuneralFinderV4 } from '../../organisms/FuneralFinder/FuneralFinderV4';
import React from 'react'; import React from 'react';
import { Navigation } from '../../organisms/Navigation'; import { Navigation } from '../../organisms/Navigation';
import { Footer } from '../../organisms/Footer'; import { Footer } from '../../organisms/Footer';
import { assetUrl } from '../../../utils/assetUrl';
// ─── Shared helpers ────────────────────────────────────────────────────────── // ─── Shared helpers ──────────────────────────────────────────────────────────
@@ -178,8 +179,8 @@ const featuredProviders: FeaturedProvider[] = [
name: 'H.Parsons Funeral Directors', name: 'H.Parsons Funeral Directors',
location: 'Wollongong, NSW', location: 'Wollongong, NSW',
verified: true, verified: true,
imageUrl: '/brandassets/images/venues/hparsons-funeral-home-kiama/01.jpg', imageUrl: assetUrl('/images/venues/hparsons-funeral-home-kiama/01.jpg'),
logoUrl: '/brandassets/images/providers/hparsons-funeral-directors/logo.png', logoUrl: assetUrl('/images/providers/hparsons-funeral-directors/logo.png'),
rating: 4.6, rating: 4.6,
reviewCount: 7, reviewCount: 7,
startingPrice: 900, startingPrice: 900,
@@ -189,8 +190,8 @@ const featuredProviders: FeaturedProvider[] = [
name: 'Rankins Funerals', name: 'Rankins Funerals',
location: 'Wollongong, NSW', location: 'Wollongong, NSW',
verified: true, verified: true,
imageUrl: '/brandassets/images/venues/rankins-funeral-home-warrawong/01.jpg', imageUrl: assetUrl('/images/venues/rankins-funeral-home-warrawong/01.jpg'),
logoUrl: '/brandassets/images/providers/rankins-funerals/logo.png', logoUrl: assetUrl('/images/providers/rankins-funerals/logo.png'),
rating: 4.8, rating: 4.8,
reviewCount: 23, reviewCount: 23,
startingPrice: 1200, startingPrice: 1200,
@@ -200,8 +201,8 @@ const featuredProviders: FeaturedProvider[] = [
name: 'Easy Funerals', name: 'Easy Funerals',
location: 'Sydney, NSW', location: 'Sydney, NSW',
verified: true, verified: true,
imageUrl: '/brandassets/images/venues/lakeside-memorial-park-chapel/01.jpg', imageUrl: assetUrl('/images/venues/lakeside-memorial-park-chapel/01.jpg'),
logoUrl: '/brandassets/images/providers/easy-funerals/logo.png', logoUrl: assetUrl('/images/providers/easy-funerals/logo.png'),
rating: 4.5, rating: 4.5,
reviewCount: 42, reviewCount: 42,
startingPrice: 850, startingPrice: 850,
@@ -210,30 +211,30 @@ const featuredProviders: FeaturedProvider[] = [
const partnerLogos: PartnerLogo[] = [ const partnerLogos: PartnerLogo[] = [
{ {
src: '/brandassets/images/providers/hparsons-funeral-directors/logo.png', src: assetUrl('/images/providers/hparsons-funeral-directors/logo.png'),
alt: 'H.Parsons Funeral Directors', alt: 'H.Parsons Funeral Directors',
}, },
{ src: '/brandassets/images/providers/rankins-funerals/logo.png', alt: 'Rankins Funerals' }, { src: assetUrl('/images/providers/rankins-funerals/logo.png'), alt: 'Rankins Funerals' },
{ src: '/brandassets/images/providers/easy-funerals/logo.png', alt: 'Easy Funerals' }, { src: assetUrl('/images/providers/easy-funerals/logo.png'), alt: 'Easy Funerals' },
{ src: '/brandassets/images/providers/lady-anne-funerals/logo.png', alt: 'Lady Anne Funerals' }, { src: assetUrl('/images/providers/lady-anne-funerals/logo.png'), alt: 'Lady Anne Funerals' },
{ {
src: '/brandassets/images/providers/killick-family-funerals/logo.png', src: assetUrl('/images/providers/killick-family-funerals/logo.png'),
alt: 'Killick Family Funerals', alt: 'Killick Family Funerals',
}, },
{ {
src: '/brandassets/images/providers/kenneallys-funerals/logo.png', src: assetUrl('/images/providers/kenneallys-funerals/logo.png'),
alt: "Kenneally's Funerals", alt: "Kenneally's Funerals",
}, },
{ {
src: '/brandassets/images/providers/wollongong-city-funerals/logo.png', src: assetUrl('/images/providers/wollongong-city-funerals/logo.png'),
alt: 'Wollongong City Funerals', alt: 'Wollongong City Funerals',
}, },
{ {
src: '/brandassets/images/providers/hparsons-funeral-directors-shoalhaven/logo.png', src: assetUrl('/images/providers/hparsons-funeral-directors-shoalhaven/logo.png'),
alt: 'H.Parsons Shoalhaven', alt: 'H.Parsons Shoalhaven',
}, },
{ {
src: '/brandassets/images/providers/mackay-family-funerals/logo.webp', src: assetUrl('/images/providers/mackay-family-funerals/logo.webp'),
alt: 'Mackay Family Funerals', alt: 'Mackay Family Funerals',
}, },
]; ];
@@ -258,7 +259,7 @@ export const Default: Story = {
args: { args: {
navigation: nav, navigation: nav,
footer, footer,
heroImageUrl: '/brandassets/images/heroes/hero-3.png', heroImageUrl: assetUrl('/images/heroes/hero-3.png'),
heroHeading: 'Compare funeral directors pricing near you and arrange with confidence', heroHeading: 'Compare funeral directors pricing near you and arrange with confidence',
heroSubheading: 'Transparent pricing \u00B7 No hidden fees \u00B7 Arrange 24/7', heroSubheading: 'Transparent pricing \u00B7 No hidden fees \u00B7 Arrange 24/7',
finderSlot: React.createElement(FuneralFinderV4, { finderSlot: React.createElement(FuneralFinderV4, {
@@ -267,7 +268,7 @@ export const Default: Story = {
stats: trustStats, stats: trustStats,
featuredProviders, featuredProviders,
discoverMapSlot: React.createElement('img', { discoverMapSlot: React.createElement('img', {
src: '/brandassets/images/placeholder/map.png', src: assetUrl('/images/placeholder/map.png'),
alt: 'Map showing provider locations', alt: 'Map showing provider locations',
style: { width: '100%', height: '100%', objectFit: 'cover' }, style: { width: '100%', height: '100%', objectFit: 'cover' },
}), }),

10
src/utils/assetUrl.ts Normal file
View File

@@ -0,0 +1,10 @@
/**
* Resolves a static asset path. In local dev the path is served by Storybook's
* staticDirs; when STORYBOOK_ASSET_BASE is set (e.g. Chromatic builds) it
* prepends the external host URL so images load from Gitea.
*/
export const assetUrl = (path: string): string => {
const base =
typeof import.meta !== 'undefined' ? (import.meta.env?.STORYBOOK_ASSET_BASE ?? '') : '';
return `${base}${path}`;
};