Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
c63375c
LIVE-1963: intake the Create a Space v3 handoff, add the phase rows a…
claude Nov 2, 2026
342fe08
LIVE-1963: close the intake row with its probe
claude Nov 2, 2026
ff78b23
LIVE-1964: Create a Space v3 pure core: offer to intents, carry keys,…
claude Nov 2, 2026
0673dbc
Merge the merged Phase 0 history so the branch fast-forwards
claude Nov 2, 2026
29720ea
Shrink the create-a-space design-system baseline: four handoff lines …
claude Nov 2, 2026
699adac
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 2, 2026
57b359c
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 2, 2026
79195da
LIVE-1965: Create a Space v3 hero builder: intro and building modes, …
claude Nov 2, 2026
e33b1d0
LIVE-1965: the intro H1 lives in source as the one sentence the SEO p…
claude Nov 2, 2026
c604b31
Merge origin/main into the Phase 2 branch: the LIVE-1978 progressbar …
claude Nov 2, 2026
8936403
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 2, 2026
2e66e72
LIVE-1966: Create a Space v3 photos: their own cover, logo and host p…
claude Nov 2, 2026
a62e88a
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 2, 2026
e42c70d
LIVE-1969: Create a Space v3 doors: every logged-out launch, build, S…
claude Nov 2, 2026
3af563e
Doors probe: read the proxy door set with a fixed pattern, no built R…
claude Nov 2, 2026
c333000
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 2, 2026
158133d
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 2, 2026
c2051e9
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 3, 2026
4b94474
LIVE-1967: Create a Space v3 lead capture: the draft saves to its ema…
claude Nov 2, 2026
e26b4b5
LIVE-1967: grant verdict and admin-client baseline for create_space_d…
claude Nov 3, 2026
f27c73f
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 3, 2026
4c0cf89
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 3, 2026
3b6e14d
LIVE-1968: Create a Space v3 abandon recovery: three draft reminder e…
claude Nov 2, 2026
5d7c753
ARCHITECTURE.md: 41 cron jobs, create-space-draft-reminders joined
claude Nov 3, 2026
58ba3e4
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 3, 2026
a461817
LIVE-1970: Create a Space v3 funnel: nine anonymous signals, their ow…
claude Nov 2, 2026
73586ba
Merge remote-tracking branch 'origin/main' into HEAD
claude Nov 3, 2026
af059e8
LIVE-1971: Create a Space v3 hardening: the e2e walk, the funnel jour…
claude Nov 2, 2026
3a17032
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Nov 3, 2026
43a89ca
LIVE-2063: Create a Space: capture on type, kit and accent cascade, a…
claude Nov 3, 2026
23d1486
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Nov 3, 2026
e4f0d28
LIVE-2064: the launched welcome email, sent once the moment a launch …
claude Nov 3, 2026
eb17d25
LIVE-2064: declare the delegated authz on the welcome sender, type th…
claude Nov 3, 2026
d9c0b41
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Nov 3, 2026
a150209
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Nov 3, 2026
a8a50d5
LIVE-2065: the Temple kit builder preview wears its own accents, surf…
claude Nov 3, 2026
f6df863
LIVE-2066: renumber the row, LIVE-2065 is taken by the earlier-opened…
claude Nov 3, 2026
c91157d
LIVE-2067: New World (preselected) and Services join the signed-out b…
claude Nov 3, 2026
eb8b170
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Nov 3, 2026
63f3c25
LIVE-2067: gate the builder picker on the one hidden flag
claude Nov 3, 2026
000e971
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Nov 3, 2026
c00cb41
LIVE-2084: Free Human and Services builder previews wear their own sh…
claude Nov 3, 2026
88364b0
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Nov 3, 2026
786d711
Services depth ramp: rename --sv-shadow-{sm,md,lg} to --sv-depth-* so…
claude Nov 3, 2026
9dd1e09
Renumber the row LIVE-2084 to LIVE-2102: main took LIVE-2084 for the …
claude Nov 3, 2026
70c881d
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Nov 3, 2026
56ed263
Drop the --sv-depth-* rename: the Services design-system check pins t…
claude Nov 3, 2026
266913d
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Nov 3, 2026
02742df
Merge branch 'main' into claude/project-thread-fddsfo
hellofrequencylab Nov 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/ledger/rows/LIVE-2102.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"id": "LIVE-2102",
"title": "Create a Space builder: the Free Human and Services previews wear their own shape and pictures; SHAPE and KIT_IMAGERY are total",
"lane": "live",
"priority": "P1",
"size": "S",
"status": "done",
"source": {
"file": "lib/create-space/preview-site.ts",
"ref": "Promised to Daniel 2026-11-03 05:52Z: the signed-out builder previews for Free Human and Services wear their own accents, shape and photos, like the Temple kit (LIVE-2066) and Nocturne (LIVE-2082)"
},
"detail": "Free Human had accents of its own (LIVE-2066) but no shape and no pictures in the builder preview: previewTokens fell back to Temple's shape and kitImagery to the gathering photos. The preview now wears the kit's own faces (Cormorant Garamond display with the clay on the emphasised word, Hanken Grotesk body, .2em labels), its 16px controls, 40px cards, the arch on its pictures and its depth-2 shadow, and its own photo library (the walking virgin hero, the founder portrait, the daisies, sun and floating candles). The font sheet loads Cormorant Garamond at 400 too. free-human joins the preview kit order behind New World and Services, gated like them on the one hidden flag (RETIRED_FROM_PICKERS), so it reaches the builder the moment it is un-hidden. Services already had its shape (Bricolage and Lora italic, 20px cards, pill buttons) and the New World pictures from LIVE-2067 and its six accents from LIVE-2066; it has no photo library of its own, so nothing more to give it until one lands. SHAPE and KIT_IMAGERY are now plain Records over every kit id and the two fallbacks are gone. Probe: node scripts/probes/create-space-kit-previews.mjs.",
"verify": {
"kind": "cmd",
"cmd": "node scripts/probes/create-space-kit-previews.mjs"
},
"closed": "2026-11-03"
}
9 changes: 9 additions & 0 deletions lib/create-space/preview-site.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ describe('preview site', () => {
expect(kitImagery('temple-kit').hero).toMatch(/^\/sites\/temple-kit\/photos\//)
})

it('Free Human wears its own library; Services wears New World\'s until it has one (LIVE-2102)', () => {
const fh = kitImagery('free-human')
expect(fh.hero).toBe('/sites/free-human/photos/heritage-a-virgin.webp')
expect(fh.portrait).toMatch(/^\/sites\/free-human\/photos\//)
for (const src of [...fh.strip, ...fh.covers, ...fh.portraits]) expect(src).toMatch(/^\/sites\/free-human\/photos\//)
expect(fh).not.toEqual(kitImagery('frequency'))
expect(kitImagery('services').hero).toMatch(/^\/sites\/new-world\/photos\//)
})

it('reads as a sample Space when nothing is filled in', () => {
const s = deriveSite(base)
expect(s.name).toBe('Your Space')
Expand Down
19 changes: 14 additions & 5 deletions lib/create-space/preview-site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,17 @@ const NETWORK_PHOTO = (n: string) => `/sites/network-kit/photos/photo-${n}.jpg`
const TEMPLE_KIT_PHOTO = (n: string) => `/sites/temple-kit/photos/photo-${n}.jpg`
const NOCTURNE_PHOTO = (name: string) => `/sites/midnight/photos/${name}.jpg`
const NEW_WORLD_PHOTO = (name: string) => `/sites/new-world/photos/${name}.webp`
const FREE_HUMAN_PHOTO = (name: string) => `/sites/free-human/photos/${name}.webp`

const FREQUENCY_IMAGERY: KitImagery = {
hero: FREQUENCY_PHOTO(2), heroPos: 'center', portrait: FREQUENCY_PHOTO(1), portraitPos: 'center',
strip: [FREQUENCY_PHOTO(1), FREQUENCY_PHOTO(3), FREQUENCY_PHOTO(2)], nav1: 'Offerings', cta2: 'Learn more',
covers: [FREQUENCY_PHOTO(2), FREQUENCY_PHOTO(1), FREQUENCY_PHOTO(3)], portraits: [FREQUENCY_PHOTO(1)],
}

/** Each kit's sample pictures. Designer wears its own library; every other kit wears the gathering photography
* until it has a library of its own. A Space's uploads replace these in the setup walk. */
const KIT_IMAGERY: Partial<Record<MegaThemeId, KitImagery>> & Record<Exclude<MegaThemeId, 'free-human'>, KitImagery> = {
/** Each kit's sample pictures. A kit with a library of its own wears it; the rest wear the gathering photography
* until theirs arrives. A Space's uploads replace these in the setup walk. */
const KIT_IMAGERY: Record<MegaThemeId, KitImagery> = {
// New World's own gathering library (lib/sites/new-world-photos.json, LIVE-2060): the beach is its hero.
'new-world': {
hero: NEW_WORLD_PHOTO('beach-joy'), heroPos: 'center 40%', portrait: NEW_WORLD_PHOTO('group-hug'), portraitPos: 'center 30%',
Expand All @@ -87,6 +88,14 @@ const KIT_IMAGERY: Partial<Record<MegaThemeId, KitImagery>> & Record<Exclude<Meg
covers: [NEW_WORLD_PHOTO('backyard-class'), NEW_WORLD_PHOTO('beach-gong'), NEW_WORLD_PHOTO('cafe-table'), NEW_WORLD_PHOTO('workshop-table')],
portraits: [NEW_WORLD_PHOTO('workshop-table'), NEW_WORLD_PHOTO('cafe-table')],
},
// Free Human's own library (lib/sites/free-human-home.ts wears the same files): the walking virgin is its hero, the
// founder its portrait.
'free-human': {
hero: FREE_HUMAN_PHOTO('heritage-a-virgin'), heroPos: 'center 20%', portrait: FREE_HUMAN_PHOTO('founder-audrey'), portraitPos: 'center 30%',
strip: [FREE_HUMAN_PHOTO('place-daisies'), FREE_HUMAN_PHOTO('light-sun'), FREE_HUMAN_PHOTO('light-floating-candles')], nav1: 'Program', cta2: 'See the program',
covers: [FREE_HUMAN_PHOTO('heritage-a-virgin'), FREE_HUMAN_PHOTO('place-daisies'), FREE_HUMAN_PHOTO('light-sun'), FREE_HUMAN_PHOTO('light-floating-candles'), FREE_HUMAN_PHOTO('light-lanterns')],
portraits: [FREE_HUMAN_PHOTO('founder-audrey'), FREE_HUMAN_PHOTO('light-honey-dipper')],
},
designer: {
hero: DESIGNER_PHOTO('dz-designer-kitchen-hero'), heroPos: '38% center',
portrait: DESIGNER_PHOTO('dz-portrait-01'), portraitPos: '48% 30%',
Expand Down Expand Up @@ -124,8 +133,8 @@ const KIT_IMAGERY: Partial<Record<MegaThemeId, KitImagery>> & Record<Exclude<Meg
frequency: FREQUENCY_IMAGERY,
}

/** Free Human is not in the builder's picker (lib/spaces/create-wizard.ts); until it is, it wears the gathering photography. */
export const kitImagery = (kit: MegaThemeId): KitImagery => KIT_IMAGERY[kit] ?? FREQUENCY_IMAGERY
/** Every kit's pictures: a kit with its own library never borrows another's. */
export const kitImagery = (kit: MegaThemeId): KitImagery => KIT_IMAGERY[kit]
/** Every kit's accents are its own: no kit falls back to another kit's family (Daniel, 2026-11-03). */
export const kitAccents = (kit: MegaThemeId): readonly PreviewAccent[] => KIT_ACCENTS[kit]

Expand Down
13 changes: 13 additions & 0 deletions lib/theme/preview-tokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ describe('preview tokens', () => {
expect(PREVIEW_KIT_ORDER[0]).toBe(SITE_THEMES.some((t) => t.id === 'new-world') ? 'new-world' : 'designer')
})

it('Free Human and Services wear shapes of their own, never Temple\'s (LIVE-2102)', () => {
const fh = previewTokens('free-human')
expect(fh['--pv-font-display']).toMatch(/^'Cormorant Garamond'/)
expect(fh['--pv-radius-card']).toBe('40px')
expect(fh['--pv-radius-photo']).toBe('999px 999px 120px 120px')
expect(fh['--pv-em-ink']).toBe('#B3361F')
expect(fh['--pv-font-display']).not.toBe(previewTokens('temple')['--pv-font-display'])
const sv = previewTokens('services')
expect(sv['--pv-font-display']).toMatch(/^'Bricolage Grotesque'/)
expect(sv['--pv-font-em']).toMatch(/^'Lora'/)
expect(sv['--pv-radius-card']).toBe('20px')
})

it('Designer reads its own palette, with a charcoal primary', () => {
const t = previewTokens('designer')
expect(t['--pv-bg']).toBe('#F7F3EC')
Expand Down
17 changes: 13 additions & 4 deletions lib/theme/preview-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ type PreviewTokens = Record<PreviewTokenKey, string>
* lib/theme/site-style.ts, the one hidden flag) is left out here too, so New World and Services reach the builder the
* moment that flag lets them go and never before. */
const PREVIEW_KIT_PREFERENCE: readonly MegaThemeId[] = [
'new-world', 'services', 'designer', 'midnight', 'network-kit', 'temple-kit', 'bold', 'wildheart', 'frequency',
'new-world', 'services', 'free-human', 'designer', 'midnight', 'network-kit', 'temple-kit', 'bold', 'wildheart', 'frequency',
]
export const PREVIEW_KIT_ORDER: readonly MegaThemeId[] = PREVIEW_KIT_PREFERENCE.filter((id) => SITE_THEMES.some((t) => t.id === id))

/** The faces the seven kits wear in the preview. The page asks Google Fonts for them only when it renders (the CSP
* allows it), the way the Space page does for a themed Space (lib/theme/site-faces.ts), so they register on no
* other route. Unbounded, Hanken Grotesk and Instrument Serif are the site's own. */
export const PREVIEW_FONT_SHEET =
'https://fonts.googleapis.com/css2?family=Anton&family=Bodoni+Moda:wght@400;600&family=Bricolage+Grotesque:opsz,wght@12..96,700&family=Cormorant:ital,wght@0,400;1,400&family=Cormorant+Garamond:ital,wght@0,600;1,600&family=Geist:wght@300..600&family=Instrument+Serif:ital@1&family=Jost:wght@300..500&family=Lora:ital@1&family=Marcellus&family=Outfit:wght@300..500&display=swap'
'https://fonts.googleapis.com/css2?family=Anton&family=Bodoni+Moda:wght@400;600&family=Bricolage+Grotesque:opsz,wght@12..96,700&family=Cormorant:ital,wght@0,400;1,400&family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400;1,600&family=Geist:wght@300..600&family=Instrument+Serif:ital@1&family=Jost:wght@300..500&family=Lora:ital@1&family=Marcellus&family=Outfit:wght@300..500&display=swap'

interface Shape {
display: string; body: string; weight: string; textCase: string; track: string; label: string
Expand All @@ -56,7 +56,7 @@ interface Shape {

const hairline = (line: string) => `inset 0 0 0 1px ${line}`

const SHAPE: Partial<Record<MegaThemeId, Shape>> & Record<Exclude<MegaThemeId, 'free-human'>, Shape> = {
const SHAPE: Record<MegaThemeId, Shape> = {
// New World (lib/theme/new-world.ts): Bricolage display and body, Lora italic for the one-word voice, the kit's 20px
// cards and pill buttons, on the Night register. Services (lib/theme/services.ts) shares the type system and the radii.
'new-world': {
Expand All @@ -71,6 +71,15 @@ const SHAPE: Partial<Record<MegaThemeId, Shape>> & Record<Exclude<MegaThemeId, '
btn: '999px', card: '20px', inner: '12px', photo: '20px', mat: '0', shadow: hairline, rule: '0', ring: '0',
emFont: "'Lora', Georgia, serif",
},
// Free Human (lib/theme/free-human.ts): Cormorant Garamond display with the clay on the emphasised word, Hanken Grotesk
// body, wide labels, the kit's 16px controls, 40px cards, the arch on its pictures and its depth-2 shadow.
'free-human': {
display: "'Cormorant Garamond', Georgia, serif", body: "'Hanken Grotesk', system-ui, sans-serif",
weight: '600', textCase: 'none', track: '-0.01em', label: '.2em',
btn: '16px', card: '40px', inner: '16px', photo: '999px 999px 120px 120px', mat: '0',
shadow: () => '0 2px 4px -2px rgba(90,30,12,.10),0 12px 24px -14px rgba(90,30,12,.22),0 34px 64px -34px rgba(120,50,20,.30)', rule: '0', ring: '0',
em: '#B3361F', // token-ok: Free Human clay 500 (--text-lyric), lib/theme/free-human.ts
},
designer: {
display: "'Cormorant', 'Cormorant Garamond', Georgia, serif", body: "'Jost', system-ui, sans-serif",
weight: '400', textCase: 'none', track: '-0.01em', label: '.18em',
Expand Down Expand Up @@ -146,7 +155,7 @@ function surfaceOf(id: MegaThemeId): ThemeSurface {
/** The --pv-* tokens of one theme, generated from its own palette and the SHAPE table. */
export function previewTokens(id: MegaThemeId): PreviewTokens {
const s = surfaceOf(id)
const p = SHAPE[id] ?? SHAPE.temple
const p = SHAPE[id]
return {
'--pv-bg': s.canvas,
'--pv-ink': s.text,
Expand Down
31 changes: 31 additions & 0 deletions scripts/probes/create-space-kit-previews.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// LIVE-2102: the Free Human and Services builder previews wear their own shape, surface and pictures; SHAPE and
// KIT_IMAGERY are total over every kit id, so no kit falls back to Temple's shape or the gathering photos. Reads the
// files under node, never a test runner (LIVE-034). Exit 1 on the first miss.
import { readFileSync, existsSync } from 'node:fs'

const bad = []
const read = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : '')

const tokens = read('lib/theme/preview-tokens.ts')
if (!/const SHAPE: Record<MegaThemeId, Shape> = \{/.test(tokens)) bad.push('SHAPE is not total over every kit id')
if (/SHAPE\[id\] \?\? SHAPE\.temple/.test(tokens)) bad.push('previewTokens still falls back to Temple\'s shape')
if (!/'free-human': \{\s*display: "'Cormorant Garamond'/.test(tokens)) bad.push('the Free Human preview has no shape of its own (Cormorant Garamond display)')
if (!/services: \{\s*display: "'Bricolage Grotesque'/.test(tokens)) bad.push('the Services preview has no shape of its own (Bricolage display)')
if (!/Cormorant\+Garamond:ital,wght@0,400;0,600;1,400;1,600/.test(tokens)) bad.push('the preview font sheet does not load Cormorant Garamond at 400 for Free Human\'s body copy')
if (!/'new-world', 'services', 'free-human',/.test(tokens)) bad.push('free-human is not in the preview kit order, so it cannot reach the builder when it is un-hidden')

const site = read('lib/create-space/preview-site.ts')
if (!/const KIT_IMAGERY: Record<MegaThemeId, KitImagery> = \{/.test(site)) bad.push('KIT_IMAGERY is not total over every kit id')
if (/KIT_IMAGERY\[kit\] \?\? FREQUENCY_IMAGERY/.test(site)) bad.push('kitImagery still falls back to the gathering photos')
if (!/'free-human': \{\s*hero: FREE_HUMAN_PHOTO\('heritage-a-virgin'\)/.test(site)) bad.push('the Free Human preview does not wear the kit\'s own photo library')
if (!/services: \{\s*hero: NEW_WORLD_PHOTO/.test(site)) bad.push('the Services preview has no pictures of its own')

for (const n of ['heritage-a-virgin', 'founder-audrey', 'place-daisies', 'light-sun', 'light-floating-candles', 'light-lanterns', 'light-honey-dipper']) {
if (!existsSync(`public/sites/free-human/photos/${n}.webp`)) bad.push(`Free Human photo ${n} is missing`)
}

if (bad.length) {
for (const b of bad) console.error(`create-space-kit-previews: ${b}`)
process.exit(1)
}
console.log('create-space-kit-previews: Free Human and Services builder previews wear their own shape and pictures')
Loading