@olonjs/cli 3.0.118 → 3.0.120

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1644,7 +1644,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
1644
1644
  "@tiptap/extension-link": "^2.11.5",
1645
1645
  "@tiptap/react": "^2.11.5",
1646
1646
  "@tiptap/starter-kit": "^2.11.5",
1647
- "@olonjs/core": "^1.0.107",
1647
+ "@olonjs/core": "^1.0.109",
1648
1648
  "class-variance-authority": "^0.7.1",
1649
1649
  "clsx": "^2.1.1",
1650
1650
  "lucide-react": "^0.474.0",
@@ -1970,6 +1970,7 @@ const {
1970
1970
  buildPageManifest,
1971
1971
  buildPageManifestHref,
1972
1972
  buildSiteManifest,
1973
+ buildLlmsTxt,
1973
1974
  } = await import(contractsUrl);
1974
1975
 
1975
1976
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -1978,7 +1979,7 @@ const pagesDir = path.resolve(root, 'src/data/pages');
1978
1979
  const publicDir = path.resolve(root, 'public');
1979
1980
  const distDir = path.resolve(root, 'dist');
1980
1981
 
1981
- async function writeJsonTargets(relativePath, value) {
1982
+ async function writeTargets(relativePath, content) {
1982
1983
  const targets = [
1983
1984
  path.resolve(publicDir, relativePath),
1984
1985
  path.resolve(distDir, relativePath),
@@ -1986,10 +1987,14 @@ async function writeJsonTargets(relativePath, value) {
1986
1987
 
1987
1988
  for (const targetPath of targets) {
1988
1989
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
1989
- await fs.writeFile(targetPath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
1990
+ await fs.writeFile(targetPath, content, 'utf-8');
1990
1991
  }
1991
1992
  }
1992
1993
 
1994
+ async function writeJsonTargets(relativePath, value) {
1995
+ await writeTargets(relativePath, `${JSON.stringify(value, null, 2)}\n`);
1996
+ }
1997
+
1993
1998
  function escapeHtmlAttribute(value) {
1994
1999
  return String(value).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
1995
2000
  }
@@ -2108,6 +2113,13 @@ const mcpManifest = buildSiteManifest({
2108
2113
  });
2109
2114
  await writeJsonTargets('mcp-manifest.json', mcpManifest);
2110
2115
 
2116
+ const llmsTxtContent = buildLlmsTxt({
2117
+ pages: webMcpBuildState.pages,
2118
+ schemas: webMcpBuildState.schemas,
2119
+ siteConfig: webMcpBuildState.siteConfig,
2120
+ });
2121
+ await writeTargets('llms.txt', `${llmsTxtContent}\n`);
2122
+
2111
2123
  for (const { slug, out, depth } of targets) {
2112
2124
  console.log(`\n[bake] Rendering /${slug === 'home' ? '' : slug}...`);
2113
2125
 
@@ -7192,13 +7204,13 @@ export function OlonArchitectureView({ data }: Props) {
7192
7204
  </p>
7193
7205
  )}
7194
7206
 
7195
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden"
7196
- data-jp-array="protocols">
7207
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden">
7197
7208
  {data.protocols.map((p) => (
7198
7209
  <div key={p.id}
7199
7210
  className="bg-[var(--local-card)] p-7 flex flex-col gap-3 border-r border-b border-[var(--local-border)] hover:bg-[var(--elevated)] transition-colors"
7200
- data-jp-item-id={p.id}>
7201
- <div>{ICONS[p.icon]}</div>
7211
+ data-jp-item-id={p.id}
7212
+ data-jp-item-field="protocols">
7213
+ <div data-jp-field="icon">{ICONS[p.icon]}</div>
7202
7214
  <p className="text-[11px] font-semibold tracking-[0.1em] uppercase text-[var(--local-p400)] font-mono"
7203
7215
  data-jp-field="version">{p.acronym} · {p.version}</p>
7204
7216
  <p className="font-bold text-foreground text-base" data-jp-field="name">{p.name}</p>
@@ -7402,12 +7414,12 @@ export function OlonGetStartedView({ data }: Props) {
7402
7414
  <p className="text-base text-[var(--local-muted)] leading-relaxed max-w-2xl mb-12"
7403
7415
  data-jp-field="body">{data.body}</p>
7404
7416
 
7405
- <div className="grid grid-cols-1 md:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden"
7406
- data-jp-array="cards">
7417
+ <div className="grid grid-cols-1 md:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden">
7407
7418
  {data.cards.map((card) => (
7408
7419
  <div key={card.id}
7409
7420
  className="bg-[var(--local-card)] p-8 flex flex-col gap-4 border-r last:border-r-0 border-[var(--local-border)] hover:bg-[var(--elevated)] transition-colors"
7410
- data-jp-item-id={card.id}>
7421
+ data-jp-item-id={card.id}
7422
+ data-jp-item-field="cards">
7411
7423
  <span className={`text-[11px] font-bold tracking-[0.08em] uppercase px-2.5 py-0.5 rounded-full w-fit ${BADGE_STYLES[card.badgeStyle]}`}
7412
7424
  data-jp-field="badge">
7413
7425
  {card.badge}
@@ -7422,7 +7434,7 @@ export function OlonGetStartedView({ data }: Props) {
7422
7434
  </code>
7423
7435
  )}
7424
7436
  {card.deployHref && card.deployLabel && (
7425
- <Button asChild variant="outline" size="sm" className="w-fit">
7437
+ <Button asChild variant="outline" size="sm" className="w-fit" data-jp-field="deployLabel">
7426
7438
  <a href={card.deployHref} target="_blank" rel="noopener noreferrer">
7427
7439
  {card.deployLabel}
7428
7440
  </a>
@@ -7616,7 +7628,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/components/olon-hero/View.tsx"
7616
7628
  import type { OlonHeroData } from './types';
7617
7629
  import { Button } from '@/components/ui/button';
7618
7630
  import { Github, Terminal } from 'lucide-react';
7619
-
7631
+ import { resolveAssetUrl, useConfig } from '@olonjs/core';
7620
7632
 
7621
7633
  interface Props {
7622
7634
  data: OlonHeroData;
@@ -7624,7 +7636,14 @@ interface Props {
7624
7636
 
7625
7637
  const heroPlugImage = '/assets/images/plug-graded-square.jpg';
7626
7638
 
7639
+ function hasRenderableCta(cta: { label?: string; href?: string } | undefined): cta is { label: string; href: string } {
7640
+ return Boolean(cta?.label?.trim() && cta?.href?.trim());
7641
+ }
7642
+
7627
7643
  export function OlonHeroView({ data }: Props) {
7644
+ const { tenantId = 'default' } = useConfig();
7645
+ const imageUrl = data.image?.url ? resolveAssetUrl(data.image.url, tenantId) : heroPlugImage;
7646
+
7628
7647
  return (
7629
7648
  <section
7630
7649
  style={{
@@ -7685,40 +7704,53 @@ export function OlonHeroView({ data }: Props) {
7685
7704
  </p>
7686
7705
 
7687
7706
  <div className="flex flex-wrap gap-3 items-center">
7688
- <Button asChild size="lg" className="font-semibold">
7689
- <a href={data.cta.primary.href}>
7690
- {data.cta.primary.label}
7691
- </a>
7692
- </Button>
7693
- <Button asChild variant="outline" size="lg" className="font-semibold gap-2">
7694
- <a href={data.cta.secondary.href}>
7695
- <Github className="w-4 h-4" />
7696
- {data.cta.secondary.label}
7697
- </a>
7698
- </Button>
7699
- <a
7700
- href={data.cta.ghost.href}
7701
- className="text-sm text-[var(--local-muted)] hover:text-[var(--local-fg)] transition-colors flex items-center gap-1.5"
7702
- >
7703
- {data.cta.ghost.label}
7704
- <Terminal className="w-4 h-4" />
7705
- </a>
7707
+ {hasRenderableCta(data.primaryCta) && (
7708
+ <div data-jp-field="primaryCta">
7709
+ <Button asChild size="lg" className="font-semibold">
7710
+ <a href={data.primaryCta.href}>
7711
+ {data.primaryCta.label} →
7712
+ </a>
7713
+ </Button>
7714
+ </div>
7715
+ )}
7716
+ {hasRenderableCta(data.secondaryCta) && (
7717
+ <div data-jp-field="secondaryCta">
7718
+ <Button asChild variant="outline" size="lg" className="font-semibold gap-2">
7719
+ <a href={data.secondaryCta.href}>
7720
+ <Github className="w-4 h-4" />
7721
+ {data.secondaryCta.label}
7722
+ </a>
7723
+ </Button>
7724
+ </div>
7725
+ )}
7726
+ {hasRenderableCta(data.ghostCta) && (
7727
+ <div data-jp-field="ghostCta">
7728
+ <a
7729
+ href={data.ghostCta.href}
7730
+ className="text-sm text-[var(--local-muted)] hover:text-[var(--local-fg)] transition-colors flex items-center gap-1.5"
7731
+ >
7732
+ {data.ghostCta.label}
7733
+ <Terminal className="w-4 h-4" />
7734
+ </a>
7735
+ </div>
7736
+ )}
7706
7737
  </div>
7707
7738
  </div>
7708
7739
 
7709
7740
  {/* Right: branded product photo */}
7710
- <div className="hidden md:flex items-center justify-center">
7711
- <div className="relative w-full max-w-lg">
7712
- <div className="absolute inset-[-8%] bg-[radial-gradient(circle_at_50%_50%,rgba(52,109,255,0.22),rgba(12,17,22,0)_70%)] blur-2xl" />
7713
- <div className="relative aspect-[1/1.03] overflow-hidden rounded-none border border-white/14 bg-[#0d1219] shadow-[0_22px_56px_rgba(4,8,20,0.42)]">
7741
+ <div className="hidden md:flex items-center justify-center pointer-events-none">
7742
+ <div className="relative w-full max-w-lg pointer-events-auto">
7743
+ <div className="absolute inset-[-8%] bg-[radial-gradient(circle_at_50%_50%,rgba(52,109,255,0.22),rgba(12,17,22,0)_70%)] blur-2xl pointer-events-none" />
7744
+ <div className="relative aspect-[1/1.03] overflow-hidden rounded-none border border-white/14 bg-[#0d1219] shadow-[0_22px_56px_rgba(4,8,20,0.42)] pointer-events-auto">
7714
7745
  <img
7715
- src={heroPlugImage}
7716
- alt="Olon interface port engraved into a dark stone surface"
7746
+ src={imageUrl}
7747
+ alt={data.image?.alt ?? "Olon interface port engraved into a dark stone surface"}
7717
7748
  className="absolute inset-0 h-full w-full object-cover"
7718
7749
  style={{ objectPosition: '50% 50%' }}
7750
+ data-jp-field="image"
7719
7751
  />
7720
- <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(7,11,21,0.02)_0%,rgba(7,11,21,0.14)_24%,rgba(7,11,21,0.44)_100%)]" />
7721
- <div className="absolute inset-0 bg-[radial-gradient(circle_at_62%_44%,rgba(122,163,255,0.18),rgba(29,78,216,0.08)_24%,rgba(12,17,22,0)_54%)] mix-blend-screen" />
7752
+ <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(7,11,21,0.02)_0%,rgba(7,11,21,0.14)_24%,rgba(7,11,21,0.44)_100%)] pointer-events-none" />
7753
+ <div className="absolute inset-0 bg-[radial-gradient(circle_at_62%_44%,rgba(122,163,255,0.18),rgba(29,78,216,0.08)_24%,rgba(12,17,22,0)_54%)] mix-blend-screen pointer-events-none" />
7722
7754
  </div>
7723
7755
  </div>
7724
7756
  </div>
@@ -7727,117 +7759,6 @@ export function OlonHeroView({ data }: Props) {
7727
7759
  );
7728
7760
  }
7729
7761
 
7730
- END_OF_FILE_CONTENT
7731
- echo "Creating src/components/olon-hero/View_.tsx..."
7732
- cat << 'END_OF_FILE_CONTENT' > "src/components/olon-hero/View_.tsx"
7733
- import type { OlonHeroData } from './types';
7734
- import { Button } from '@/components/ui/button';
7735
-
7736
- interface Props {
7737
- data: OlonHeroData;
7738
- }
7739
-
7740
- export function OlonHeroView({ data }: Props) {
7741
- return (
7742
- <section
7743
- style={{
7744
- '--local-bg': 'var(--background)',
7745
- '--local-fg': 'var(--foreground)',
7746
- '--local-muted': 'var(--muted-foreground)',
7747
- '--local-primary': 'var(--primary)',
7748
- '--local-p300': 'var(--primary-light)',
7749
- } as React.CSSProperties}
7750
- className="min-h-screen bg-[var(--local-bg)] text-[var(--local-fg)] pt-36 pb-24"
7751
- data-jp-section-id={data.id}
7752
- data-jp-section-type="olon-hero"
7753
- >
7754
- <div className="max-w-6xl mx-auto px-8 grid grid-cols-1 md:grid-cols-2 gap-16 items-center">
7755
- {/* Left: copy */}
7756
- <div className="flex flex-col gap-6">
7757
- <p className="text-xs font-semibold tracking-[0.12em] uppercase text-[var(--local-muted)]"
7758
- data-jp-field="eyebrow">
7759
- {data.eyebrow}
7760
- </p>
7761
-
7762
- <div>
7763
- <h1 className="text-5xl md:text-6xl font-bold tracking-[-0.03em] leading-[1.05] text-foreground"
7764
- data-jp-field="headline">
7765
- {data.headline}
7766
- </h1>
7767
- <p className="text-4xl md:text-5xl font-semibold tracking-[-0.03em] leading-[1.1] text-[var(--local-p300)] italic mt-1"
7768
- data-jp-field="subline">
7769
- {data.subline}
7770
- </p>
7771
- </div>
7772
-
7773
- <p className="text-base text-[var(--local-muted)] leading-relaxed max-w-lg"
7774
- data-jp-field="body">
7775
- {data.body}
7776
- </p>
7777
-
7778
- <div className="flex flex-wrap gap-3 items-center">
7779
- <Button asChild size="lg" className="font-semibold">
7780
- <a href={data.cta.primary.href}>{data.cta.primary.label} →</a>
7781
- </Button>
7782
- <Button asChild variant="outline" size="lg" className="font-semibold">
7783
- <a href={data.cta.secondary.href}>{data.cta.secondary.label}</a>
7784
- </Button>
7785
- <a href={data.cta.ghost.href}
7786
- className="text-sm text-[var(--local-muted)] hover:text-[var(--local-fg)] transition-colors flex items-center gap-1.5">
7787
- {data.cta.ghost.label}
7788
- </a>
7789
- </div>
7790
- </div>
7791
-
7792
- {/* Right: SVG illustration */}
7793
- <div className="hidden md:flex items-center justify-center">
7794
- <svg viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full max-w-md">
7795
- <defs>
7796
- <linearGradient id="hero-main" x1="0" y1="0" x2="1" y2="1">
7797
- <stop offset="0%" stopColor="#84ABFF"/>
7798
- <stop offset="60%" stopColor="#1763FF"/>
7799
- <stop offset="100%" stopColor="#0F52E0"/>
7800
- </linearGradient>
7801
- <linearGradient id="hero-accent" x1="0" y1="0" x2="0" y2="1">
7802
- <stop offset="0%" stopColor="#EEF3FF"/>
7803
- <stop offset="100%" stopColor="#84ABFF"/>
7804
- </linearGradient>
7805
- <linearGradient id="hero-glow" x1="0" y1="0" x2="0" y2="1">
7806
- <stop offset="0%" stopColor="#1763FF" stopOpacity="0.3"/>
7807
- <stop offset="100%" stopColor="#1763FF" stopOpacity="0"/>
7808
- </linearGradient>
7809
- <filter id="glow"><feGaussianBlur stdDeviation="8" result="blur"/><feComposite in="SourceGraphic" in2="blur" operator="over"/></filter>
7810
- </defs>
7811
- <circle cx="200" cy="200" r="160" fill="url(#hero-glow)" opacity="0.4"/>
7812
- <rect x="90" y="90" width="220" height="220" rx="28" fill="none" stroke="url(#hero-main)" strokeWidth="14"/>
7813
- <line x1="16" y1="148" x2="90" y2="148" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7814
- <line x1="16" y1="200" x2="90" y2="200" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7815
- <line x1="16" y1="252" x2="90" y2="252" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7816
- <line x1="310" y1="148" x2="384" y2="148" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7817
- <line x1="310" y1="200" x2="384" y2="200" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7818
- <line x1="310" y1="252" x2="384" y2="252" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7819
- <line x1="148" y1="16" x2="148" y2="90" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7820
- <line x1="200" y1="16" x2="200" y2="90" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7821
- <line x1="252" y1="16" x2="252" y2="90" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7822
- <line x1="148" y1="310" x2="148" y2="384" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7823
- <line x1="200" y1="310" x2="200" y2="384" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7824
- <line x1="252" y1="310" x2="252" y2="384" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7825
- <circle cx="148" cy="148" r="13" fill="url(#hero-main)"/>
7826
- <circle cx="252" cy="148" r="13" fill="url(#hero-main)"/>
7827
- <circle cx="148" cy="252" r="13" fill="url(#hero-main)"/>
7828
- <circle cx="252" cy="252" r="13" fill="url(#hero-main)"/>
7829
- <line x1="148" y1="148" x2="200" y2="200" stroke="#84ABFF" strokeWidth="2.5" opacity="0.35"/>
7830
- <line x1="252" y1="148" x2="200" y2="200" stroke="#84ABFF" strokeWidth="2.5" opacity="0.35"/>
7831
- <line x1="148" y1="252" x2="200" y2="200" stroke="#84ABFF" strokeWidth="2.5" opacity="0.35"/>
7832
- <line x1="252" y1="252" x2="200" y2="200" stroke="#84ABFF" strokeWidth="2.5" opacity="0.35"/>
7833
- <circle cx="200" cy="200" r="18" fill="url(#hero-accent)" filter="url(#glow)"/>
7834
- </svg>
7835
- </div>
7836
- </div>
7837
- </section>
7838
- );
7839
- }
7840
-
7841
7762
  END_OF_FILE_CONTENT
7842
7763
  echo "Creating src/components/olon-hero/index.ts..."
7843
7764
  cat << 'END_OF_FILE_CONTENT' > "src/components/olon-hero/index.ts"
@@ -7849,17 +7770,19 @@ END_OF_FILE_CONTENT
7849
7770
  echo "Creating src/components/olon-hero/schema.ts..."
7850
7771
  cat << 'END_OF_FILE_CONTENT' > "src/components/olon-hero/schema.ts"
7851
7772
  import { z } from 'zod';
7852
- import { BaseSectionData } from '@/lib/base-schemas';
7773
+ import { BaseSectionData, CtaSchema, ImageSelectionSchema } from '@/lib/base-schemas';
7853
7774
 
7854
7775
  export const OlonHeroSchema = BaseSectionData.extend({
7855
7776
  eyebrow: z.string().default('CONTRACT LAYER · V1.5 · OPEN CORE'),
7856
7777
  headline: z.string().default('Contract Layer'),
7857
7778
  subline: z.string().default('for the agentic web.'),
7858
7779
  body: z.string().default(''),
7859
- cta: z.object({
7860
- primary: z.object({ label: z.string(), href: z.string() }),
7861
- secondary: z.object({ label: z.string(), href: z.string() }),
7862
- ghost: z.object({ label: z.string(), href: z.string() }),
7780
+ primaryCta: CtaSchema.optional(),
7781
+ secondaryCta: CtaSchema.optional(),
7782
+ ghostCta: CtaSchema.optional(),
7783
+ image: ImageSelectionSchema.default({
7784
+ url: '/assets/images/plug-graded-square.jpg',
7785
+ alt: 'Olon interface port engraved into a dark stone surface'
7863
7786
  }),
7864
7787
  });
7865
7788
 
@@ -7944,13 +7867,13 @@ export function OlonWhyView({ data }: Props) {
7944
7867
  <p className="text-base text-[var(--local-muted)] leading-relaxed max-w-2xl mb-12"
7945
7868
  data-jp-field="body">{data.body}</p>
7946
7869
 
7947
- <div className="grid grid-cols-1 md:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden"
7948
- data-jp-array="pillars">
7870
+ <div className="grid grid-cols-1 md:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden">
7949
7871
  {data.pillars.map((pillar) => (
7950
7872
  <div key={pillar.id}
7951
7873
  className="bg-[var(--local-card)] p-8 flex flex-col gap-4 border-r last:border-r-0 border-[var(--local-border)]"
7952
- data-jp-item-id={pillar.id}>
7953
- <div>{ICONS[pillar.icon]}</div>
7874
+ data-jp-item-id={pillar.id}
7875
+ data-jp-item-field="pillars">
7876
+ <div data-jp-field="icon">{ICONS[pillar.icon]}</div>
7954
7877
  <div className="font-bold text-foreground" data-jp-field="title">{pillar.title}</div>
7955
7878
  <div className="text-sm text-[var(--local-muted)] leading-relaxed" data-jp-field="body">{pillar.body}</div>
7956
7879
  </div>
@@ -11586,13 +11509,12 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/docs.json"
11586
11509
  "id": "docs-main",
11587
11510
  "type": "tiptap",
11588
11511
  "data": {
11589
- "content": "# 📐 OlonJS Architecture Specifications v1.5\n\n**Status:** Mandatory Standard\\\n**Version:** 1.5.0 (Sovereign Core Edition — Architecture + Studio/ICE UX, Path-Deterministic Nested Editing, Deterministic Local Design Tokens, Three-Layer CSS Bridge Contract)\\\n**Target:** Senior Architects / AI Agents / Enterprise Governance\n\nThis tenant follows the current OlonJS source-of-truth model: the tenant app owns content, schemas, theme, and persistence wiring; `@olonjs/core` owns the Studio shell, routing, preview, and editing engine.\n\n---\n\n## Canonical Editorial Flows\n\nThe supported Studio flows are now:\n\n- `SSG` for static HTML and route output.\n- `Save to file` for local JSON persistence back into tenant source files.\n- `Hot Save` for cloud/editorial persistence when the tenant config provides it.\n- `Add Section` for deterministic section lifecycle management inside Studio.\n\nPrevious one-off bake and JSON export paths are no longer part of Studio.\n\n---\n\n## Persistence Model\n\n`@olonjs/core` no longer performs HTML bake or ZIP export. Studio now invokes tenant-provided persistence callbacks:\n\n- `saveToFile(state, slug)`\n- `hotSave(state, slug)`\n\nThis keeps persistence explicit, tenant-owned, and aligned with the current `JsonPagesConfig` contract.\n\n---\n\n## Tenant Source Of Truth\n\n`apps/tenant-alpha` is the DNA source of truth for this tenant. Generated CLI templates are downstream artifacts and should be regenerated from source apps instead of being edited manually.\n\nThe canonical content and design files remain:\n\n- `src/data/config/site.json`\n- `src/data/config/menu.json`\n- `src/data/config/theme.json`\n- `src/data/pages/<slug>.json`\n\n---\n\n## Reference Specs\n\nUse these monorepo sources for the full protocol and architecture details:\n\n- `specs/olonjsSpecs_V_1_5.md`\n- `apps/tenant-alpha/specs/olonjsSpecs_V.1.3.md`\n\nThese source specs are the maintained references for architecture, Studio behavior, and tenant compliance."
11512
+ "content": "# 📐 OlonJS Architecture Specifications v1.5\n\n**Status:** Mandatory Standard\\\n**Version:** 1.5.0 (Sovereign Core Edition — Architecture + Studio/ICE UX, Path-Deterministic Nested Editing, Deterministic Local Design Tokens, Three-Layer CSS Bridge Contract)\\\n**Target:** Senior Architects / AI Agents / Enterprise Governance\n\n**Scope v1.5:** This edition preserves the complete v1.3 architecture (MTRP, JSP, TBP, CIP, ECIP, JAP + Studio/ICE UX contract: IDAC, TOCC, BSDS, ASC, JEB + Tenant Type & Code-Generation Annex + strict path-based/nested-array behavior) as a **faithful superset**, and upgrades **Local Design Tokens** from a principle to a deterministic implementation contract.\\\n**Scope note (breaking):** In strict v1.3+ Studio semantics, the legacy flat protocol (`itemField` / `itemId`) is removed in favor of `itemPath` (root-to-leaf path segments).\\\n**Scope note (clarification):** In v1.5, `theme.json` is the tenant theme source of truth for themed tenants; runtime theme publication is mandatory for compliant themed tenants; section-local tokens (`--local-*`) are the required scoping layer for section-owned color and radius concerns.\n\n---\n\n## 1. 📐 Modular Type Registry Pattern (MTRP) v1.2\n\n**Objective:** Establish a strictly typed, open-ended protocol for extending content data structures where the **Core Engine** is the orchestrator and the **Tenant** is the provider.\n\n### 1.1 The Sovereign Dependency Inversion\n\nThe **Core** defines the empty `SectionDataRegistry`. The **Tenant** \"injects\" its specific definitions using **Module Augmentation**. This allows the Core to be distributed as a compiled NPM package while remaining aware of Tenant-specific types at compile-time.\n\n### 1.2 Technical Implementation (`@olonjs/core/kernel`)\n\n```typescript\nexport interface SectionDataRegistry {} // Augmented by Tenant\nexport interface SectionSettingsRegistry {} // Augmented by Tenant\n\nexport interface BaseSection<K extends keyof SectionDataRegistry> {\n id: string;\n type: K;\n data: SectionDataRegistry[K];\n settings?: K extends keyof SectionSettingsRegistry\n ? SectionSettingsRegistry[K]\n : BaseSectionSettings;\n}\n\nexport type Section = {\n [K in keyof SectionDataRegistry]: BaseSection<K>\n}[keyof SectionDataRegistry];\n```\n\n**SectionType:** Core exports (or Tenant infers) `SectionType` as `keyof SectionDataRegistry`. After Tenant module augmentation, this is the union of all section type keys (e.g. `'header' | 'footer' | 'hero' | ...`). The Tenant uses this type for the ComponentRegistry and SECTION_SCHEMAS keys.\n\n**Perché servono:** Il Core deve poter renderizzare section senza conoscere i tipi concreti a compile-time; il Tenant deve poter aggiungere nuovi tipi senza modificare il Core. I registry vuoti + module augmentation permettono di distribuire Core come pacchetto NPM e mantenere type-safety end-to-end (Section, registry, config). Senza MTRP, ogni nuovo tipo richiederebbe cambi nel Core o tipi deboli (`any`).\n\n---\n\n## 2. 📐 JsonPages Site Protocol (JSP) v1.8\n\n**Objective:** Define the deterministic file system and the **Sovereign Projection Engine** (CLI).\n\n### 2.1 The File System Ontology (The Silo Contract)\n\nEvery site must reside in an isolated directory. Global Governance is physically separated from Local Content.\n\n- `/config/site.json` — Global Identity & Reserved System Blocks (Header/Footer). See Appendix A for typed shape.\n- `/config/menu.json` — Navigation Tree (SSOT for System Header). See Appendix A.\n- `/config/theme.json` — Theme tokens for themed tenants. See Appendix A.\n- `/pages/[slug].json` — Local Body Content per page. See Appendix A (PageConfig).\n\n**Application path convention:** The runtime app typically imports these via an alias (e.g. `@/data/config/` and `@/data/pages/`). The physical silo may be `src/data/config/` and `src/data/pages/` so that `site.json`, `menu.json`, `theme.json` live under `src/data/config/`, and page JSONs under `src/data/pages/`. The CLI or projection script may use `/config/` and `/pages/` at repo root; the **contract** is that the app receives **siteConfig**, **menuConfig**, **themeConfig**, **pages**, and optional **refDocuments** as defined in JEB (§10) and Appendix A. In v1.5 runtime behavior, menu data may be resolved from the JSON graph via `$ref` (e.g. `menu.json#/main`, `../config/menu.json#/main`) and is not limited to a single `menuConfig.main` source.\n\n**Rule:** For a tenant that claims v1.5 design-token compliance, `theme.json` is not optional in practice. If a tenant omits a physical `theme.json`, it must still provide an equivalent `ThemeConfig` object before bootstrap; otherwise the tenant is outside full v1.5 theme compliance.\n\n### 2.2 Deterministic Projection (CLI Workflow)\n\nThe CLI (`@olonjs/cli`) creates new tenants by:\n\n1. **Infra Projection:** Generating `package.json`, `tsconfig.json`, and `vite.config.ts` (The Shell).\n2. **Source Projection:** Executing a deterministic script (`src_tenant_alpha.sh`) to reconstruct the `src` folder (The DNA).\n3. **Dependency Resolution:** Enforcing specific versions of React, Radix, and Tailwind v4.\n\n**Perché servono:** Una struttura file deterministica (config vs pages) separa governance globale (site, menu, theme) dal contenuto per pagina; il CLI può rigenerare tenant e tooling può trovare dati e schemi sempre negli stessi path. Senza JSP, ogni tenant sarebbe una struttura ad hoc e ingestione/export/Bake sarebbero fragili.\n\n---\n\n## 3. 🧱 Tenant Block Protocol (TBP) v1.0\n\n**Objective:** Standardize the \"Capsule\" structure for components to enable automated ingestion (Pull) by the SaaS.\n\n### 3.1 The Atomic Capsule Structure\n\nComponents are self-contained directories under `src/components/<sectionType>/`:\n\n- `View.tsx` — The pure React component (Dumb View). Props: see Appendix A (SectionComponentPropsMap).\n- `schema.ts` — Zod schema(s) for the **data** contract (and optionally **settings**). Exports at least one schema (e.g. `HeroSchema`) used as the **data** schema for that type. Must extend BaseSectionData (§8) for data; array items must extend BaseArrayItem (§8).\n- `types.ts` — TypeScript interfaces inferred from the schema (e.g. `HeroData`, `HeroSettings`). Export types with names `<SectionType>Data` and `<SectionType>Settings` (or equivalent) so the Tenant can aggregate them in a single types module.\n- `index.ts` — Public API: re-exports View, schema(s), and types.\n\n### 3.2 Reserved System Types\n\n- `type: 'header'` — Reserved for `site.json`. Receives `menu: MenuItem[]` in addition to `data` and `settings`. Menu is sourced from `menu.json` (see Appendix A). The Tenant **must** type `SectionComponentPropsMap['header']` as `{ data: HeaderData; settings?: HeaderSettings; menu: MenuItem[] }`.\n- `type: 'footer'` — Reserved for `site.json`. Props: `{ data: FooterData; settings?: FooterSettings }` only (no `menu`).\n- `type: 'sectionHeader'` — A standard local block. Must define its own `links` array in its local schema if used.\n\n**Perché servono:** La capsula (View + schema + types + index) è l’unità di estensione: il Core e il Form Factory possono scoprire tipi e contratti per tipo senza convenzioni ad hoc. Header/footer riservati evitano conflitti tra globale e locale. Senza TBP, aggregazione di SECTION_SCHEMAS e registry sarebbe incoerente e l’ingestion da SaaS non sarebbe automatizzabile.\n\n---\n\n## 4. 🧱 Component Implementation Protocol (CIP) v1.6\n\n**Objective:** Ensure system-wide stability and Admin UI integrity.\n\n1. **The \"Sovereign View\" Law:** Components receive `data` and `settings` (and `menu` for header only) and return JSX. They are metadata-blind (never import Zod schemas).\n2. **Z-Index Neutrality:** Components must not use `z-index > 1`. Layout delegation (sticky/fixed) is managed by the `SectionRenderer`.\n3. **Agnostic Asset Protocol:** Use `resolveAssetUrl(path, tenantId)` for all media. Resolved URLs are under `/assets/...` with no tenantId segment in the path (e.g. relative `img/hero.jpg` → `/assets/img/hero.jpg`).\n\n### 4.4 Local Design Tokens (v1.5)\n\n**Objective:** Standardize how a section consumes tenant theme values without leaking global styling assumptions into the section implementation.\n\n#### 4.4.1 The Required Four-Layer Chain\n\nFor any section that controls background, text color, border color, accent color, or radii, the following chain is normative:\n\n1. **Tenant theme source of truth** — Values are declared in `src/data/config/theme.json`.\n2. **Runtime theme publication** — The Core and/or tenant bootstrap **must** publish those values as CSS custom properties.\n3. **Section-local scope** — The View root **must** define `--local-*` variables mapped to the published theme variables for the concerns the section owns.\n4. **Rendered classes** — Section-owned color/radius utilities **must** consume `var(--local-*)`.\n\n**Rule:** A section may not skip layer 3 when it visually owns those concerns. Directly using global theme variables throughout the JSX is non-canonical for a fully themed section and must be treated as non-compliant unless the usage falls under an explicitly allowed exception.\n\n#### 4.4.2 Source Of Truth: `theme.json`\n\n`theme.json` is the tenant-level source of truth for theme values. Example:\n\n```json\n{\n \"name\": \"JsonPages Landing\",\n \"tokens\": {\n \"colors\": {\n \"primary\": \"#3b82f6\",\n \"secondary\": \"#22d3ee\",\n \"accent\": \"#60a5fa\",\n \"background\": \"#060d1b\",\n \"surface\": \"#0b1529\",\n \"surfaceAlt\": \"#101e38\",\n \"text\": \"#e2e8f0\",\n \"textMuted\": \"#94a3b8\",\n \"border\": \"#162a4d\"\n },\n \"typography\": {\n \"fontFamily\": {\n \"primary\": \"'Instrument Sans', system-ui, sans-serif\",\n \"mono\": \"'JetBrains Mono', monospace\",\n \"display\": \"'Bricolage Grotesque', system-ui, sans-serif\"\n }\n },\n \"borderRadius\": {\n \"sm\": \"0px\",\n \"md\": \"0px\",\n \"lg\": \"2px\"\n }\n }\n}\n```\n\n**Rule:** For a themed tenant, `theme.json` must contain the canonical semantic keys defined in Appendix A. Extra brand-specific keys are allowed only as extensions to those canonical groups, not as replacements for them.\n\n#### 4.4.3 Runtime Theme Publication\n\nThe tenant and/or Core **must** expose theme values as CSS variables before section rendering. The compliant bridge is a **three-layer chain** implemented in the tenant's `index.css`. Runtime publication is mandatory for themed tenants.\n\n##### Layer architecture\n\n```\ntheme.json → engine injection → :root bridge → @theme (Tailwind) → JSX classes\n```\n\n**Layer 0 — Engine injection (Core-provided)** `@olonjs/core` reads `theme.json` and injects all token values as flattened CSS custom properties before section rendering. The naming convention is:\n\nJSON path Injected CSS var `tokens.colors.{name}` `--theme-colors-{name}` `tokens.typography.fontFamily.{role}` `--theme-font-{role}` `tokens.typography.scale.{step}` `--theme-typography-scale-{step}` `tokens.typography.tracking.{name}` `--theme-typography-tracking-{name}` `tokens.typography.leading.{name}` `--theme-typography-leading-{name}` `tokens.typography.wordmark.*` `--theme-typography-wordmark-*` `tokens.borderRadius.{name}` `--theme-border-radius-{name}` `tokens.spacing.{name}` `--theme-spacing-{name}` `tokens.zIndex.{name}` `--theme-z-index-{name}` `tokens.modes.{mode}.colors.{name}` `--theme-modes-{mode}-colors-{name}`\n\nThe engine also publishes shorthand aliases for the most common radius and font tokens (e.g. `--theme-radius-sm`, `--theme-font-primary`). Tokens not covered by the shorthand aliases must be bridged in the tenant `:root`.\n\n**Layer 1 —** `:root` **semantic bridge (Tenant-provided,** `index.css`**)** The tenant maps engine-injected vars to its own semantic naming. **The naming in this layer is the tenant's sovereign choice** — it is not imposed by the Core. Any naming convention is valid as long as it is consistent throughout the tenant.\n\n```css\n:root {\n /* Backgrounds */\n --background: var(--theme-colors-background);\n --card: var(--theme-colors-card);\n --elevated: var(--theme-colors-elevated);\n --overlay: var(--theme-colors-overlay);\n --popover: var(--theme-colors-popover);\n --popover-foreground: var(--theme-colors-popover-foreground);\n\n /* Foregrounds */\n --foreground: var(--theme-colors-foreground);\n --card-foreground: var(--theme-colors-card-foreground);\n --muted-foreground: var(--theme-colors-muted-foreground);\n --placeholder: var(--theme-colors-placeholder);\n\n /* Brand ramp */\n --primary: var(--theme-colors-primary);\n --primary-foreground: var(--theme-colors-primary-foreground);\n --primary-light: var(--theme-colors-primary-light);\n --primary-dark: var(--theme-colors-primary-dark);\n /* ... full ramp --primary-50 through --primary-900 ... */\n\n /* Accent, secondary, muted, border, input, ring */\n --accent: var(--theme-colors-accent);\n --accent-foreground: var(--theme-colors-accent-foreground);\n --secondary: var(--theme-colors-secondary);\n --secondary-foreground: var(--theme-colors-secondary-foreground);\n --muted: var(--theme-colors-muted);\n --border: var(--theme-colors-border);\n --border-strong: var(--theme-colors-border-strong);\n --input: var(--theme-colors-input);\n --ring: var(--theme-colors-ring);\n\n /* Feedback */\n --destructive: var(--theme-colors-destructive);\n --destructive-foreground: var(--theme-colors-destructive-foreground);\n --success: var(--theme-colors-success);\n --success-foreground: var(--theme-colors-success-foreground);\n --warning: var(--theme-colors-warning);\n --warning-foreground: var(--theme-colors-warning-foreground);\n --info: var(--theme-colors-info);\n --info-foreground: var(--theme-colors-info-foreground);\n\n /* Typography scale, tracking, leading */\n --theme-text-xs: var(--theme-typography-scale-xs);\n --theme-text-sm: var(--theme-typography-scale-sm);\n /* ... full scale ... */\n --theme-tracking-tight: var(--theme-typography-tracking-tight);\n --theme-leading-normal: var(--theme-typography-leading-normal);\n /* ... */\n\n /* Spacing */\n --theme-container-max: var(--theme-spacing-container-max);\n --theme-section-y: var(--theme-spacing-section-y);\n --theme-header-h: var(--theme-spacing-header-h);\n --theme-sidebar-w: var(--theme-spacing-sidebar-w);\n\n /* Z-index */\n --z-base: var(--theme-z-index-base);\n --z-elevated: var(--theme-z-index-elevated);\n --z-dropdown: var(--theme-z-index-dropdown);\n --z-sticky: var(--theme-z-index-sticky);\n --z-overlay: var(--theme-z-index-overlay);\n --z-modal: var(--theme-z-index-modal);\n --z-toast: var(--theme-z-index-toast);\n}\n```\n\n**Layer 2 —** `@theme` **Tailwind v4 bridge (Tenant-provided,** `index.css`**)** Every semantic variable from Layer 1 is re-exposed under the Tailwind v4 `@theme` namespace so it becomes a utility class. Pattern: `--color-{slug}: var(--{slug})`.\n\n```css\n@theme {\n --color-background: var(--background);\n --color-card: var(--card);\n --color-foreground: var(--foreground);\n --color-primary: var(--primary);\n --color-accent: var(--accent);\n --color-border: var(--border);\n /* ... full token set ... */\n\n --font-primary: var(--theme-font-primary);\n --font-mono: var(--theme-font-mono);\n --font-display: var(--theme-font-display);\n\n --radius-sm: var(--theme-radius-sm);\n --radius-md: var(--theme-radius-md);\n --radius-lg: var(--theme-radius-lg);\n --radius-xl: var(--theme-radius-xl);\n --radius-full: var(--theme-radius-full);\n}\n```\n\nAfter this bridge, the full Tailwind utility vocabulary (`bg-primary`, `text-foreground`, `rounded-lg`, `font-display`, etc.) resolves to live theme values — with no hardcoded hex anywhere in the React layer.\n\n**Light mode / additional modes** are bridged by overriding the Layer 1 semantic vars under a `[data-theme=\"light\"]` selector (or equivalent), pointing to the engine-injected mode vars (`--theme-modes-light-colors-*`). The `@theme` layer requires no changes.\n\n**Rule:** A tenant `index.css` must implement all three layers. Skipping Layer 2 breaks Tailwind utility resolution. Skipping Layer 1 couples sections to engine-internal naming. Hardcoding values in either layer is non-compliant.\n\n#### 4.4.4 Section-Local Scope\n\nIf a section controls its own visual language, it **shall** establish a local token scope on the section root. Example:\n\n```tsx\n<section\n style={{\n '--local-bg': 'var(--background)',\n '--local-text': 'var(--foreground)',\n '--local-text-muted': 'var(--muted-foreground)',\n '--local-primary': 'var(--primary)',\n '--local-border': 'var(--border)',\n '--local-surface': 'var(--card)',\n '--local-radius-sm': 'var(--theme-radius-sm)',\n '--local-radius-md': 'var(--theme-radius-md)',\n '--local-radius-lg': 'var(--theme-radius-lg)',\n } as React.CSSProperties}\n>\n```\n\n**Rule:** `--local-*` values must map to published theme variables. They must **not** be defined as hardcoded brand values such as `#fff`, `#111827`, `12px`, or `Inter, sans-serif` if those values belong to the tenant theme layer.\n\n**Rule:** Local tokens are **mandatory** for section-owned color and radius concerns. They are **optional** for font-family concerns unless the section must remap or isolate font roles locally.\n\n#### 4.4.5 Canonical Typography Rule\n\nTypography follows a deterministic rule distinct from color/radius:\n\n1. **Canonical font publication** — Tenant/Core must publish semantic font variables such as `--theme-font-primary`, `--theme-font-mono`, and `--theme-font-display` when those roles exist in the theme.\n2. **Canonical font consumption** — Sections must consume typography through semantic tenant font utilities or variables backed by those published theme roles (for example `.font-display` backed by `--font-display`, itself backed by `--theme-font-display`).\n3. **Local font tokens** — `--local-font-*` is optional and should be used only when a section needs to remap a font role locally rather than simply consume the canonical tenant font role.\n\nExample of canonical global semantic bridge:\n\n```css\n:root {\n --font-primary: var(--theme-font-primary);\n --font-display: var(--theme-font-display);\n}\n\n.font-display {\n font-family: var(--font-display, var(--font-primary));\n}\n```\n\n**Rule:** A section is compliant if it consumes themed fonts through this published semantic chain. It is **not** required to define `--local-font-display` unless the section needs local remapping. This closes the ambiguity between global semantic typography utilities and local color/radius scoping.\n\n#### 4.4.6 View Consumption\n\nAll section-owned classes that affect color or radius must consume local variables. Font consumption must follow the typography rule above. Example:\n\n```tsx\n<section\n style={{\n '--local-bg': 'var(--background)',\n '--local-text': 'var(--foreground)',\n '--local-primary': 'var(--primary)',\n '--local-border': 'var(--border)',\n '--local-radius-md': 'var(--theme-radius-md)',\n '--local-radius-lg': 'var(--theme-radius-lg)',\n } as React.CSSProperties}\n className=\"bg-[var(--local-bg)]\"\n>\n <h1 className=\"font-display text-[var(--local-text)]\">Build Tenant DNA</h1>\n\n <a className=\"bg-[var(--local-primary)] rounded-[var(--local-radius-md)] text-white\">\n Read the Docs\n </a>\n\n <div className=\"border border-[var(--local-border)] rounded-[var(--local-radius-lg)]\">\n {/* illustration / mockup / card */}\n </div>\n</section>\n```\n\n#### 4.4.7 Compliance Rules\n\nA section is compliant when all of the following are true:\n\n1. `theme.json` is the source of truth for the theme values being used.\n2. Those values are published at runtime as CSS custom properties before the section renders.\n3. The section root defines a local token scope for the color/radius concerns it controls.\n4. Local color/radius tokens map to published theme variables rather than hardcoded literals.\n5. JSX classes use `var(--local-*)` for section-owned color/radius concerns.\n6. Fonts are consumed through the published semantic font chain, and only use local font tokens when local remapping is required.\n7. Hardcoded colors/radii are absent from the primary visual contract of the section.\n\n#### 4.4.8 Allowed Exceptions\n\nThe following are acceptable if documented and intentionally limited:\n\n- Tiny decorative one-off values that are not part of the tenant theme contract (for example an isolated translucent pixel-grid overlay).\n- Temporary compatibility shims during migration, provided the section still exposes a clear compliant path and the literal is not the primary themed value.\n- Semantic alias bridges in tenant CSS (for example `--font-display: var(--theme-font-display)`), as long as the source remains the theme layer.\n\n#### 4.4.9 Non-Compliant Patterns\n\nThe following are non-compliant:\n\n- `style={{ '--local-bg': '#060d1b' }}` when that background belongs to tenant theme.\n- Buttons using `rounded-[7px]`, `bg-blue-500`, `text-zinc-100`, or similar hardcoded utilities inside a section that claims to be theme-driven.\n- A section root that defines `--local-*`, but child elements still use raw `bg-*`, `text-*`, or `rounded-*` utilities for the same owned concerns.\n- Reading `theme.json` directly inside a View instead of consuming published runtime theme variables.\n- Treating brand-specific extension keys as a replacement for canonical semantic keys such as `primary`, `background`, `text`, `border`, or `fontFamily.primary`.\n\n#### 4.4.10 Practical Interpretation\n\n`--local-*` is not the source of truth. It is the **local scoping layer** between tenant theme and section implementation.\n\nCanonical chain:\n\n`theme.json` → published runtime theme vars → section `--local-*` → JSX classes\\`\n\nCanonical font chain:\n\n`theme.json` → published semantic font vars → tenant font utility/variable → section typography\\`\n\n### 4.5 Z-Index & Overlay Governance (v1.2)\n\nSection content root **must** stay at `z-index` **≤ 1** (prefer `z-0`) so the Sovereign Overlay can sit above with high z-index in Tenant CSS (§7). Header/footer may use a higher z-index (e.g. 50) only as a documented exception for global chrome.\n\n**Perché servono (CIP):** View “dumb” (solo data/settings) e senza import di Zod evita accoppiamento e permette al Form Factory di essere l’unica fonte di verità sugli schemi. Z-index basso evita che il contenuto copra l’overlay di selezione in Studio. Asset via `resolveAssetUrl`: i path relativi vengono risolti in `/assets/...` (senza segmento tenantId nel path). In v1.5 la catena `theme.json -> runtime vars -> --local-* -> JSX classes` rende i tenant temabili, riproducibili e compatibili con la Studio UX; senza questa separazione, stili “nudi” o valori hardcoded creano drift visivo, rompono il contratto del brand, e rendono ambiguo ciò che appartiene al tema contro ciò che appartiene alla section.\n\n---\n\n## 5. 🛠️ Editor Component Implementation Protocol (ECIP) v1.5\n\n**Objective:** Standardize the Polymorphic ICE engine.\n\n1. **Recursive Form Factory:** The Admin UI builds forms by traversing the Zod ontology.\n2. **UI Metadata:** Use `.describe('ui:[widget]')` in schemas to pass instructions to the Form Factory.\n3. **Deterministic IDs:** Every object in a `ZodArray` must extend `BaseArrayItem` (containing an `id`) to ensure React reconciliation stability during reordering.\n\n### 5.4 UI Metadata Vocabulary (v1.2)\n\nStandard keys for the Form Factory:\n\nKey Use case `ui:text` Single-line text input. `ui:textarea` Multi-line text. `ui:select` Enum / single choice. `ui:number` Numeric input. `ui:list` Array of items; list editor (add/remove/reorder). `ui:icon-picker` Icon selection.\n\nUnknown keys may be treated as `ui:text`. Array fields must use `BaseArrayItem` for items.\n\n### 5.5 Path-Only Nested Selection & Expansion (v1.3, breaking)\n\nIn strict v1.3 Studio/Inspector behavior, nested editing targets are represented by **path segments from root to leaf**.\n\n```typescript\nexport type SelectionPathSegment = { fieldKey: string; itemId?: string };\nexport type SelectionPath = SelectionPathSegment[];\n```\n\nRules:\n\n- Expansion and focus for nested arrays **must** be computed from `SelectionPath` (root → leaf), not from a single flat pair.\n- Matching by `fieldKey` alone is non-compliant for nested structures.\n- Legacy flat payload fields `itemField` and `itemId` are removed in strict v1.3 selection protocol.\n\n**Perché servono (ECIP):** Il Form Factory deve sapere quale widget usare (text, textarea, select, list, …) senza hardcodare per tipo; `.describe('ui:...')` è il contratto. BaseArrayItem con `id` su ogni item di array garantisce chiavi stabili in React e reorder/delete corretti nell’Inspector. In v1.3 la selezione/espansione path-only elimina ambiguità su array annidati: senza path completo root→leaf, la sidebar può aprire il ramo sbagliato o non aprire il target.\n\n---\n\n## 6. 🎯 ICE Data Attribute Contract (IDAC) v1.1\n\n**Objective:** Mandatory data attributes so the Stage (iframe) and Inspector can bind selection and field/item editing without coupling to Tenant DOM.\n\n### 6.1 Section-Level Markup (Core-Provided)\n\n**SectionRenderer** (Core) wraps each section root with:\n\n- `data-section-id` — Section instance ID (e.g. UUID). On the wrapper that contains content + overlay.\n- Sibling overlay element `data-jp-section-overlay` — Selection ring and type label. **Tenant does not add this;** Core injects it.\n\nTenant Views render the **content** root only (e.g. `<section>` or `<div>`), placed **inside** the Core wrapper.\n\n### 6.2 Field-Level Binding (Tenant-Provided)\n\nFor every **editable scalar field** the View **must** attach `data-jp-field=\"<fieldKey>\"` (key matches schema path: e.g. `title`, `description`, `sectionTitle`, `label`).\n\n### 6.3 Array-Item Binding (Tenant-Provided)\n\nFor every **editable array item** the View **must** attach:\n\n- `data-jp-item-id=\"<stableId>\"` — Prefer `item.id`; fallback e.g. `legacy-${index}` only outside strict mode.\n- `data-jp-item-field=\"<arrayKey>\"` — e.g. `cards`, `layers`, `products`, `paragraphs`.\n\n### 6.4 Compliance\n\n**Reserved types** (`header`, `footer`): ICE attributes optional unless Studio edits them. **All other section types** in the Stage and in `SECTION_SCHEMAS` **must** implement §6.2 and §6.3 for every editable field and array item.\n\n### 6.5 Strict Path Extraction for Nested Arrays (v1.3, breaking)\n\nFor nested array targets, the Core/Inspector contract is path-based:\n\n- The runtime selection target is expressed as `itemPath: SelectionPath` (root → leaf).\n- Flat identity (`itemField` + `itemId`) is not sufficient for nested structures and is removed in strict v1.3 payloads.\n- In strict mode, index-based identity fallback is non-compliant for editable object arrays.\n\n**Perché servono (IDAC):** Lo Stage è in un iframe e l’Inspector deve sapere **quale campo o item** corrisponde al click (o alla selezione) senza conoscere la struttura DOM del Tenant. `data-jp-field` associa un nodo DOM al path dello schema (es. `title`, `description`): così il Core può evidenziare la riga giusta nella sidebar, applicare opacità attivo/inattivo e aprire il form sul campo corretto. `data-jp-item-id` e `data-jp-item-field` fanno lo stesso per gli item di array (liste, reorder, delete). In v1.3, `itemPath` rende deterministico anche il caso nested (array dentro array), eliminando mismatch tra selezione canvas e ramo aperto in sidebar.\n\n---\n\n## 7. 🎨 Tenant Overlay CSS Contract (TOCC) v1.0\n\n**Objective:** The Stage iframe loads only Tenant HTML/CSS. Core injects overlay **markup** but does **not** ship overlay styles. The Tenant **must** supply CSS so overlay is visible.\n\n### 7.1 Required Selectors (Tenant global CSS)\n\n1. `[data-jp-section-overlay]` — `position: absolute; inset: 0`; `pointer-events: none`; base state transparent.\n2. `[data-section-id]:hover [data-jp-section-overlay]` — Hover: e.g. dashed border, subtle tint.\n3. `[data-section-id][data-jp-selected] [data-jp-section-overlay]` — Selected: solid border, optional tint.\n4. `[data-jp-section-overlay] > div` (type label) — Position and visibility (e.g. visible on hover/selected).\n\n### 7.2 Z-Index\n\nOverlay **z-index** high (e.g. 9999). Section content at or below CIP limit (§4.5).\n\n### 7.3 Responsibility\n\n**Core:** Injects wrapper and overlay DOM; sets `data-jp-selected`. **Tenant:** All overlay **visual** rules.\n\n**Perché servono (TOCC):** L’iframe dello Stage carica solo HTML/CSS del Tenant; il Core inietta il markup dell’overlay ma non gli stili. Senza CSS Tenant per i selettori TOCC, bordo hover/selected e type label non sarebbero visibili: l’autore non vedrebbe quale section è selezionata né il label del tipo. TOCC chiarisce la responsabilità (Core = markup, Tenant = aspetto) e garantisce UX uniforme tra tenant.\n\n---\n\n## 8. 📦 Base Section Data & Settings (BSDS) v1.0\n\n**Objective:** Standardize base schema fragments for anchors, array items, and section settings.\n\n### 8.1 BaseSectionData\n\nEvery section data schema **must** extend a base with at least `anchorId` (optional string). Canonical Zod (Tenant `lib/base-schemas.ts` or equivalent):\n\n```typescript\nexport const BaseSectionData = z.object({\n anchorId: z.string().optional().describe('ui:text'),\n});\n```\n\n### 8.2 BaseArrayItem\n\nEvery array item schema editable in the Inspector **must** include `id` (optional string minimum). Canonical Zod:\n\n```typescript\nexport const BaseArrayItem = z.object({\n id: z.string().optional(),\n});\n```\n\nRecommended: required UUID for new items. Used by `data-jp-item-id` and React reconciliation.\n\n### 8.3 BaseSectionSettings (Optional)\n\nCommon section-level settings. Canonical Zod (name **BaseSectionSettingsSchema** or as exported by Core):\n\n```typescript\nexport const BaseSectionSettingsSchema = z.object({\n paddingTop: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),\n paddingBottom: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),\n theme: z.enum(['dark', 'light', 'accent']).default('dark').describe('ui:select'),\n container: z.enum(['boxed', 'fluid']).default('boxed').describe('ui:select'),\n});\n```\n\nCapsules may extend this for type-specific settings. Core may export **BaseSectionSettings** as the TypeScript type inferred from this or a superset.\n\n**Perché servono (BSDS):** anchorId permette deep-link e navigazione in-page; id sugli array item è necessario per `data-jp-item-id`, reorder e React reconciliation. BaseSectionSettings comuni (padding, theme, container) evitano ripetizione e allineano il Form Factory tra capsule. Senza base condivisi, ogni capsule inventa convenzioni e validazione/add-section diventano fragili.\n\n---\n\n## 9. 📌 AddSectionConfig (ASC) v1.0\n\n**Objective:** Formalize the \"Add Section\" contract used by the Studio.\n\n**Type (Core exports** `AddSectionConfig`**):**\n\n```typescript\ninterface AddSectionConfig {\n addableSectionTypes: readonly string[];\n sectionTypeLabels: Record<string, string>;\n getDefaultSectionData(sectionType: string): Record<string, unknown>;\n}\n```\n\n**Shape:** Tenant provides one object (e.g. `addSectionConfig`) with:\n\n- `addableSectionTypes` — Readonly array of section type keys. Only these types appear in the Add Section Library. Must be a subset of (or equal to) the keys in SectionDataRegistry.\n- `sectionTypeLabels` — Map type key → display string (e.g. `{ hero: 'Hero', 'cta-banner': 'CTA Banner' }`).\n- `getDefaultSectionData(sectionType: string): Record<string, unknown>` — Returns default `data` for a new section. Must conform to the capsule’s data schema so the new section validates.\n\nCore creates a new section with deterministic UUID, `type`, and `data` from `getDefaultSectionData(type)`.\n\n**Perché servono (ASC):** Lo Studio deve mostrare una libreria “Aggiungi sezione” con nomi leggibili e, alla scelta, creare una section con dati iniziali validi. addableSectionTypes, sectionTypeLabels e getDefaultSectionData sono il contratto: il Tenant è l’unica fonte di verità su quali tipi sono addabili e con quali default. Senza ASC, il Core non saprebbe cosa mostrare in modal né come popolare i dati della nuova section.\n\n---\n\n## 10. ⚙️ JsonPagesConfig & Engine Bootstrap (JEB) v1.1\n\n**Objective:** Bootstrap contract between Tenant app and `@olonjs/core`.\n\n### 10.1 JsonPagesConfig (required fields)\n\nThe Tenant passes a single **config** object to **JsonPagesEngine**. Required fields:\n\nField Type Description **tenantId** string Passed to `resolveAssetUrl(path, tenantId)`; resolved asset URLs are `/assets/...` with no tenantId segment in the path. **registry** `{ [K in SectionType]: React.FC<SectionComponentPropsMap[K]> }` Component registry. Must match MTRP keys. See Appendix A. **schemas** `Record<SectionType, ZodType>` or equivalent SECTION_SCHEMAS: type → **data** Zod schema. Form Factory uses this. See Appendix A. **pages** `Record<string, PageConfig>` Slug → page config. See Appendix A. **siteConfig** SiteConfig Global site (identity, header/footer blocks). See Appendix A. **themeConfig** ThemeConfig Theme tokens. See Appendix A. **menuConfig** MenuConfig Navigation fallback payload and resolver context. Header navigation may also come from dereferenced `$ref` paths. See Appendix A. **refDocuments** `Record<string, unknown>` (optional) Extra JSON documents available to the recursive `$ref` resolver (for example `menu.json`, `config/menu.json`, `src/data/config/menu.json`). **themeCss** `{ tenant: string }` At least **tenant**: string (inline CSS or URL) for Stage iframe injection. **addSection** AddSectionConfig Add-section config (§9).\n\nCore may define optional fields. The Tenant must not omit required fields.\n\n### 10.2 JsonPagesEngine\n\nRoot component: `<JsonPagesEngine config={config} />`. Responsibilities: route → page, SectionRenderer per section; in Studio mode Sovereign Shell (Inspector, Control Bar, postMessage); section wrappers and overlay per IDAC and JAP. In v1.5, the engine uses a recursive `$ref` resolver (`config-resolver`) across runtime surfaces (Engine, renderer path, and preview path), so references such as `menu.json#/main` and `../config/menu.json#/main` can be resolved from local runtime docs and optional tenant `refDocuments`. Tenant does not implement the Shell.\n\n### 10.3 Studio Selection Event Contract (v1.3, breaking)\n\nIn strict v1.3 Studio, section selection payload for nested targets is path-based:\n\n```typescript\ntype SectionSelectMessage = {\n type: 'SECTION_SELECT';\n section: { id: string; type: string; scope: 'global' | 'local' };\n itemPath?: SelectionPath; // root -> leaf\n};\n```\n\nRemoved from strict protocol:\n\n- `itemField`\n- `itemId`\n\n**Perché servono (JEB):** Un unico punto di bootstrap (config + Engine) evita che il Tenant replichi logica di routing, Shell e overlay. I campi obbligatori in JsonPagesConfig (tenantId, registry, schemas, pages, siteConfig, themeConfig, menuConfig, themeCss, addSection) sono il minimo per far funzionare rendering, Studio e Form Factory; `refDocuments` estende il runtime con documenti aggiuntivi per la dereferenziazione ricorsiva. In v1.3, il payload `itemPath` sincronizza in modo non ambiguo Stage e Inspector su nested arrays.\n\n---\n\n# 🏛️ OlonJS_ADMIN_PROTOCOL (JAP) v1.2\n\n**Status:** Mandatory Standard\\\n**Version:** 1.2.0 (Sovereign Shell Edition — Path/Nested Strictness)\\\n**Objective:** Deterministic orchestration of the \"Studio\" environment (ICE Level 1).\n\n---\n\n## 1. The Sovereign Shell Topology\n\nThe Admin interface is a **Sovereign Shell** from `@olonjs/core`.\n\n1. **The Stage (Canvas):** Isolated Iframe; postMessage for data updates and selection mirroring. Section markup follows **IDAC** (§6); overlay styling follows **TOCC** (§7).\n2. **The Inspector (Sidebar):** Consumes Tenant Zod schemas to generate editors; binding via `data-jp-field` and `data-jp-item-*`.\n3. **The Studio Actions:** Save to file, Hot Save, Add Section.\n\n## 2. State Orchestration & Persistence\n\n- **Working Draft:** Reactive local state for unsaved changes.\n- **Sync Law:** Inspector changes → Working Draft → Stage via `STUDIO_EVENTS.UPDATE_DRAFTS`.\n- **Persistence Protocol:** Studio invokes tenant-provided `saveToFile` and `hotSave` callbacks for editorial persistence.\n\n## 3. Context Switching (Global vs. Local)\n\n- **Header/Footer** selection → Global Mode, `site.json`.\n- Any other section → Page Mode, current `[slug].json`.\n\n## 4. Section Lifecycle Management\n\n1. **Add Section:** Modal from Tenant `SECTION_SCHEMAS`; UUID + default data via **AddSectionConfig** (§9).\n2. **Reorder:** Inspector or Stage Overlay; array mutation in Working Draft.\n3. **Delete:** Confirmation; remove from array, clear selection.\n\n## 5. Stage Isolation & Overlay\n\n- **CSS Shielding:** Stage in Iframe; Tenant CSS does not leak into Admin.\n- **Sovereign Overlay:** Selection ring and type labels injected per **IDAC** (§6); Tenant styles them per **TOCC** (§7).\n\n## 6. \"Green Build\" Validation\n\nStudio enforces `tsc && vite build`. No Studio or SSG build should proceed with TypeScript errors.\n\n## 7. Path-Deterministic Selection & Sidebar Expansion (v1.3, breaking)\n\n- Section/item focus synchronization uses `itemPath` (root → leaf), not flat `itemField/itemId`.\n- Sidebar expansion state for nested arrays must be derived from all path segments.\n- Flat-only matching may open/close wrong branches and is non-compliant in strict mode.\n\n**Perché servono (JAP):** Stage in iframe + Inspector + Studio actions separano il contesto di editing dal sito; postMessage e Working Draft permettono modifiche senza toccare subito i file. Save to file e Hot Save richiedono uno stato coerente. Global vs Page mode evita confusione su dove si sta editando (site.json vs \\[slug\\].json). Add/Reorder/Delete sono gestiti in un solo modo (Working Draft + ASC). Green Build garantisce che Studio e SSG compilino correttamente. In v1.3, il path completo elimina ambiguità nella sincronizzazione Stage↔Sidebar su strutture annidate.\n\n---\n\n## Compliance: Legacy vs Full UX (v1.5)\n\nDimension Legacy / Less UX Full UX (Core-aligned) **ICE binding** No `data-jp-*`; Inspector cannot bind. IDAC (§6) on every editable section/field/item. **Section wrapper** Plain `<section>`; no overlay contract. Core wrapper + overlay; Tenant CSS per TOCC (§7). **Design tokens** Raw BEM / fixed classes, or local vars fed by literals. `theme.json` as source of truth, mandatory runtime publication, local color/radius scope via `--local-*`, typography via canonical semantic font chain, no primary hardcoded themed values. **Base schemas** Ad hoc. BSDS (§8): BaseSectionData, BaseArrayItem, BaseSectionSettings. **Add Section** Ad hoc defaults. ASC (§9): addableSectionTypes, labels, getDefaultSectionData. **Bootstrap** Implicit. JEB (§10): JsonPagesConfig + JsonPagesEngine. **Selection payload** Flat `itemField/itemId`. Path-only `itemPath: SelectionPath` (JEB §10.3). **Nested array expansion** Single-segment or field-only heuristics. Root-to-leaf path expansion (ECIP §5.5, JAP §7). **Array item identity (strict)** Index fallback tolerated. Stable `id` required for editable object arrays.\n\n**Rule:** Every page section (non-header/footer) that appears in the Stage and in `SECTION_SCHEMAS` must comply with §6, §7, §4.4, §8, §9, §10 for full Studio UX.\n\n---\n\n## Summary of v1.5 Additions\n\n§ Title Purpose 4.4.3 Three-Layer CSS Bridge Replaces the informal \"publish CSS vars\" rule with the deterministic Layer 0 (engine injection) → Layer 1 (`:root` semantic bridge) → Layer 2 (`@theme` Tailwind bridge) architecture. Documents the engine's `--theme-colors-{name}` naming convention and the tenant's sovereign naming freedom in Layer 1. A.2.6 ThemeConfig (v1.5) Replaces the incorrect `surface/surfaceAlt/text/textMuted` canonical keys with the actual schema-aligned keys (`card`, `elevated`, `foreground`, `muted-foreground`, etc.). Adds `spacing`, `zIndex`, full typography sub-interfaces (`scale`, `tracking`, `leading`, `wordmark`), and `modes`. Establishes `theme.json` as SOT with schema as the formalisation layer.\n\n---\n\n## Summary of v1.5 Additions\n\n§ Title Purpose 4.4 Local Design Tokens Makes the `theme.json -> runtime vars -> --local-* -> JSX classes` chain explicit and normative. 4.4.3 Runtime Theme Publication Makes runtime CSS publication mandatory for themed tenants. 4.4.5 Canonical Typography Rule Removes ambiguity between global semantic font utilities and local token scoping. 4.4.7 Compliance Rules Turns Local Design Tokens into a checklist-grade compliance contract. 4.4.9 Non-Compliant Patterns Makes hardcoded token anti-patterns explicit. **Appendix A.2.6** **Deterministic ThemeConfig** Aligns the spec-level theme contract with the core’s structured semantic keys plus extension policy. **Appendix A.7** **Local Design Tokens Implementation Addendum** Operational checklist and implementation examples for compliant tenant sections.\n\n---\n\n# Appendix A — Tenant Type & Code-Generation Annex\n\n**Objective:** Make the specification **sufficient** to generate or audit a full tenant (new site, new components, new data) without a reference codebase. Defines TypeScript types, JSON shapes, schema contract, file paths, and integration pattern.\n\n**Status:** Mandatory for code-generation and governance. Compliance ensures generated tenants are typed and wired like the reference implementation.\n\n---\n\n## A.1 Core-Provided Types (from `@olonjs/core`)\n\nThe following are assumed to be exported by Core. The Tenant augments **SectionDataRegistry** and **SectionSettingsRegistry**; all other types are consumed as-is.\n\nType Description **SectionType** `keyof SectionDataRegistry` (after Tenant augmentation). Union of all section type keys. **Section** Union of `BaseSection<K>` for all K in SectionDataRegistry. See MTRP §1.2. **BaseSectionSettings** Optional base type for section settings (may align with BSDS §8.3). **MenuItem** Navigation item. **Minimum shape:** `{ label: string; href: string }`. Core may extend (e.g. `children?: MenuItem[]`). **AddSectionConfig** See §9. **JsonPagesConfig** See §10.1.\n\n**Perché servono (A.1):** Il Tenant deve conoscere i tipi esportati dal Core (SectionType, MenuItem, AddSectionConfig, JsonPagesConfig) per tipizzare registry, config e augmentation senza dipendere da implementazioni interne.\n\n---\n\n## A.2 Tenant-Provided Types (single source: `src/types.ts` or equivalent)\n\nThe Tenant **must** define the following in one module (e.g. `src/types.ts`). This module **must** perform the **module augmentation** of `@olonjs/core` for **SectionDataRegistry** and **SectionSettingsRegistry**, and **must** export **SectionComponentPropsMap** and re-export from `@olonjs/core` so that **SectionType** is available after augmentation.\n\n### A.2.1 SectionComponentPropsMap\n\nMaps each section type to the props of its React component. **Header** is the only type that receives **menu**.\n\n**Option A — Explicit (recommended for clarity and tooling):** For each section type K, add one entry. Header receives **menu**.\n\n```typescript\nimport type { MenuItem } from '@olonjs/core';\n// Import Data/Settings from each capsule.\n\nexport type SectionComponentPropsMap = {\n 'header': { data: HeaderData; settings?: HeaderSettings; menu: MenuItem[] };\n 'footer': { data: FooterData; settings?: FooterSettings };\n 'hero': { data: HeroData; settings?: HeroSettings };\n // ... one entry per SectionType, e.g. 'feature-grid', 'cta-banner', etc.\n};\n```\n\n**Option B — Mapped type (DRY, requires SectionDataRegistry/SectionSettingsRegistry in scope):**\n\n```typescript\nimport type { MenuItem } from '@olonjs/core';\n\nexport type SectionComponentPropsMap = {\n [K in SectionType]: K extends 'header'\n ? { data: SectionDataRegistry[K]; settings?: SectionSettingsRegistry[K]; menu: MenuItem[] }\n : { data: SectionDataRegistry[K]; settings?: K extends keyof SectionSettingsRegistry ? SectionSettingsRegistry[K] : BaseSectionSettings };\n};\n```\n\nSectionType is imported from Core (after Tenant augmentation). In practice Option A is the reference pattern; Option B is valid if the Tenant prefers a single derived definition.\n\n**Perché servono (A.2):** SectionComponentPropsMap e i tipi di config (PageConfig, SiteConfig, MenuConfig, ThemeConfig) definiscono il contratto tra dati (JSON, API) e componente; l’augmentation è l’unico modo per estendere i registry del Core senza fork. Senza questi tipi, generazione tenant e refactor sarebbero senza guida e il type-check fallirebbe.\n\n### A.2.2 ComponentRegistry type\n\nThe registry object **must** be typed as:\n\n```typescript\nimport type { SectionType } from '@olonjs/core';\nimport type { SectionComponentPropsMap } from '@/types';\n\nexport const ComponentRegistry: {\n [K in SectionType]: React.FC<SectionComponentPropsMap[K]>;\n} = { /* ... */ };\n```\n\nFile: `src/lib/ComponentRegistry.tsx` (or equivalent). Imports one View per section type and assigns it to the corresponding key.\n\n### A.2.3 PageConfig\n\nMinimum shape for a single page (used in **pages** and in each `[slug].json`):\n\n```typescript\nexport interface PageConfig {\n id?: string;\n slug: string;\n meta?: {\n title?: string;\n description?: string;\n };\n sections: Section[];\n}\n```\n\n**Section** is the union type from MTRP (§1.2). Each element of **sections** has **id**, **type**, **data**, **settings** and conforms to the capsule schemas.\n\n### A.2.4 SiteConfig\n\nMinimum shape for **site.json** (and for **siteConfig** in JsonPagesConfig):\n\n```typescript\nexport interface SiteConfigIdentity {\n title?: string;\n logoUrl?: string;\n}\n\nexport interface SiteConfig {\n identity?: SiteConfigIdentity;\n pages?: Array<{ slug: string; label: string }>;\n header: {\n id: string;\n type: 'header';\n data: HeaderData;\n settings?: HeaderSettings;\n };\n footer: {\n id: string;\n type: 'footer';\n data: FooterData;\n settings?: FooterSettings;\n };\n}\n```\n\n**HeaderData**, **FooterData**, **HeaderSettings**, **FooterSettings** are the types exported from the header and footer capsules.\n\n### A.2.5 MenuConfig\n\nMinimum shape for **menu.json** (and for **menuConfig** in JsonPagesConfig). Structure is tenant-defined; Core expects the header to receive **MenuItem\\[\\]** after runtime resolution. Common pattern: an object with a key (e.g. **main**) whose value is **MenuItem\\[\\]**.\n\n```typescript\nexport interface MenuConfig {\n main?: MenuItem[];\n [key: string]: MenuItem[] | undefined;\n}\n```\n\nOr simply `MenuItem[]` if the app uses a single flat list. In v1.5 runtime behavior, header menu is resolved through recursive `$ref` resolution first (e.g. `menu.json#/main`, `../config/menu.json#/main`) using local documents and optional `refDocuments`, then falls back to `menuConfig.main` / `menuConfig` when needed. The final value passed to header must conform to **MenuItem\\[\\]**.\n\n### A.2.6 ThemeConfig\n\nMinimum shape for **theme.json** (and for **themeConfig** in JsonPagesConfig). `theme.json` is the **source of truth** for the entire visual contract of the tenant. The schema (`design-system.schema.json`) is the machine-readable formalisation of this contract — if the TypeScript interfaces and the JSON Schema diverge, the JSON Schema wins.\n\n**Naming policy:** The keys within `tokens.colors` are the tenant's sovereign choice. The engine flattens all keys to `--theme-colors-{name}` regardless of naming convention. The required keys listed below are the ones the engine's `:root` bridge and the `@theme` Tailwind bridge must be able to resolve. Extra brand-specific keys are always allowed as additive extensions.\n\n```typescript\nexport interface ThemeColors {\n /* Required — backgrounds */\n background: string;\n card: string;\n elevated: string;\n overlay: string;\n popover: string;\n 'popover-foreground': string;\n\n /* Required — foregrounds */\n foreground: string;\n 'card-foreground': string;\n 'muted-foreground': string;\n placeholder: string;\n\n /* Required — brand */\n primary: string;\n 'primary-foreground': string;\n 'primary-light': string;\n 'primary-dark': string;\n\n /* Optional — brand ramp (50–900) */\n 'primary-50'?: string;\n 'primary-100'?: string;\n 'primary-200'?: string;\n 'primary-300'?: string;\n 'primary-400'?: string;\n 'primary-500'?: string;\n 'primary-600'?: string;\n 'primary-700'?: string;\n 'primary-800'?: string;\n 'primary-900'?: string;\n\n /* Required — accent, secondary, muted */\n accent: string;\n 'accent-foreground': string;\n secondary: string;\n 'secondary-foreground': string;\n muted: string;\n\n /* Required — border, form */\n border: string;\n 'border-strong': string;\n input: string;\n ring: string;\n\n /* Required — feedback */\n destructive: string;\n 'destructive-foreground': string;\n 'destructive-border': string;\n 'destructive-ring': string;\n success: string;\n 'success-foreground': string;\n 'success-border': string;\n 'success-indicator': string;\n warning: string;\n 'warning-foreground': string;\n 'warning-border': string;\n info: string;\n 'info-foreground': string;\n 'info-border': string;\n\n [key: string]: string | undefined;\n}\n\nexport interface ThemeFontFamily {\n primary: string;\n mono: string;\n display?: string;\n [key: string]: string | undefined;\n}\n\nexport interface ThemeWordmark {\n fontFamily: string;\n weight: string;\n width: string;\n}\n\nexport interface ThemeTypography {\n fontFamily: ThemeFontFamily;\n wordmark?: ThemeWordmark;\n scale?: Record<string, string>; /* xs sm base md lg xl 2xl 3xl 4xl 5xl 6xl 7xl */\n tracking?: Record<string, string>; /* tight display normal wide label */\n leading?: Record<string, string>; /* none tight snug normal relaxed */\n}\n\nexport interface ThemeBorderRadius {\n sm: string;\n md: string;\n lg: string;\n xl?: string;\n full?: string;\n [key: string]: string | undefined;\n}\n\nexport interface ThemeSpacing {\n 'container-max'?: string;\n 'section-y'?: string;\n 'header-h'?: string;\n 'sidebar-w'?: string;\n [key: string]: string | undefined;\n}\n\nexport interface ThemeZIndex {\n base?: string;\n elevated?: string;\n dropdown?: string;\n sticky?: string;\n overlay?: string;\n modal?: string;\n toast?: string;\n [key: string]: string | undefined;\n}\n\nexport interface ThemeModes {\n [mode: string]: { colors: Partial<ThemeColors> };\n}\n\nexport interface ThemeTokens {\n colors: ThemeColors;\n typography: ThemeTypography;\n borderRadius: ThemeBorderRadius;\n spacing?: ThemeSpacing;\n zIndex?: ThemeZIndex;\n modes?: ThemeModes;\n}\n\nexport interface ThemeConfig {\n name: string;\n tokens: ThemeTokens;\n}\n```\n\n**Rule:** `theme.json` is the single source of truth. All layers downstream (engine injection, `:root` bridge, `@theme` bridge, React JSX) are read-only consumers. No layer below `theme.json` may hardcode a value that belongs to the theme contract.\n\n**Rule:** Brand-specific extension keys (e.g. `colors.primary-50` through `primary-900`, custom spacing tokens) are always allowed as additive extensions within the canonical groups. They must not replace the required semantic keys.\n\n---\n\n## A.3 Schema Contract (SECTION_SCHEMAS)\n\n**Location:** `src/lib/schemas.ts` (or equivalent).\n\n**Contract:**\n\n- **SECTION_SCHEMAS** is a **single object** whose keys are **SectionType** and whose values are **Zod schemas for the section data** (not settings, unless the Form Factory contract expects a combined or per-type settings schema; then each value may be the data schema only, and settings may be defined per capsule and aggregated elsewhere if needed).\n- The Tenant **must** re-export **BaseSectionData**, **BaseArrayItem**, and optionally **BaseSectionSettingsSchema** from `src/lib/base-schemas.ts` (or equivalent). Each capsule’s data schema **must** extend BaseSectionData; each array item schema **must** extend or include BaseArrayItem.\n- **SECTION_SCHEMAS** is typed as `Record<SectionType, ZodType>` or `{ [K in SectionType]: ZodType }` so that keys match the registry and SectionDataRegistry.\n\n**Export:** The app imports **SECTION_SCHEMAS** and passes it as **config.schemas** to JsonPagesEngine. The Form Factory traverses these schemas to build editors.\n\n**Perché servono (A.3):** Un unico oggetto SECTION_SCHEMAS con chiavi = SectionType e valori = schema data permette al Form Factory di costruire form per tipo senza convenzioni ad hoc; i base schema garantiscono anchorId e id su item. Senza questo contratto, l’Inspector non saprebbe quali campi mostrare né come validare.\n\n---\n\n## A.4 File Paths & Data Layout\n\nPurpose Path (conventional) Description Site config `src/data/config/site.json` SiteConfig (identity, header, footer, pages list). Menu config `src/data/config/menu.json` MenuConfig (e.g. main nav). Theme config `src/data/config/theme.json` ThemeConfig (tokens). Page data `src/data/pages/<slug>.json` One file per page; content is PageConfig (slug, meta, sections). Base schemas `src/lib/base-schemas.ts` BaseSectionData, BaseArrayItem, BaseSectionSettingsSchema. Schema aggregate `src/lib/schemas.ts` SECTION_SCHEMAS; re-exports base schemas. Registry `src/lib/ComponentRegistry.tsx` ComponentRegistry object. Add-section config `src/lib/addSectionConfig.ts` addSectionConfig (AddSectionConfig). Tenant types & augmentation `src/types.ts` SectionComponentPropsMap, PageConfig, SiteConfig, MenuConfig, ThemeConfig; **declare module '@olonjs/core'** for SectionDataRegistry and SectionSettingsRegistry; re-export from `@olonjs/core`. Bootstrap `src/App.tsx` Imports config (site, theme, menu, pages), optional `refDocuments`, registry, schemas, addSection, themeCss; builds JsonPagesConfig; renders .\n\nThe app entry (e.g. **main.tsx**) renders **App**. No other bootstrap contract is specified; the Tenant may use Vite aliases (e.g. **@/**) for the paths above.\n\n**Perché servono (A.4):** Path fissi (data/config, data/pages, lib/schemas, types.ts, App.tsx) permettono a CLI, tooling e agenti di trovare sempre gli stessi file; l’onboarding e la generazione da spec sono deterministici. Senza convenzione, ogni tenant sarebbe una struttura diversa.\n\n---\n\n## A.5 Integration Checklist (Code-Generation)\n\nWhen generating or auditing a tenant, ensure the following in order:\n\n 1. **Capsules** — For each section type, create `src/components/<type>/` with View.tsx, schema.ts, types.ts, index.ts. Data schema extends BaseSectionData; array items extend BaseArrayItem; View complies with CIP and IDAC (§6.2–6.3 for non-reserved types).\n 2. **Base schemas** — **src/lib/base-schemas.ts** exports BaseSectionData, BaseArrayItem, BaseSectionSettingsSchema (and optional CtaSchema or similar shared fragments).\n 3. **types.ts** — Define SectionComponentPropsMap (header with **menu**), PageConfig, SiteConfig, MenuConfig, ThemeConfig; **declare module '@olonjs/core'** and augment SectionDataRegistry and SectionSettingsRegistry; re-export from `@olonjs/core`.\n 4. **ComponentRegistry** — Import every View; build object **{ \\[K in SectionType\\]: ViewComponent }**; type as **{ \\[K in SectionType\\]: React.FC&lt;SectionComponentPropsMap\\[K\\]&gt; }**.\n 5. **schemas.ts** — Import base schemas and each capsule’s data schema; export SECTION_SCHEMAS as **{ \\[K in SectionType\\]: SchemaK }**; export SectionType as **keyof typeof SECTION_SCHEMAS** if not using Core’s SectionType.\n 6. **addSectionConfig** — addableSectionTypes, sectionTypeLabels, getDefaultSectionData; export as AddSectionConfig.\n 7. **App.tsx** — Import site, theme, menu, pages from data paths; expose optional `refDocuments` for recursive `$ref` resolution; build config (tenantId, registry, schemas, pages, siteConfig, themeConfig, menuConfig, refDocuments, themeCss: { tenant }, addSection); render JsonPagesEngine.\n 8. **Data files** — Create or update site.json, menu.json, theme.json, and one or more **.json** under the paths in A.4. Ensure JSON shapes match SiteConfig, MenuConfig, ThemeConfig, PageConfig.\n 9. **Runtime theme publication** — Publish the theme contract as runtime CSS custom properties before themed sections render.\n10. **Tenant CSS** — Include TOCC (§7) selectors in global CSS so the Stage overlay is visible, and bridge semantic theme variables where needed.\n11. **Reserved types** — Header and footer capsules receive props per SectionComponentPropsMap; menu is resolved from the JSON graph via `$ref` (with `refDocuments` support) and falls back to `menuConfig` values when direct resolution is unavailable.\n\n**Perché servono (A.5):** La checklist in ordine evita di dimenticare passi (es. augmentation prima del registry, TOCC dopo le View) e rende la spec sufficiente per generare o verificare un tenant senza codebase di riferimento.\n\n---\n\n## A.6 v1.3 Path/Nested Strictness Addendum (breaking)\n\nThis addendum extends Appendix A without removing prior v1.2 obligations:\n\n1. **Type exports** — Core and/or shared types module should expose `SelectionPathSegment` and `SelectionPath` for Studio messaging and Inspector expansion logic.\n2. **Protocol migration** — Replace flat payload fields `itemField` / `itemId` with `itemPath?: SelectionPath` in strict v1.3 channels.\n3. **Nested array compliance** — For editable object arrays, item identity must be stable (`id`) and propagated to DOM attributes (`data-jp-item-id`), schema items (BaseArrayItem), and selection path segments (`itemId` when segment targets array item).\n4. **Backward compatibility policy** — Legacy flat fields may exist only in transitional adapters outside strict mode; normative v1.3 contract is path-only.\n\n---\n\n## A.7 v1.5 Local Design Tokens Implementation Addendum\n\nThis addendum extends Appendix A without removing prior v1.3 obligations:\n\n1. **Theme source of truth** — Tenant theme values belong in `src/data/config/theme.json`.\n2. **Runtime publication** — Core and/or tenant bootstrap **must** expose those values as runtime CSS custom properties before section rendering.\n3. **Local scope** — A themed section must define `--local-*` variables on its root for the color/radius concerns it owns.\n4. **Class consumption** — Section-owned color/radius utilities must consume `var(--local-*)`, not raw hardcoded theme values.\n5. **Typography policy** — Fonts must consume the published semantic font chain; local font tokens are optional and only for local remapping.\n6. **Migration policy** — Hardcoded colors/radii may exist only as temporary compatibility shims or purely decorative exceptions, not as the primary section contract.\n\nCanonical implementation pattern:\n\n```text\ntheme.json -> published runtime theme vars -> section --local-* -> JSX classes\n```\n\nCanonical typography pattern:\n\n```text\ntheme.json -> published semantic font vars -> tenant font utility/variable -> section typography\n```\n\nMinimal compliant example:\n\n```tsx\n<section\n style={{\n '--local-bg': 'var(--background)',\n '--local-text': 'var(--foreground)',\n '--local-primary': 'var(--primary)',\n '--local-radius-md': 'var(--theme-radius-md)',\n } as React.CSSProperties}\n className=\"bg-[var(--local-bg)]\"\n>\n <h2 className=\"font-display text-[var(--local-text)]\">Title</h2>\n <a className=\"bg-[var(--local-primary)] rounded-[var(--local-radius-md)]\">CTA</a>\n</section>\n```\n\nDeterministic compliance checklist:\n\n1. Canonical semantic theme keys exist.\n2. Runtime publication exists.\n3. Section-local color/radius scope exists.\n4. Section-owned color/radius classes consume `var(--local-*)`.\n5. Fonts consume the semantic published font chain.\n6. Primary themed values are not hardcoded.\n\n---\n\n**Validation:** Align with current `@olonjs/core` exports (SectionType, MenuItem, AddSectionConfig, JsonPagesConfig, and in v1.3+ path types for Studio selection), with the deterministic `ThemeConfig` contract, and with the runtime theme publication contract used by tenant CSS.\\\n**Distribution:** Core via `.yalc`; tenant projections via `@olonjs/cli`. This annex makes the spec **necessary and sufficient** for tenant code-generation and governance at enterprise grade."
11590
11513
  },
11591
11514
  "settings": {}
11592
11515
  }
11593
11516
  ]
11594
- }
11595
-
11517
+ }
11596
11518
  END_OF_FILE_CONTENT
11597
11519
  echo "Creating src/data/pages/home.json..."
11598
11520
  cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
@@ -11613,19 +11535,21 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
11613
11535
  "headline": "Contract Layer",
11614
11536
  "subline": "for the agentic web.",
11615
11537
  "body": "AI agents are becoming operational actors in commerce, marketing, and support. They need more than content — they need a contract. OlonJS is the deterministic machine contract for websites: every site typed, structured, and addressable by design. No custom glue. No fragile integrations. Just a contract any agent can read and operate.",
11616
- "cta": {
11617
- "primary": {
11618
- "label": "Get started",
11619
- "href": "#getstarted"
11620
- },
11621
- "secondary": {
11622
- "label": "GitHub",
11623
- "href": "https://github.com/olonjs/core"
11624
- },
11625
- "ghost": {
11626
- "label": "Explore platform",
11627
- "href": "#architecture"
11628
- }
11538
+ "primaryCta": {
11539
+ "label": "Get started",
11540
+ "href": "#getstarted"
11541
+ },
11542
+ "secondaryCta": {
11543
+ "label": "GitHub",
11544
+ "href": "https://github.com/olonjs/core"
11545
+ },
11546
+ "ghostCta": {
11547
+ "label": "Explore platform",
11548
+ "href": "#architecture"
11549
+ },
11550
+ "image": {
11551
+ "url": "/assets/images/plug-graded-square.jpg",
11552
+ "alt": "Olon interface port engraved into a dark stone surface"
11629
11553
  }
11630
11554
  }
11631
11555
  },
@@ -11802,6 +11726,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
11802
11726
  }
11803
11727
  ]
11804
11728
  }
11729
+
11805
11730
  END_OF_FILE_CONTENT
11806
11731
  echo "Creating src/data/pages/home_.json..."
11807
11732
  cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home_.json"
@@ -596,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
596
596
  "@tiptap/extension-link": "^2.11.5",
597
597
  "@tiptap/react": "^2.11.5",
598
598
  "@tiptap/starter-kit": "^2.11.5",
599
- "@olonjs/core": "^1.0.107",
599
+ "@olonjs/core": "^1.0.109",
600
600
  "clsx": "^2.1.1",
601
601
  "lucide-react": "^0.474.0",
602
602
  "react": "^19.0.0",
@@ -1644,7 +1644,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
1644
1644
  "@tiptap/extension-link": "^2.11.5",
1645
1645
  "@tiptap/react": "^2.11.5",
1646
1646
  "@tiptap/starter-kit": "^2.11.5",
1647
- "@olonjs/core": "^1.0.107",
1647
+ "@olonjs/core": "^1.0.109",
1648
1648
  "class-variance-authority": "^0.7.1",
1649
1649
  "clsx": "^2.1.1",
1650
1650
  "lucide-react": "^0.474.0",
@@ -1970,6 +1970,7 @@ const {
1970
1970
  buildPageManifest,
1971
1971
  buildPageManifestHref,
1972
1972
  buildSiteManifest,
1973
+ buildLlmsTxt,
1973
1974
  } = await import(contractsUrl);
1974
1975
 
1975
1976
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -1978,7 +1979,7 @@ const pagesDir = path.resolve(root, 'src/data/pages');
1978
1979
  const publicDir = path.resolve(root, 'public');
1979
1980
  const distDir = path.resolve(root, 'dist');
1980
1981
 
1981
- async function writeJsonTargets(relativePath, value) {
1982
+ async function writeTargets(relativePath, content) {
1982
1983
  const targets = [
1983
1984
  path.resolve(publicDir, relativePath),
1984
1985
  path.resolve(distDir, relativePath),
@@ -1986,10 +1987,14 @@ async function writeJsonTargets(relativePath, value) {
1986
1987
 
1987
1988
  for (const targetPath of targets) {
1988
1989
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
1989
- await fs.writeFile(targetPath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
1990
+ await fs.writeFile(targetPath, content, 'utf-8');
1990
1991
  }
1991
1992
  }
1992
1993
 
1994
+ async function writeJsonTargets(relativePath, value) {
1995
+ await writeTargets(relativePath, `${JSON.stringify(value, null, 2)}\n`);
1996
+ }
1997
+
1993
1998
  function escapeHtmlAttribute(value) {
1994
1999
  return String(value).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
1995
2000
  }
@@ -2108,6 +2113,13 @@ const mcpManifest = buildSiteManifest({
2108
2113
  });
2109
2114
  await writeJsonTargets('mcp-manifest.json', mcpManifest);
2110
2115
 
2116
+ const llmsTxtContent = buildLlmsTxt({
2117
+ pages: webMcpBuildState.pages,
2118
+ schemas: webMcpBuildState.schemas,
2119
+ siteConfig: webMcpBuildState.siteConfig,
2120
+ });
2121
+ await writeTargets('llms.txt', `${llmsTxtContent}\n`);
2122
+
2111
2123
  for (const { slug, out, depth } of targets) {
2112
2124
  console.log(`\n[bake] Rendering /${slug === 'home' ? '' : slug}...`);
2113
2125
 
@@ -7192,13 +7204,13 @@ export function OlonArchitectureView({ data }: Props) {
7192
7204
  </p>
7193
7205
  )}
7194
7206
 
7195
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden"
7196
- data-jp-array="protocols">
7207
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden">
7197
7208
  {data.protocols.map((p) => (
7198
7209
  <div key={p.id}
7199
7210
  className="bg-[var(--local-card)] p-7 flex flex-col gap-3 border-r border-b border-[var(--local-border)] hover:bg-[var(--elevated)] transition-colors"
7200
- data-jp-item-id={p.id}>
7201
- <div>{ICONS[p.icon]}</div>
7211
+ data-jp-item-id={p.id}
7212
+ data-jp-item-field="protocols">
7213
+ <div data-jp-field="icon">{ICONS[p.icon]}</div>
7202
7214
  <p className="text-[11px] font-semibold tracking-[0.1em] uppercase text-[var(--local-p400)] font-mono"
7203
7215
  data-jp-field="version">{p.acronym} · {p.version}</p>
7204
7216
  <p className="font-bold text-foreground text-base" data-jp-field="name">{p.name}</p>
@@ -7402,12 +7414,12 @@ export function OlonGetStartedView({ data }: Props) {
7402
7414
  <p className="text-base text-[var(--local-muted)] leading-relaxed max-w-2xl mb-12"
7403
7415
  data-jp-field="body">{data.body}</p>
7404
7416
 
7405
- <div className="grid grid-cols-1 md:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden"
7406
- data-jp-array="cards">
7417
+ <div className="grid grid-cols-1 md:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden">
7407
7418
  {data.cards.map((card) => (
7408
7419
  <div key={card.id}
7409
7420
  className="bg-[var(--local-card)] p-8 flex flex-col gap-4 border-r last:border-r-0 border-[var(--local-border)] hover:bg-[var(--elevated)] transition-colors"
7410
- data-jp-item-id={card.id}>
7421
+ data-jp-item-id={card.id}
7422
+ data-jp-item-field="cards">
7411
7423
  <span className={`text-[11px] font-bold tracking-[0.08em] uppercase px-2.5 py-0.5 rounded-full w-fit ${BADGE_STYLES[card.badgeStyle]}`}
7412
7424
  data-jp-field="badge">
7413
7425
  {card.badge}
@@ -7422,7 +7434,7 @@ export function OlonGetStartedView({ data }: Props) {
7422
7434
  </code>
7423
7435
  )}
7424
7436
  {card.deployHref && card.deployLabel && (
7425
- <Button asChild variant="outline" size="sm" className="w-fit">
7437
+ <Button asChild variant="outline" size="sm" className="w-fit" data-jp-field="deployLabel">
7426
7438
  <a href={card.deployHref} target="_blank" rel="noopener noreferrer">
7427
7439
  {card.deployLabel}
7428
7440
  </a>
@@ -7616,7 +7628,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/components/olon-hero/View.tsx"
7616
7628
  import type { OlonHeroData } from './types';
7617
7629
  import { Button } from '@/components/ui/button';
7618
7630
  import { Github, Terminal } from 'lucide-react';
7619
-
7631
+ import { resolveAssetUrl, useConfig } from '@olonjs/core';
7620
7632
 
7621
7633
  interface Props {
7622
7634
  data: OlonHeroData;
@@ -7624,7 +7636,14 @@ interface Props {
7624
7636
 
7625
7637
  const heroPlugImage = '/assets/images/plug-graded-square.jpg';
7626
7638
 
7639
+ function hasRenderableCta(cta: { label?: string; href?: string } | undefined): cta is { label: string; href: string } {
7640
+ return Boolean(cta?.label?.trim() && cta?.href?.trim());
7641
+ }
7642
+
7627
7643
  export function OlonHeroView({ data }: Props) {
7644
+ const { tenantId = 'default' } = useConfig();
7645
+ const imageUrl = data.image?.url ? resolveAssetUrl(data.image.url, tenantId) : heroPlugImage;
7646
+
7628
7647
  return (
7629
7648
  <section
7630
7649
  style={{
@@ -7685,40 +7704,53 @@ export function OlonHeroView({ data }: Props) {
7685
7704
  </p>
7686
7705
 
7687
7706
  <div className="flex flex-wrap gap-3 items-center">
7688
- <Button asChild size="lg" className="font-semibold">
7689
- <a href={data.cta.primary.href}>
7690
- {data.cta.primary.label}
7691
- </a>
7692
- </Button>
7693
- <Button asChild variant="outline" size="lg" className="font-semibold gap-2">
7694
- <a href={data.cta.secondary.href}>
7695
- <Github className="w-4 h-4" />
7696
- {data.cta.secondary.label}
7697
- </a>
7698
- </Button>
7699
- <a
7700
- href={data.cta.ghost.href}
7701
- className="text-sm text-[var(--local-muted)] hover:text-[var(--local-fg)] transition-colors flex items-center gap-1.5"
7702
- >
7703
- {data.cta.ghost.label}
7704
- <Terminal className="w-4 h-4" />
7705
- </a>
7707
+ {hasRenderableCta(data.primaryCta) && (
7708
+ <div data-jp-field="primaryCta">
7709
+ <Button asChild size="lg" className="font-semibold">
7710
+ <a href={data.primaryCta.href}>
7711
+ {data.primaryCta.label} →
7712
+ </a>
7713
+ </Button>
7714
+ </div>
7715
+ )}
7716
+ {hasRenderableCta(data.secondaryCta) && (
7717
+ <div data-jp-field="secondaryCta">
7718
+ <Button asChild variant="outline" size="lg" className="font-semibold gap-2">
7719
+ <a href={data.secondaryCta.href}>
7720
+ <Github className="w-4 h-4" />
7721
+ {data.secondaryCta.label}
7722
+ </a>
7723
+ </Button>
7724
+ </div>
7725
+ )}
7726
+ {hasRenderableCta(data.ghostCta) && (
7727
+ <div data-jp-field="ghostCta">
7728
+ <a
7729
+ href={data.ghostCta.href}
7730
+ className="text-sm text-[var(--local-muted)] hover:text-[var(--local-fg)] transition-colors flex items-center gap-1.5"
7731
+ >
7732
+ {data.ghostCta.label}
7733
+ <Terminal className="w-4 h-4" />
7734
+ </a>
7735
+ </div>
7736
+ )}
7706
7737
  </div>
7707
7738
  </div>
7708
7739
 
7709
7740
  {/* Right: branded product photo */}
7710
- <div className="hidden md:flex items-center justify-center">
7711
- <div className="relative w-full max-w-lg">
7712
- <div className="absolute inset-[-8%] bg-[radial-gradient(circle_at_50%_50%,rgba(52,109,255,0.22),rgba(12,17,22,0)_70%)] blur-2xl" />
7713
- <div className="relative aspect-[1/1.03] overflow-hidden rounded-none border border-white/14 bg-[#0d1219] shadow-[0_22px_56px_rgba(4,8,20,0.42)]">
7741
+ <div className="hidden md:flex items-center justify-center pointer-events-none">
7742
+ <div className="relative w-full max-w-lg pointer-events-auto">
7743
+ <div className="absolute inset-[-8%] bg-[radial-gradient(circle_at_50%_50%,rgba(52,109,255,0.22),rgba(12,17,22,0)_70%)] blur-2xl pointer-events-none" />
7744
+ <div className="relative aspect-[1/1.03] overflow-hidden rounded-none border border-white/14 bg-[#0d1219] shadow-[0_22px_56px_rgba(4,8,20,0.42)] pointer-events-auto">
7714
7745
  <img
7715
- src={heroPlugImage}
7716
- alt="Olon interface port engraved into a dark stone surface"
7746
+ src={imageUrl}
7747
+ alt={data.image?.alt ?? "Olon interface port engraved into a dark stone surface"}
7717
7748
  className="absolute inset-0 h-full w-full object-cover"
7718
7749
  style={{ objectPosition: '50% 50%' }}
7750
+ data-jp-field="image"
7719
7751
  />
7720
- <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(7,11,21,0.02)_0%,rgba(7,11,21,0.14)_24%,rgba(7,11,21,0.44)_100%)]" />
7721
- <div className="absolute inset-0 bg-[radial-gradient(circle_at_62%_44%,rgba(122,163,255,0.18),rgba(29,78,216,0.08)_24%,rgba(12,17,22,0)_54%)] mix-blend-screen" />
7752
+ <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(7,11,21,0.02)_0%,rgba(7,11,21,0.14)_24%,rgba(7,11,21,0.44)_100%)] pointer-events-none" />
7753
+ <div className="absolute inset-0 bg-[radial-gradient(circle_at_62%_44%,rgba(122,163,255,0.18),rgba(29,78,216,0.08)_24%,rgba(12,17,22,0)_54%)] mix-blend-screen pointer-events-none" />
7722
7754
  </div>
7723
7755
  </div>
7724
7756
  </div>
@@ -7727,117 +7759,6 @@ export function OlonHeroView({ data }: Props) {
7727
7759
  );
7728
7760
  }
7729
7761
 
7730
- END_OF_FILE_CONTENT
7731
- echo "Creating src/components/olon-hero/View_.tsx..."
7732
- cat << 'END_OF_FILE_CONTENT' > "src/components/olon-hero/View_.tsx"
7733
- import type { OlonHeroData } from './types';
7734
- import { Button } from '@/components/ui/button';
7735
-
7736
- interface Props {
7737
- data: OlonHeroData;
7738
- }
7739
-
7740
- export function OlonHeroView({ data }: Props) {
7741
- return (
7742
- <section
7743
- style={{
7744
- '--local-bg': 'var(--background)',
7745
- '--local-fg': 'var(--foreground)',
7746
- '--local-muted': 'var(--muted-foreground)',
7747
- '--local-primary': 'var(--primary)',
7748
- '--local-p300': 'var(--primary-light)',
7749
- } as React.CSSProperties}
7750
- className="min-h-screen bg-[var(--local-bg)] text-[var(--local-fg)] pt-36 pb-24"
7751
- data-jp-section-id={data.id}
7752
- data-jp-section-type="olon-hero"
7753
- >
7754
- <div className="max-w-6xl mx-auto px-8 grid grid-cols-1 md:grid-cols-2 gap-16 items-center">
7755
- {/* Left: copy */}
7756
- <div className="flex flex-col gap-6">
7757
- <p className="text-xs font-semibold tracking-[0.12em] uppercase text-[var(--local-muted)]"
7758
- data-jp-field="eyebrow">
7759
- {data.eyebrow}
7760
- </p>
7761
-
7762
- <div>
7763
- <h1 className="text-5xl md:text-6xl font-bold tracking-[-0.03em] leading-[1.05] text-foreground"
7764
- data-jp-field="headline">
7765
- {data.headline}
7766
- </h1>
7767
- <p className="text-4xl md:text-5xl font-semibold tracking-[-0.03em] leading-[1.1] text-[var(--local-p300)] italic mt-1"
7768
- data-jp-field="subline">
7769
- {data.subline}
7770
- </p>
7771
- </div>
7772
-
7773
- <p className="text-base text-[var(--local-muted)] leading-relaxed max-w-lg"
7774
- data-jp-field="body">
7775
- {data.body}
7776
- </p>
7777
-
7778
- <div className="flex flex-wrap gap-3 items-center">
7779
- <Button asChild size="lg" className="font-semibold">
7780
- <a href={data.cta.primary.href}>{data.cta.primary.label} →</a>
7781
- </Button>
7782
- <Button asChild variant="outline" size="lg" className="font-semibold">
7783
- <a href={data.cta.secondary.href}>{data.cta.secondary.label}</a>
7784
- </Button>
7785
- <a href={data.cta.ghost.href}
7786
- className="text-sm text-[var(--local-muted)] hover:text-[var(--local-fg)] transition-colors flex items-center gap-1.5">
7787
- {data.cta.ghost.label}
7788
- </a>
7789
- </div>
7790
- </div>
7791
-
7792
- {/* Right: SVG illustration */}
7793
- <div className="hidden md:flex items-center justify-center">
7794
- <svg viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full max-w-md">
7795
- <defs>
7796
- <linearGradient id="hero-main" x1="0" y1="0" x2="1" y2="1">
7797
- <stop offset="0%" stopColor="#84ABFF"/>
7798
- <stop offset="60%" stopColor="#1763FF"/>
7799
- <stop offset="100%" stopColor="#0F52E0"/>
7800
- </linearGradient>
7801
- <linearGradient id="hero-accent" x1="0" y1="0" x2="0" y2="1">
7802
- <stop offset="0%" stopColor="#EEF3FF"/>
7803
- <stop offset="100%" stopColor="#84ABFF"/>
7804
- </linearGradient>
7805
- <linearGradient id="hero-glow" x1="0" y1="0" x2="0" y2="1">
7806
- <stop offset="0%" stopColor="#1763FF" stopOpacity="0.3"/>
7807
- <stop offset="100%" stopColor="#1763FF" stopOpacity="0"/>
7808
- </linearGradient>
7809
- <filter id="glow"><feGaussianBlur stdDeviation="8" result="blur"/><feComposite in="SourceGraphic" in2="blur" operator="over"/></filter>
7810
- </defs>
7811
- <circle cx="200" cy="200" r="160" fill="url(#hero-glow)" opacity="0.4"/>
7812
- <rect x="90" y="90" width="220" height="220" rx="28" fill="none" stroke="url(#hero-main)" strokeWidth="14"/>
7813
- <line x1="16" y1="148" x2="90" y2="148" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7814
- <line x1="16" y1="200" x2="90" y2="200" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7815
- <line x1="16" y1="252" x2="90" y2="252" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7816
- <line x1="310" y1="148" x2="384" y2="148" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7817
- <line x1="310" y1="200" x2="384" y2="200" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7818
- <line x1="310" y1="252" x2="384" y2="252" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7819
- <line x1="148" y1="16" x2="148" y2="90" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7820
- <line x1="200" y1="16" x2="200" y2="90" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7821
- <line x1="252" y1="16" x2="252" y2="90" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7822
- <line x1="148" y1="310" x2="148" y2="384" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7823
- <line x1="200" y1="310" x2="200" y2="384" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7824
- <line x1="252" y1="310" x2="252" y2="384" stroke="url(#hero-main)" strokeWidth="10" strokeLinecap="round"/>
7825
- <circle cx="148" cy="148" r="13" fill="url(#hero-main)"/>
7826
- <circle cx="252" cy="148" r="13" fill="url(#hero-main)"/>
7827
- <circle cx="148" cy="252" r="13" fill="url(#hero-main)"/>
7828
- <circle cx="252" cy="252" r="13" fill="url(#hero-main)"/>
7829
- <line x1="148" y1="148" x2="200" y2="200" stroke="#84ABFF" strokeWidth="2.5" opacity="0.35"/>
7830
- <line x1="252" y1="148" x2="200" y2="200" stroke="#84ABFF" strokeWidth="2.5" opacity="0.35"/>
7831
- <line x1="148" y1="252" x2="200" y2="200" stroke="#84ABFF" strokeWidth="2.5" opacity="0.35"/>
7832
- <line x1="252" y1="252" x2="200" y2="200" stroke="#84ABFF" strokeWidth="2.5" opacity="0.35"/>
7833
- <circle cx="200" cy="200" r="18" fill="url(#hero-accent)" filter="url(#glow)"/>
7834
- </svg>
7835
- </div>
7836
- </div>
7837
- </section>
7838
- );
7839
- }
7840
-
7841
7762
  END_OF_FILE_CONTENT
7842
7763
  echo "Creating src/components/olon-hero/index.ts..."
7843
7764
  cat << 'END_OF_FILE_CONTENT' > "src/components/olon-hero/index.ts"
@@ -7849,17 +7770,19 @@ END_OF_FILE_CONTENT
7849
7770
  echo "Creating src/components/olon-hero/schema.ts..."
7850
7771
  cat << 'END_OF_FILE_CONTENT' > "src/components/olon-hero/schema.ts"
7851
7772
  import { z } from 'zod';
7852
- import { BaseSectionData } from '@/lib/base-schemas';
7773
+ import { BaseSectionData, CtaSchema, ImageSelectionSchema } from '@/lib/base-schemas';
7853
7774
 
7854
7775
  export const OlonHeroSchema = BaseSectionData.extend({
7855
7776
  eyebrow: z.string().default('CONTRACT LAYER · V1.5 · OPEN CORE'),
7856
7777
  headline: z.string().default('Contract Layer'),
7857
7778
  subline: z.string().default('for the agentic web.'),
7858
7779
  body: z.string().default(''),
7859
- cta: z.object({
7860
- primary: z.object({ label: z.string(), href: z.string() }),
7861
- secondary: z.object({ label: z.string(), href: z.string() }),
7862
- ghost: z.object({ label: z.string(), href: z.string() }),
7780
+ primaryCta: CtaSchema.optional(),
7781
+ secondaryCta: CtaSchema.optional(),
7782
+ ghostCta: CtaSchema.optional(),
7783
+ image: ImageSelectionSchema.default({
7784
+ url: '/assets/images/plug-graded-square.jpg',
7785
+ alt: 'Olon interface port engraved into a dark stone surface'
7863
7786
  }),
7864
7787
  });
7865
7788
 
@@ -7944,13 +7867,13 @@ export function OlonWhyView({ data }: Props) {
7944
7867
  <p className="text-base text-[var(--local-muted)] leading-relaxed max-w-2xl mb-12"
7945
7868
  data-jp-field="body">{data.body}</p>
7946
7869
 
7947
- <div className="grid grid-cols-1 md:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden"
7948
- data-jp-array="pillars">
7870
+ <div className="grid grid-cols-1 md:grid-cols-3 border border-[var(--local-border)] rounded-2xl overflow-hidden">
7949
7871
  {data.pillars.map((pillar) => (
7950
7872
  <div key={pillar.id}
7951
7873
  className="bg-[var(--local-card)] p-8 flex flex-col gap-4 border-r last:border-r-0 border-[var(--local-border)]"
7952
- data-jp-item-id={pillar.id}>
7953
- <div>{ICONS[pillar.icon]}</div>
7874
+ data-jp-item-id={pillar.id}
7875
+ data-jp-item-field="pillars">
7876
+ <div data-jp-field="icon">{ICONS[pillar.icon]}</div>
7954
7877
  <div className="font-bold text-foreground" data-jp-field="title">{pillar.title}</div>
7955
7878
  <div className="text-sm text-[var(--local-muted)] leading-relaxed" data-jp-field="body">{pillar.body}</div>
7956
7879
  </div>
@@ -11586,13 +11509,12 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/docs.json"
11586
11509
  "id": "docs-main",
11587
11510
  "type": "tiptap",
11588
11511
  "data": {
11589
- "content": "# 📐 OlonJS Architecture Specifications v1.5\n\n**Status:** Mandatory Standard\\\n**Version:** 1.5.0 (Sovereign Core Edition — Architecture + Studio/ICE UX, Path-Deterministic Nested Editing, Deterministic Local Design Tokens, Three-Layer CSS Bridge Contract)\\\n**Target:** Senior Architects / AI Agents / Enterprise Governance\n\nThis tenant follows the current OlonJS source-of-truth model: the tenant app owns content, schemas, theme, and persistence wiring; `@olonjs/core` owns the Studio shell, routing, preview, and editing engine.\n\n---\n\n## Canonical Editorial Flows\n\nThe supported Studio flows are now:\n\n- `SSG` for static HTML and route output.\n- `Save to file` for local JSON persistence back into tenant source files.\n- `Hot Save` for cloud/editorial persistence when the tenant config provides it.\n- `Add Section` for deterministic section lifecycle management inside Studio.\n\nPrevious one-off bake and JSON export paths are no longer part of Studio.\n\n---\n\n## Persistence Model\n\n`@olonjs/core` no longer performs HTML bake or ZIP export. Studio now invokes tenant-provided persistence callbacks:\n\n- `saveToFile(state, slug)`\n- `hotSave(state, slug)`\n\nThis keeps persistence explicit, tenant-owned, and aligned with the current `JsonPagesConfig` contract.\n\n---\n\n## Tenant Source Of Truth\n\n`apps/tenant-alpha` is the DNA source of truth for this tenant. Generated CLI templates are downstream artifacts and should be regenerated from source apps instead of being edited manually.\n\nThe canonical content and design files remain:\n\n- `src/data/config/site.json`\n- `src/data/config/menu.json`\n- `src/data/config/theme.json`\n- `src/data/pages/<slug>.json`\n\n---\n\n## Reference Specs\n\nUse these monorepo sources for the full protocol and architecture details:\n\n- `specs/olonjsSpecs_V_1_5.md`\n- `apps/tenant-alpha/specs/olonjsSpecs_V.1.3.md`\n\nThese source specs are the maintained references for architecture, Studio behavior, and tenant compliance."
11512
+ "content": "# 📐 OlonJS Architecture Specifications v1.5\n\n**Status:** Mandatory Standard\\\n**Version:** 1.5.0 (Sovereign Core Edition — Architecture + Studio/ICE UX, Path-Deterministic Nested Editing, Deterministic Local Design Tokens, Three-Layer CSS Bridge Contract)\\\n**Target:** Senior Architects / AI Agents / Enterprise Governance\n\n**Scope v1.5:** This edition preserves the complete v1.3 architecture (MTRP, JSP, TBP, CIP, ECIP, JAP + Studio/ICE UX contract: IDAC, TOCC, BSDS, ASC, JEB + Tenant Type & Code-Generation Annex + strict path-based/nested-array behavior) as a **faithful superset**, and upgrades **Local Design Tokens** from a principle to a deterministic implementation contract.\\\n**Scope note (breaking):** In strict v1.3+ Studio semantics, the legacy flat protocol (`itemField` / `itemId`) is removed in favor of `itemPath` (root-to-leaf path segments).\\\n**Scope note (clarification):** In v1.5, `theme.json` is the tenant theme source of truth for themed tenants; runtime theme publication is mandatory for compliant themed tenants; section-local tokens (`--local-*`) are the required scoping layer for section-owned color and radius concerns.\n\n---\n\n## 1. 📐 Modular Type Registry Pattern (MTRP) v1.2\n\n**Objective:** Establish a strictly typed, open-ended protocol for extending content data structures where the **Core Engine** is the orchestrator and the **Tenant** is the provider.\n\n### 1.1 The Sovereign Dependency Inversion\n\nThe **Core** defines the empty `SectionDataRegistry`. The **Tenant** \"injects\" its specific definitions using **Module Augmentation**. This allows the Core to be distributed as a compiled NPM package while remaining aware of Tenant-specific types at compile-time.\n\n### 1.2 Technical Implementation (`@olonjs/core/kernel`)\n\n```typescript\nexport interface SectionDataRegistry {} // Augmented by Tenant\nexport interface SectionSettingsRegistry {} // Augmented by Tenant\n\nexport interface BaseSection<K extends keyof SectionDataRegistry> {\n id: string;\n type: K;\n data: SectionDataRegistry[K];\n settings?: K extends keyof SectionSettingsRegistry\n ? SectionSettingsRegistry[K]\n : BaseSectionSettings;\n}\n\nexport type Section = {\n [K in keyof SectionDataRegistry]: BaseSection<K>\n}[keyof SectionDataRegistry];\n```\n\n**SectionType:** Core exports (or Tenant infers) `SectionType` as `keyof SectionDataRegistry`. After Tenant module augmentation, this is the union of all section type keys (e.g. `'header' | 'footer' | 'hero' | ...`). The Tenant uses this type for the ComponentRegistry and SECTION_SCHEMAS keys.\n\n**Perché servono:** Il Core deve poter renderizzare section senza conoscere i tipi concreti a compile-time; il Tenant deve poter aggiungere nuovi tipi senza modificare il Core. I registry vuoti + module augmentation permettono di distribuire Core come pacchetto NPM e mantenere type-safety end-to-end (Section, registry, config). Senza MTRP, ogni nuovo tipo richiederebbe cambi nel Core o tipi deboli (`any`).\n\n---\n\n## 2. 📐 JsonPages Site Protocol (JSP) v1.8\n\n**Objective:** Define the deterministic file system and the **Sovereign Projection Engine** (CLI).\n\n### 2.1 The File System Ontology (The Silo Contract)\n\nEvery site must reside in an isolated directory. Global Governance is physically separated from Local Content.\n\n- `/config/site.json` — Global Identity & Reserved System Blocks (Header/Footer). See Appendix A for typed shape.\n- `/config/menu.json` — Navigation Tree (SSOT for System Header). See Appendix A.\n- `/config/theme.json` — Theme tokens for themed tenants. See Appendix A.\n- `/pages/[slug].json` — Local Body Content per page. See Appendix A (PageConfig).\n\n**Application path convention:** The runtime app typically imports these via an alias (e.g. `@/data/config/` and `@/data/pages/`). The physical silo may be `src/data/config/` and `src/data/pages/` so that `site.json`, `menu.json`, `theme.json` live under `src/data/config/`, and page JSONs under `src/data/pages/`. The CLI or projection script may use `/config/` and `/pages/` at repo root; the **contract** is that the app receives **siteConfig**, **menuConfig**, **themeConfig**, **pages**, and optional **refDocuments** as defined in JEB (§10) and Appendix A. In v1.5 runtime behavior, menu data may be resolved from the JSON graph via `$ref` (e.g. `menu.json#/main`, `../config/menu.json#/main`) and is not limited to a single `menuConfig.main` source.\n\n**Rule:** For a tenant that claims v1.5 design-token compliance, `theme.json` is not optional in practice. If a tenant omits a physical `theme.json`, it must still provide an equivalent `ThemeConfig` object before bootstrap; otherwise the tenant is outside full v1.5 theme compliance.\n\n### 2.2 Deterministic Projection (CLI Workflow)\n\nThe CLI (`@olonjs/cli`) creates new tenants by:\n\n1. **Infra Projection:** Generating `package.json`, `tsconfig.json`, and `vite.config.ts` (The Shell).\n2. **Source Projection:** Executing a deterministic script (`src_tenant_alpha.sh`) to reconstruct the `src` folder (The DNA).\n3. **Dependency Resolution:** Enforcing specific versions of React, Radix, and Tailwind v4.\n\n**Perché servono:** Una struttura file deterministica (config vs pages) separa governance globale (site, menu, theme) dal contenuto per pagina; il CLI può rigenerare tenant e tooling può trovare dati e schemi sempre negli stessi path. Senza JSP, ogni tenant sarebbe una struttura ad hoc e ingestione/export/Bake sarebbero fragili.\n\n---\n\n## 3. 🧱 Tenant Block Protocol (TBP) v1.0\n\n**Objective:** Standardize the \"Capsule\" structure for components to enable automated ingestion (Pull) by the SaaS.\n\n### 3.1 The Atomic Capsule Structure\n\nComponents are self-contained directories under `src/components/<sectionType>/`:\n\n- `View.tsx` — The pure React component (Dumb View). Props: see Appendix A (SectionComponentPropsMap).\n- `schema.ts` — Zod schema(s) for the **data** contract (and optionally **settings**). Exports at least one schema (e.g. `HeroSchema`) used as the **data** schema for that type. Must extend BaseSectionData (§8) for data; array items must extend BaseArrayItem (§8).\n- `types.ts` — TypeScript interfaces inferred from the schema (e.g. `HeroData`, `HeroSettings`). Export types with names `<SectionType>Data` and `<SectionType>Settings` (or equivalent) so the Tenant can aggregate them in a single types module.\n- `index.ts` — Public API: re-exports View, schema(s), and types.\n\n### 3.2 Reserved System Types\n\n- `type: 'header'` — Reserved for `site.json`. Receives `menu: MenuItem[]` in addition to `data` and `settings`. Menu is sourced from `menu.json` (see Appendix A). The Tenant **must** type `SectionComponentPropsMap['header']` as `{ data: HeaderData; settings?: HeaderSettings; menu: MenuItem[] }`.\n- `type: 'footer'` — Reserved for `site.json`. Props: `{ data: FooterData; settings?: FooterSettings }` only (no `menu`).\n- `type: 'sectionHeader'` — A standard local block. Must define its own `links` array in its local schema if used.\n\n**Perché servono:** La capsula (View + schema + types + index) è l’unità di estensione: il Core e il Form Factory possono scoprire tipi e contratti per tipo senza convenzioni ad hoc. Header/footer riservati evitano conflitti tra globale e locale. Senza TBP, aggregazione di SECTION_SCHEMAS e registry sarebbe incoerente e l’ingestion da SaaS non sarebbe automatizzabile.\n\n---\n\n## 4. 🧱 Component Implementation Protocol (CIP) v1.6\n\n**Objective:** Ensure system-wide stability and Admin UI integrity.\n\n1. **The \"Sovereign View\" Law:** Components receive `data` and `settings` (and `menu` for header only) and return JSX. They are metadata-blind (never import Zod schemas).\n2. **Z-Index Neutrality:** Components must not use `z-index > 1`. Layout delegation (sticky/fixed) is managed by the `SectionRenderer`.\n3. **Agnostic Asset Protocol:** Use `resolveAssetUrl(path, tenantId)` for all media. Resolved URLs are under `/assets/...` with no tenantId segment in the path (e.g. relative `img/hero.jpg` → `/assets/img/hero.jpg`).\n\n### 4.4 Local Design Tokens (v1.5)\n\n**Objective:** Standardize how a section consumes tenant theme values without leaking global styling assumptions into the section implementation.\n\n#### 4.4.1 The Required Four-Layer Chain\n\nFor any section that controls background, text color, border color, accent color, or radii, the following chain is normative:\n\n1. **Tenant theme source of truth** — Values are declared in `src/data/config/theme.json`.\n2. **Runtime theme publication** — The Core and/or tenant bootstrap **must** publish those values as CSS custom properties.\n3. **Section-local scope** — The View root **must** define `--local-*` variables mapped to the published theme variables for the concerns the section owns.\n4. **Rendered classes** — Section-owned color/radius utilities **must** consume `var(--local-*)`.\n\n**Rule:** A section may not skip layer 3 when it visually owns those concerns. Directly using global theme variables throughout the JSX is non-canonical for a fully themed section and must be treated as non-compliant unless the usage falls under an explicitly allowed exception.\n\n#### 4.4.2 Source Of Truth: `theme.json`\n\n`theme.json` is the tenant-level source of truth for theme values. Example:\n\n```json\n{\n \"name\": \"JsonPages Landing\",\n \"tokens\": {\n \"colors\": {\n \"primary\": \"#3b82f6\",\n \"secondary\": \"#22d3ee\",\n \"accent\": \"#60a5fa\",\n \"background\": \"#060d1b\",\n \"surface\": \"#0b1529\",\n \"surfaceAlt\": \"#101e38\",\n \"text\": \"#e2e8f0\",\n \"textMuted\": \"#94a3b8\",\n \"border\": \"#162a4d\"\n },\n \"typography\": {\n \"fontFamily\": {\n \"primary\": \"'Instrument Sans', system-ui, sans-serif\",\n \"mono\": \"'JetBrains Mono', monospace\",\n \"display\": \"'Bricolage Grotesque', system-ui, sans-serif\"\n }\n },\n \"borderRadius\": {\n \"sm\": \"0px\",\n \"md\": \"0px\",\n \"lg\": \"2px\"\n }\n }\n}\n```\n\n**Rule:** For a themed tenant, `theme.json` must contain the canonical semantic keys defined in Appendix A. Extra brand-specific keys are allowed only as extensions to those canonical groups, not as replacements for them.\n\n#### 4.4.3 Runtime Theme Publication\n\nThe tenant and/or Core **must** expose theme values as CSS variables before section rendering. The compliant bridge is a **three-layer chain** implemented in the tenant's `index.css`. Runtime publication is mandatory for themed tenants.\n\n##### Layer architecture\n\n```\ntheme.json → engine injection → :root bridge → @theme (Tailwind) → JSX classes\n```\n\n**Layer 0 — Engine injection (Core-provided)** `@olonjs/core` reads `theme.json` and injects all token values as flattened CSS custom properties before section rendering. The naming convention is:\n\nJSON path Injected CSS var `tokens.colors.{name}` `--theme-colors-{name}` `tokens.typography.fontFamily.{role}` `--theme-font-{role}` `tokens.typography.scale.{step}` `--theme-typography-scale-{step}` `tokens.typography.tracking.{name}` `--theme-typography-tracking-{name}` `tokens.typography.leading.{name}` `--theme-typography-leading-{name}` `tokens.typography.wordmark.*` `--theme-typography-wordmark-*` `tokens.borderRadius.{name}` `--theme-border-radius-{name}` `tokens.spacing.{name}` `--theme-spacing-{name}` `tokens.zIndex.{name}` `--theme-z-index-{name}` `tokens.modes.{mode}.colors.{name}` `--theme-modes-{mode}-colors-{name}`\n\nThe engine also publishes shorthand aliases for the most common radius and font tokens (e.g. `--theme-radius-sm`, `--theme-font-primary`). Tokens not covered by the shorthand aliases must be bridged in the tenant `:root`.\n\n**Layer 1 —** `:root` **semantic bridge (Tenant-provided,** `index.css`**)** The tenant maps engine-injected vars to its own semantic naming. **The naming in this layer is the tenant's sovereign choice** — it is not imposed by the Core. Any naming convention is valid as long as it is consistent throughout the tenant.\n\n```css\n:root {\n /* Backgrounds */\n --background: var(--theme-colors-background);\n --card: var(--theme-colors-card);\n --elevated: var(--theme-colors-elevated);\n --overlay: var(--theme-colors-overlay);\n --popover: var(--theme-colors-popover);\n --popover-foreground: var(--theme-colors-popover-foreground);\n\n /* Foregrounds */\n --foreground: var(--theme-colors-foreground);\n --card-foreground: var(--theme-colors-card-foreground);\n --muted-foreground: var(--theme-colors-muted-foreground);\n --placeholder: var(--theme-colors-placeholder);\n\n /* Brand ramp */\n --primary: var(--theme-colors-primary);\n --primary-foreground: var(--theme-colors-primary-foreground);\n --primary-light: var(--theme-colors-primary-light);\n --primary-dark: var(--theme-colors-primary-dark);\n /* ... full ramp --primary-50 through --primary-900 ... */\n\n /* Accent, secondary, muted, border, input, ring */\n --accent: var(--theme-colors-accent);\n --accent-foreground: var(--theme-colors-accent-foreground);\n --secondary: var(--theme-colors-secondary);\n --secondary-foreground: var(--theme-colors-secondary-foreground);\n --muted: var(--theme-colors-muted);\n --border: var(--theme-colors-border);\n --border-strong: var(--theme-colors-border-strong);\n --input: var(--theme-colors-input);\n --ring: var(--theme-colors-ring);\n\n /* Feedback */\n --destructive: var(--theme-colors-destructive);\n --destructive-foreground: var(--theme-colors-destructive-foreground);\n --success: var(--theme-colors-success);\n --success-foreground: var(--theme-colors-success-foreground);\n --warning: var(--theme-colors-warning);\n --warning-foreground: var(--theme-colors-warning-foreground);\n --info: var(--theme-colors-info);\n --info-foreground: var(--theme-colors-info-foreground);\n\n /* Typography scale, tracking, leading */\n --theme-text-xs: var(--theme-typography-scale-xs);\n --theme-text-sm: var(--theme-typography-scale-sm);\n /* ... full scale ... */\n --theme-tracking-tight: var(--theme-typography-tracking-tight);\n --theme-leading-normal: var(--theme-typography-leading-normal);\n /* ... */\n\n /* Spacing */\n --theme-container-max: var(--theme-spacing-container-max);\n --theme-section-y: var(--theme-spacing-section-y);\n --theme-header-h: var(--theme-spacing-header-h);\n --theme-sidebar-w: var(--theme-spacing-sidebar-w);\n\n /* Z-index */\n --z-base: var(--theme-z-index-base);\n --z-elevated: var(--theme-z-index-elevated);\n --z-dropdown: var(--theme-z-index-dropdown);\n --z-sticky: var(--theme-z-index-sticky);\n --z-overlay: var(--theme-z-index-overlay);\n --z-modal: var(--theme-z-index-modal);\n --z-toast: var(--theme-z-index-toast);\n}\n```\n\n**Layer 2 —** `@theme` **Tailwind v4 bridge (Tenant-provided,** `index.css`**)** Every semantic variable from Layer 1 is re-exposed under the Tailwind v4 `@theme` namespace so it becomes a utility class. Pattern: `--color-{slug}: var(--{slug})`.\n\n```css\n@theme {\n --color-background: var(--background);\n --color-card: var(--card);\n --color-foreground: var(--foreground);\n --color-primary: var(--primary);\n --color-accent: var(--accent);\n --color-border: var(--border);\n /* ... full token set ... */\n\n --font-primary: var(--theme-font-primary);\n --font-mono: var(--theme-font-mono);\n --font-display: var(--theme-font-display);\n\n --radius-sm: var(--theme-radius-sm);\n --radius-md: var(--theme-radius-md);\n --radius-lg: var(--theme-radius-lg);\n --radius-xl: var(--theme-radius-xl);\n --radius-full: var(--theme-radius-full);\n}\n```\n\nAfter this bridge, the full Tailwind utility vocabulary (`bg-primary`, `text-foreground`, `rounded-lg`, `font-display`, etc.) resolves to live theme values — with no hardcoded hex anywhere in the React layer.\n\n**Light mode / additional modes** are bridged by overriding the Layer 1 semantic vars under a `[data-theme=\"light\"]` selector (or equivalent), pointing to the engine-injected mode vars (`--theme-modes-light-colors-*`). The `@theme` layer requires no changes.\n\n**Rule:** A tenant `index.css` must implement all three layers. Skipping Layer 2 breaks Tailwind utility resolution. Skipping Layer 1 couples sections to engine-internal naming. Hardcoding values in either layer is non-compliant.\n\n#### 4.4.4 Section-Local Scope\n\nIf a section controls its own visual language, it **shall** establish a local token scope on the section root. Example:\n\n```tsx\n<section\n style={{\n '--local-bg': 'var(--background)',\n '--local-text': 'var(--foreground)',\n '--local-text-muted': 'var(--muted-foreground)',\n '--local-primary': 'var(--primary)',\n '--local-border': 'var(--border)',\n '--local-surface': 'var(--card)',\n '--local-radius-sm': 'var(--theme-radius-sm)',\n '--local-radius-md': 'var(--theme-radius-md)',\n '--local-radius-lg': 'var(--theme-radius-lg)',\n } as React.CSSProperties}\n>\n```\n\n**Rule:** `--local-*` values must map to published theme variables. They must **not** be defined as hardcoded brand values such as `#fff`, `#111827`, `12px`, or `Inter, sans-serif` if those values belong to the tenant theme layer.\n\n**Rule:** Local tokens are **mandatory** for section-owned color and radius concerns. They are **optional** for font-family concerns unless the section must remap or isolate font roles locally.\n\n#### 4.4.5 Canonical Typography Rule\n\nTypography follows a deterministic rule distinct from color/radius:\n\n1. **Canonical font publication** — Tenant/Core must publish semantic font variables such as `--theme-font-primary`, `--theme-font-mono`, and `--theme-font-display` when those roles exist in the theme.\n2. **Canonical font consumption** — Sections must consume typography through semantic tenant font utilities or variables backed by those published theme roles (for example `.font-display` backed by `--font-display`, itself backed by `--theme-font-display`).\n3. **Local font tokens** — `--local-font-*` is optional and should be used only when a section needs to remap a font role locally rather than simply consume the canonical tenant font role.\n\nExample of canonical global semantic bridge:\n\n```css\n:root {\n --font-primary: var(--theme-font-primary);\n --font-display: var(--theme-font-display);\n}\n\n.font-display {\n font-family: var(--font-display, var(--font-primary));\n}\n```\n\n**Rule:** A section is compliant if it consumes themed fonts through this published semantic chain. It is **not** required to define `--local-font-display` unless the section needs local remapping. This closes the ambiguity between global semantic typography utilities and local color/radius scoping.\n\n#### 4.4.6 View Consumption\n\nAll section-owned classes that affect color or radius must consume local variables. Font consumption must follow the typography rule above. Example:\n\n```tsx\n<section\n style={{\n '--local-bg': 'var(--background)',\n '--local-text': 'var(--foreground)',\n '--local-primary': 'var(--primary)',\n '--local-border': 'var(--border)',\n '--local-radius-md': 'var(--theme-radius-md)',\n '--local-radius-lg': 'var(--theme-radius-lg)',\n } as React.CSSProperties}\n className=\"bg-[var(--local-bg)]\"\n>\n <h1 className=\"font-display text-[var(--local-text)]\">Build Tenant DNA</h1>\n\n <a className=\"bg-[var(--local-primary)] rounded-[var(--local-radius-md)] text-white\">\n Read the Docs\n </a>\n\n <div className=\"border border-[var(--local-border)] rounded-[var(--local-radius-lg)]\">\n {/* illustration / mockup / card */}\n </div>\n</section>\n```\n\n#### 4.4.7 Compliance Rules\n\nA section is compliant when all of the following are true:\n\n1. `theme.json` is the source of truth for the theme values being used.\n2. Those values are published at runtime as CSS custom properties before the section renders.\n3. The section root defines a local token scope for the color/radius concerns it controls.\n4. Local color/radius tokens map to published theme variables rather than hardcoded literals.\n5. JSX classes use `var(--local-*)` for section-owned color/radius concerns.\n6. Fonts are consumed through the published semantic font chain, and only use local font tokens when local remapping is required.\n7. Hardcoded colors/radii are absent from the primary visual contract of the section.\n\n#### 4.4.8 Allowed Exceptions\n\nThe following are acceptable if documented and intentionally limited:\n\n- Tiny decorative one-off values that are not part of the tenant theme contract (for example an isolated translucent pixel-grid overlay).\n- Temporary compatibility shims during migration, provided the section still exposes a clear compliant path and the literal is not the primary themed value.\n- Semantic alias bridges in tenant CSS (for example `--font-display: var(--theme-font-display)`), as long as the source remains the theme layer.\n\n#### 4.4.9 Non-Compliant Patterns\n\nThe following are non-compliant:\n\n- `style={{ '--local-bg': '#060d1b' }}` when that background belongs to tenant theme.\n- Buttons using `rounded-[7px]`, `bg-blue-500`, `text-zinc-100`, or similar hardcoded utilities inside a section that claims to be theme-driven.\n- A section root that defines `--local-*`, but child elements still use raw `bg-*`, `text-*`, or `rounded-*` utilities for the same owned concerns.\n- Reading `theme.json` directly inside a View instead of consuming published runtime theme variables.\n- Treating brand-specific extension keys as a replacement for canonical semantic keys such as `primary`, `background`, `text`, `border`, or `fontFamily.primary`.\n\n#### 4.4.10 Practical Interpretation\n\n`--local-*` is not the source of truth. It is the **local scoping layer** between tenant theme and section implementation.\n\nCanonical chain:\n\n`theme.json` → published runtime theme vars → section `--local-*` → JSX classes\\`\n\nCanonical font chain:\n\n`theme.json` → published semantic font vars → tenant font utility/variable → section typography\\`\n\n### 4.5 Z-Index & Overlay Governance (v1.2)\n\nSection content root **must** stay at `z-index` **≤ 1** (prefer `z-0`) so the Sovereign Overlay can sit above with high z-index in Tenant CSS (§7). Header/footer may use a higher z-index (e.g. 50) only as a documented exception for global chrome.\n\n**Perché servono (CIP):** View “dumb” (solo data/settings) e senza import di Zod evita accoppiamento e permette al Form Factory di essere l’unica fonte di verità sugli schemi. Z-index basso evita che il contenuto copra l’overlay di selezione in Studio. Asset via `resolveAssetUrl`: i path relativi vengono risolti in `/assets/...` (senza segmento tenantId nel path). In v1.5 la catena `theme.json -> runtime vars -> --local-* -> JSX classes` rende i tenant temabili, riproducibili e compatibili con la Studio UX; senza questa separazione, stili “nudi” o valori hardcoded creano drift visivo, rompono il contratto del brand, e rendono ambiguo ciò che appartiene al tema contro ciò che appartiene alla section.\n\n---\n\n## 5. 🛠️ Editor Component Implementation Protocol (ECIP) v1.5\n\n**Objective:** Standardize the Polymorphic ICE engine.\n\n1. **Recursive Form Factory:** The Admin UI builds forms by traversing the Zod ontology.\n2. **UI Metadata:** Use `.describe('ui:[widget]')` in schemas to pass instructions to the Form Factory.\n3. **Deterministic IDs:** Every object in a `ZodArray` must extend `BaseArrayItem` (containing an `id`) to ensure React reconciliation stability during reordering.\n\n### 5.4 UI Metadata Vocabulary (v1.2)\n\nStandard keys for the Form Factory:\n\nKey Use case `ui:text` Single-line text input. `ui:textarea` Multi-line text. `ui:select` Enum / single choice. `ui:number` Numeric input. `ui:list` Array of items; list editor (add/remove/reorder). `ui:icon-picker` Icon selection.\n\nUnknown keys may be treated as `ui:text`. Array fields must use `BaseArrayItem` for items.\n\n### 5.5 Path-Only Nested Selection & Expansion (v1.3, breaking)\n\nIn strict v1.3 Studio/Inspector behavior, nested editing targets are represented by **path segments from root to leaf**.\n\n```typescript\nexport type SelectionPathSegment = { fieldKey: string; itemId?: string };\nexport type SelectionPath = SelectionPathSegment[];\n```\n\nRules:\n\n- Expansion and focus for nested arrays **must** be computed from `SelectionPath` (root → leaf), not from a single flat pair.\n- Matching by `fieldKey` alone is non-compliant for nested structures.\n- Legacy flat payload fields `itemField` and `itemId` are removed in strict v1.3 selection protocol.\n\n**Perché servono (ECIP):** Il Form Factory deve sapere quale widget usare (text, textarea, select, list, …) senza hardcodare per tipo; `.describe('ui:...')` è il contratto. BaseArrayItem con `id` su ogni item di array garantisce chiavi stabili in React e reorder/delete corretti nell’Inspector. In v1.3 la selezione/espansione path-only elimina ambiguità su array annidati: senza path completo root→leaf, la sidebar può aprire il ramo sbagliato o non aprire il target.\n\n---\n\n## 6. 🎯 ICE Data Attribute Contract (IDAC) v1.1\n\n**Objective:** Mandatory data attributes so the Stage (iframe) and Inspector can bind selection and field/item editing without coupling to Tenant DOM.\n\n### 6.1 Section-Level Markup (Core-Provided)\n\n**SectionRenderer** (Core) wraps each section root with:\n\n- `data-section-id` — Section instance ID (e.g. UUID). On the wrapper that contains content + overlay.\n- Sibling overlay element `data-jp-section-overlay` — Selection ring and type label. **Tenant does not add this;** Core injects it.\n\nTenant Views render the **content** root only (e.g. `<section>` or `<div>`), placed **inside** the Core wrapper.\n\n### 6.2 Field-Level Binding (Tenant-Provided)\n\nFor every **editable scalar field** the View **must** attach `data-jp-field=\"<fieldKey>\"` (key matches schema path: e.g. `title`, `description`, `sectionTitle`, `label`).\n\n### 6.3 Array-Item Binding (Tenant-Provided)\n\nFor every **editable array item** the View **must** attach:\n\n- `data-jp-item-id=\"<stableId>\"` — Prefer `item.id`; fallback e.g. `legacy-${index}` only outside strict mode.\n- `data-jp-item-field=\"<arrayKey>\"` — e.g. `cards`, `layers`, `products`, `paragraphs`.\n\n### 6.4 Compliance\n\n**Reserved types** (`header`, `footer`): ICE attributes optional unless Studio edits them. **All other section types** in the Stage and in `SECTION_SCHEMAS` **must** implement §6.2 and §6.3 for every editable field and array item.\n\n### 6.5 Strict Path Extraction for Nested Arrays (v1.3, breaking)\n\nFor nested array targets, the Core/Inspector contract is path-based:\n\n- The runtime selection target is expressed as `itemPath: SelectionPath` (root → leaf).\n- Flat identity (`itemField` + `itemId`) is not sufficient for nested structures and is removed in strict v1.3 payloads.\n- In strict mode, index-based identity fallback is non-compliant for editable object arrays.\n\n**Perché servono (IDAC):** Lo Stage è in un iframe e l’Inspector deve sapere **quale campo o item** corrisponde al click (o alla selezione) senza conoscere la struttura DOM del Tenant. `data-jp-field` associa un nodo DOM al path dello schema (es. `title`, `description`): così il Core può evidenziare la riga giusta nella sidebar, applicare opacità attivo/inattivo e aprire il form sul campo corretto. `data-jp-item-id` e `data-jp-item-field` fanno lo stesso per gli item di array (liste, reorder, delete). In v1.3, `itemPath` rende deterministico anche il caso nested (array dentro array), eliminando mismatch tra selezione canvas e ramo aperto in sidebar.\n\n---\n\n## 7. 🎨 Tenant Overlay CSS Contract (TOCC) v1.0\n\n**Objective:** The Stage iframe loads only Tenant HTML/CSS. Core injects overlay **markup** but does **not** ship overlay styles. The Tenant **must** supply CSS so overlay is visible.\n\n### 7.1 Required Selectors (Tenant global CSS)\n\n1. `[data-jp-section-overlay]` — `position: absolute; inset: 0`; `pointer-events: none`; base state transparent.\n2. `[data-section-id]:hover [data-jp-section-overlay]` — Hover: e.g. dashed border, subtle tint.\n3. `[data-section-id][data-jp-selected] [data-jp-section-overlay]` — Selected: solid border, optional tint.\n4. `[data-jp-section-overlay] > div` (type label) — Position and visibility (e.g. visible on hover/selected).\n\n### 7.2 Z-Index\n\nOverlay **z-index** high (e.g. 9999). Section content at or below CIP limit (§4.5).\n\n### 7.3 Responsibility\n\n**Core:** Injects wrapper and overlay DOM; sets `data-jp-selected`. **Tenant:** All overlay **visual** rules.\n\n**Perché servono (TOCC):** L’iframe dello Stage carica solo HTML/CSS del Tenant; il Core inietta il markup dell’overlay ma non gli stili. Senza CSS Tenant per i selettori TOCC, bordo hover/selected e type label non sarebbero visibili: l’autore non vedrebbe quale section è selezionata né il label del tipo. TOCC chiarisce la responsabilità (Core = markup, Tenant = aspetto) e garantisce UX uniforme tra tenant.\n\n---\n\n## 8. 📦 Base Section Data & Settings (BSDS) v1.0\n\n**Objective:** Standardize base schema fragments for anchors, array items, and section settings.\n\n### 8.1 BaseSectionData\n\nEvery section data schema **must** extend a base with at least `anchorId` (optional string). Canonical Zod (Tenant `lib/base-schemas.ts` or equivalent):\n\n```typescript\nexport const BaseSectionData = z.object({\n anchorId: z.string().optional().describe('ui:text'),\n});\n```\n\n### 8.2 BaseArrayItem\n\nEvery array item schema editable in the Inspector **must** include `id` (optional string minimum). Canonical Zod:\n\n```typescript\nexport const BaseArrayItem = z.object({\n id: z.string().optional(),\n});\n```\n\nRecommended: required UUID for new items. Used by `data-jp-item-id` and React reconciliation.\n\n### 8.3 BaseSectionSettings (Optional)\n\nCommon section-level settings. Canonical Zod (name **BaseSectionSettingsSchema** or as exported by Core):\n\n```typescript\nexport const BaseSectionSettingsSchema = z.object({\n paddingTop: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),\n paddingBottom: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),\n theme: z.enum(['dark', 'light', 'accent']).default('dark').describe('ui:select'),\n container: z.enum(['boxed', 'fluid']).default('boxed').describe('ui:select'),\n});\n```\n\nCapsules may extend this for type-specific settings. Core may export **BaseSectionSettings** as the TypeScript type inferred from this or a superset.\n\n**Perché servono (BSDS):** anchorId permette deep-link e navigazione in-page; id sugli array item è necessario per `data-jp-item-id`, reorder e React reconciliation. BaseSectionSettings comuni (padding, theme, container) evitano ripetizione e allineano il Form Factory tra capsule. Senza base condivisi, ogni capsule inventa convenzioni e validazione/add-section diventano fragili.\n\n---\n\n## 9. 📌 AddSectionConfig (ASC) v1.0\n\n**Objective:** Formalize the \"Add Section\" contract used by the Studio.\n\n**Type (Core exports** `AddSectionConfig`**):**\n\n```typescript\ninterface AddSectionConfig {\n addableSectionTypes: readonly string[];\n sectionTypeLabels: Record<string, string>;\n getDefaultSectionData(sectionType: string): Record<string, unknown>;\n}\n```\n\n**Shape:** Tenant provides one object (e.g. `addSectionConfig`) with:\n\n- `addableSectionTypes` — Readonly array of section type keys. Only these types appear in the Add Section Library. Must be a subset of (or equal to) the keys in SectionDataRegistry.\n- `sectionTypeLabels` — Map type key → display string (e.g. `{ hero: 'Hero', 'cta-banner': 'CTA Banner' }`).\n- `getDefaultSectionData(sectionType: string): Record<string, unknown>` — Returns default `data` for a new section. Must conform to the capsule’s data schema so the new section validates.\n\nCore creates a new section with deterministic UUID, `type`, and `data` from `getDefaultSectionData(type)`.\n\n**Perché servono (ASC):** Lo Studio deve mostrare una libreria “Aggiungi sezione” con nomi leggibili e, alla scelta, creare una section con dati iniziali validi. addableSectionTypes, sectionTypeLabels e getDefaultSectionData sono il contratto: il Tenant è l’unica fonte di verità su quali tipi sono addabili e con quali default. Senza ASC, il Core non saprebbe cosa mostrare in modal né come popolare i dati della nuova section.\n\n---\n\n## 10. ⚙️ JsonPagesConfig & Engine Bootstrap (JEB) v1.1\n\n**Objective:** Bootstrap contract between Tenant app and `@olonjs/core`.\n\n### 10.1 JsonPagesConfig (required fields)\n\nThe Tenant passes a single **config** object to **JsonPagesEngine**. Required fields:\n\nField Type Description **tenantId** string Passed to `resolveAssetUrl(path, tenantId)`; resolved asset URLs are `/assets/...` with no tenantId segment in the path. **registry** `{ [K in SectionType]: React.FC<SectionComponentPropsMap[K]> }` Component registry. Must match MTRP keys. See Appendix A. **schemas** `Record<SectionType, ZodType>` or equivalent SECTION_SCHEMAS: type → **data** Zod schema. Form Factory uses this. See Appendix A. **pages** `Record<string, PageConfig>` Slug → page config. See Appendix A. **siteConfig** SiteConfig Global site (identity, header/footer blocks). See Appendix A. **themeConfig** ThemeConfig Theme tokens. See Appendix A. **menuConfig** MenuConfig Navigation fallback payload and resolver context. Header navigation may also come from dereferenced `$ref` paths. See Appendix A. **refDocuments** `Record<string, unknown>` (optional) Extra JSON documents available to the recursive `$ref` resolver (for example `menu.json`, `config/menu.json`, `src/data/config/menu.json`). **themeCss** `{ tenant: string }` At least **tenant**: string (inline CSS or URL) for Stage iframe injection. **addSection** AddSectionConfig Add-section config (§9).\n\nCore may define optional fields. The Tenant must not omit required fields.\n\n### 10.2 JsonPagesEngine\n\nRoot component: `<JsonPagesEngine config={config} />`. Responsibilities: route → page, SectionRenderer per section; in Studio mode Sovereign Shell (Inspector, Control Bar, postMessage); section wrappers and overlay per IDAC and JAP. In v1.5, the engine uses a recursive `$ref` resolver (`config-resolver`) across runtime surfaces (Engine, renderer path, and preview path), so references such as `menu.json#/main` and `../config/menu.json#/main` can be resolved from local runtime docs and optional tenant `refDocuments`. Tenant does not implement the Shell.\n\n### 10.3 Studio Selection Event Contract (v1.3, breaking)\n\nIn strict v1.3 Studio, section selection payload for nested targets is path-based:\n\n```typescript\ntype SectionSelectMessage = {\n type: 'SECTION_SELECT';\n section: { id: string; type: string; scope: 'global' | 'local' };\n itemPath?: SelectionPath; // root -> leaf\n};\n```\n\nRemoved from strict protocol:\n\n- `itemField`\n- `itemId`\n\n**Perché servono (JEB):** Un unico punto di bootstrap (config + Engine) evita che il Tenant replichi logica di routing, Shell e overlay. I campi obbligatori in JsonPagesConfig (tenantId, registry, schemas, pages, siteConfig, themeConfig, menuConfig, themeCss, addSection) sono il minimo per far funzionare rendering, Studio e Form Factory; `refDocuments` estende il runtime con documenti aggiuntivi per la dereferenziazione ricorsiva. In v1.3, il payload `itemPath` sincronizza in modo non ambiguo Stage e Inspector su nested arrays.\n\n---\n\n# 🏛️ OlonJS_ADMIN_PROTOCOL (JAP) v1.2\n\n**Status:** Mandatory Standard\\\n**Version:** 1.2.0 (Sovereign Shell Edition — Path/Nested Strictness)\\\n**Objective:** Deterministic orchestration of the \"Studio\" environment (ICE Level 1).\n\n---\n\n## 1. The Sovereign Shell Topology\n\nThe Admin interface is a **Sovereign Shell** from `@olonjs/core`.\n\n1. **The Stage (Canvas):** Isolated Iframe; postMessage for data updates and selection mirroring. Section markup follows **IDAC** (§6); overlay styling follows **TOCC** (§7).\n2. **The Inspector (Sidebar):** Consumes Tenant Zod schemas to generate editors; binding via `data-jp-field` and `data-jp-item-*`.\n3. **The Studio Actions:** Save to file, Hot Save, Add Section.\n\n## 2. State Orchestration & Persistence\n\n- **Working Draft:** Reactive local state for unsaved changes.\n- **Sync Law:** Inspector changes → Working Draft → Stage via `STUDIO_EVENTS.UPDATE_DRAFTS`.\n- **Persistence Protocol:** Studio invokes tenant-provided `saveToFile` and `hotSave` callbacks for editorial persistence.\n\n## 3. Context Switching (Global vs. Local)\n\n- **Header/Footer** selection → Global Mode, `site.json`.\n- Any other section → Page Mode, current `[slug].json`.\n\n## 4. Section Lifecycle Management\n\n1. **Add Section:** Modal from Tenant `SECTION_SCHEMAS`; UUID + default data via **AddSectionConfig** (§9).\n2. **Reorder:** Inspector or Stage Overlay; array mutation in Working Draft.\n3. **Delete:** Confirmation; remove from array, clear selection.\n\n## 5. Stage Isolation & Overlay\n\n- **CSS Shielding:** Stage in Iframe; Tenant CSS does not leak into Admin.\n- **Sovereign Overlay:** Selection ring and type labels injected per **IDAC** (§6); Tenant styles them per **TOCC** (§7).\n\n## 6. \"Green Build\" Validation\n\nStudio enforces `tsc && vite build`. No Studio or SSG build should proceed with TypeScript errors.\n\n## 7. Path-Deterministic Selection & Sidebar Expansion (v1.3, breaking)\n\n- Section/item focus synchronization uses `itemPath` (root → leaf), not flat `itemField/itemId`.\n- Sidebar expansion state for nested arrays must be derived from all path segments.\n- Flat-only matching may open/close wrong branches and is non-compliant in strict mode.\n\n**Perché servono (JAP):** Stage in iframe + Inspector + Studio actions separano il contesto di editing dal sito; postMessage e Working Draft permettono modifiche senza toccare subito i file. Save to file e Hot Save richiedono uno stato coerente. Global vs Page mode evita confusione su dove si sta editando (site.json vs \\[slug\\].json). Add/Reorder/Delete sono gestiti in un solo modo (Working Draft + ASC). Green Build garantisce che Studio e SSG compilino correttamente. In v1.3, il path completo elimina ambiguità nella sincronizzazione Stage↔Sidebar su strutture annidate.\n\n---\n\n## Compliance: Legacy vs Full UX (v1.5)\n\nDimension Legacy / Less UX Full UX (Core-aligned) **ICE binding** No `data-jp-*`; Inspector cannot bind. IDAC (§6) on every editable section/field/item. **Section wrapper** Plain `<section>`; no overlay contract. Core wrapper + overlay; Tenant CSS per TOCC (§7). **Design tokens** Raw BEM / fixed classes, or local vars fed by literals. `theme.json` as source of truth, mandatory runtime publication, local color/radius scope via `--local-*`, typography via canonical semantic font chain, no primary hardcoded themed values. **Base schemas** Ad hoc. BSDS (§8): BaseSectionData, BaseArrayItem, BaseSectionSettings. **Add Section** Ad hoc defaults. ASC (§9): addableSectionTypes, labels, getDefaultSectionData. **Bootstrap** Implicit. JEB (§10): JsonPagesConfig + JsonPagesEngine. **Selection payload** Flat `itemField/itemId`. Path-only `itemPath: SelectionPath` (JEB §10.3). **Nested array expansion** Single-segment or field-only heuristics. Root-to-leaf path expansion (ECIP §5.5, JAP §7). **Array item identity (strict)** Index fallback tolerated. Stable `id` required for editable object arrays.\n\n**Rule:** Every page section (non-header/footer) that appears in the Stage and in `SECTION_SCHEMAS` must comply with §6, §7, §4.4, §8, §9, §10 for full Studio UX.\n\n---\n\n## Summary of v1.5 Additions\n\n§ Title Purpose 4.4.3 Three-Layer CSS Bridge Replaces the informal \"publish CSS vars\" rule with the deterministic Layer 0 (engine injection) → Layer 1 (`:root` semantic bridge) → Layer 2 (`@theme` Tailwind bridge) architecture. Documents the engine's `--theme-colors-{name}` naming convention and the tenant's sovereign naming freedom in Layer 1. A.2.6 ThemeConfig (v1.5) Replaces the incorrect `surface/surfaceAlt/text/textMuted` canonical keys with the actual schema-aligned keys (`card`, `elevated`, `foreground`, `muted-foreground`, etc.). Adds `spacing`, `zIndex`, full typography sub-interfaces (`scale`, `tracking`, `leading`, `wordmark`), and `modes`. Establishes `theme.json` as SOT with schema as the formalisation layer.\n\n---\n\n## Summary of v1.5 Additions\n\n§ Title Purpose 4.4 Local Design Tokens Makes the `theme.json -> runtime vars -> --local-* -> JSX classes` chain explicit and normative. 4.4.3 Runtime Theme Publication Makes runtime CSS publication mandatory for themed tenants. 4.4.5 Canonical Typography Rule Removes ambiguity between global semantic font utilities and local token scoping. 4.4.7 Compliance Rules Turns Local Design Tokens into a checklist-grade compliance contract. 4.4.9 Non-Compliant Patterns Makes hardcoded token anti-patterns explicit. **Appendix A.2.6** **Deterministic ThemeConfig** Aligns the spec-level theme contract with the core’s structured semantic keys plus extension policy. **Appendix A.7** **Local Design Tokens Implementation Addendum** Operational checklist and implementation examples for compliant tenant sections.\n\n---\n\n# Appendix A — Tenant Type & Code-Generation Annex\n\n**Objective:** Make the specification **sufficient** to generate or audit a full tenant (new site, new components, new data) without a reference codebase. Defines TypeScript types, JSON shapes, schema contract, file paths, and integration pattern.\n\n**Status:** Mandatory for code-generation and governance. Compliance ensures generated tenants are typed and wired like the reference implementation.\n\n---\n\n## A.1 Core-Provided Types (from `@olonjs/core`)\n\nThe following are assumed to be exported by Core. The Tenant augments **SectionDataRegistry** and **SectionSettingsRegistry**; all other types are consumed as-is.\n\nType Description **SectionType** `keyof SectionDataRegistry` (after Tenant augmentation). Union of all section type keys. **Section** Union of `BaseSection<K>` for all K in SectionDataRegistry. See MTRP §1.2. **BaseSectionSettings** Optional base type for section settings (may align with BSDS §8.3). **MenuItem** Navigation item. **Minimum shape:** `{ label: string; href: string }`. Core may extend (e.g. `children?: MenuItem[]`). **AddSectionConfig** See §9. **JsonPagesConfig** See §10.1.\n\n**Perché servono (A.1):** Il Tenant deve conoscere i tipi esportati dal Core (SectionType, MenuItem, AddSectionConfig, JsonPagesConfig) per tipizzare registry, config e augmentation senza dipendere da implementazioni interne.\n\n---\n\n## A.2 Tenant-Provided Types (single source: `src/types.ts` or equivalent)\n\nThe Tenant **must** define the following in one module (e.g. `src/types.ts`). This module **must** perform the **module augmentation** of `@olonjs/core` for **SectionDataRegistry** and **SectionSettingsRegistry**, and **must** export **SectionComponentPropsMap** and re-export from `@olonjs/core` so that **SectionType** is available after augmentation.\n\n### A.2.1 SectionComponentPropsMap\n\nMaps each section type to the props of its React component. **Header** is the only type that receives **menu**.\n\n**Option A — Explicit (recommended for clarity and tooling):** For each section type K, add one entry. Header receives **menu**.\n\n```typescript\nimport type { MenuItem } from '@olonjs/core';\n// Import Data/Settings from each capsule.\n\nexport type SectionComponentPropsMap = {\n 'header': { data: HeaderData; settings?: HeaderSettings; menu: MenuItem[] };\n 'footer': { data: FooterData; settings?: FooterSettings };\n 'hero': { data: HeroData; settings?: HeroSettings };\n // ... one entry per SectionType, e.g. 'feature-grid', 'cta-banner', etc.\n};\n```\n\n**Option B — Mapped type (DRY, requires SectionDataRegistry/SectionSettingsRegistry in scope):**\n\n```typescript\nimport type { MenuItem } from '@olonjs/core';\n\nexport type SectionComponentPropsMap = {\n [K in SectionType]: K extends 'header'\n ? { data: SectionDataRegistry[K]; settings?: SectionSettingsRegistry[K]; menu: MenuItem[] }\n : { data: SectionDataRegistry[K]; settings?: K extends keyof SectionSettingsRegistry ? SectionSettingsRegistry[K] : BaseSectionSettings };\n};\n```\n\nSectionType is imported from Core (after Tenant augmentation). In practice Option A is the reference pattern; Option B is valid if the Tenant prefers a single derived definition.\n\n**Perché servono (A.2):** SectionComponentPropsMap e i tipi di config (PageConfig, SiteConfig, MenuConfig, ThemeConfig) definiscono il contratto tra dati (JSON, API) e componente; l’augmentation è l’unico modo per estendere i registry del Core senza fork. Senza questi tipi, generazione tenant e refactor sarebbero senza guida e il type-check fallirebbe.\n\n### A.2.2 ComponentRegistry type\n\nThe registry object **must** be typed as:\n\n```typescript\nimport type { SectionType } from '@olonjs/core';\nimport type { SectionComponentPropsMap } from '@/types';\n\nexport const ComponentRegistry: {\n [K in SectionType]: React.FC<SectionComponentPropsMap[K]>;\n} = { /* ... */ };\n```\n\nFile: `src/lib/ComponentRegistry.tsx` (or equivalent). Imports one View per section type and assigns it to the corresponding key.\n\n### A.2.3 PageConfig\n\nMinimum shape for a single page (used in **pages** and in each `[slug].json`):\n\n```typescript\nexport interface PageConfig {\n id?: string;\n slug: string;\n meta?: {\n title?: string;\n description?: string;\n };\n sections: Section[];\n}\n```\n\n**Section** is the union type from MTRP (§1.2). Each element of **sections** has **id**, **type**, **data**, **settings** and conforms to the capsule schemas.\n\n### A.2.4 SiteConfig\n\nMinimum shape for **site.json** (and for **siteConfig** in JsonPagesConfig):\n\n```typescript\nexport interface SiteConfigIdentity {\n title?: string;\n logoUrl?: string;\n}\n\nexport interface SiteConfig {\n identity?: SiteConfigIdentity;\n pages?: Array<{ slug: string; label: string }>;\n header: {\n id: string;\n type: 'header';\n data: HeaderData;\n settings?: HeaderSettings;\n };\n footer: {\n id: string;\n type: 'footer';\n data: FooterData;\n settings?: FooterSettings;\n };\n}\n```\n\n**HeaderData**, **FooterData**, **HeaderSettings**, **FooterSettings** are the types exported from the header and footer capsules.\n\n### A.2.5 MenuConfig\n\nMinimum shape for **menu.json** (and for **menuConfig** in JsonPagesConfig). Structure is tenant-defined; Core expects the header to receive **MenuItem\\[\\]** after runtime resolution. Common pattern: an object with a key (e.g. **main**) whose value is **MenuItem\\[\\]**.\n\n```typescript\nexport interface MenuConfig {\n main?: MenuItem[];\n [key: string]: MenuItem[] | undefined;\n}\n```\n\nOr simply `MenuItem[]` if the app uses a single flat list. In v1.5 runtime behavior, header menu is resolved through recursive `$ref` resolution first (e.g. `menu.json#/main`, `../config/menu.json#/main`) using local documents and optional `refDocuments`, then falls back to `menuConfig.main` / `menuConfig` when needed. The final value passed to header must conform to **MenuItem\\[\\]**.\n\n### A.2.6 ThemeConfig\n\nMinimum shape for **theme.json** (and for **themeConfig** in JsonPagesConfig). `theme.json` is the **source of truth** for the entire visual contract of the tenant. The schema (`design-system.schema.json`) is the machine-readable formalisation of this contract — if the TypeScript interfaces and the JSON Schema diverge, the JSON Schema wins.\n\n**Naming policy:** The keys within `tokens.colors` are the tenant's sovereign choice. The engine flattens all keys to `--theme-colors-{name}` regardless of naming convention. The required keys listed below are the ones the engine's `:root` bridge and the `@theme` Tailwind bridge must be able to resolve. Extra brand-specific keys are always allowed as additive extensions.\n\n```typescript\nexport interface ThemeColors {\n /* Required — backgrounds */\n background: string;\n card: string;\n elevated: string;\n overlay: string;\n popover: string;\n 'popover-foreground': string;\n\n /* Required — foregrounds */\n foreground: string;\n 'card-foreground': string;\n 'muted-foreground': string;\n placeholder: string;\n\n /* Required — brand */\n primary: string;\n 'primary-foreground': string;\n 'primary-light': string;\n 'primary-dark': string;\n\n /* Optional — brand ramp (50–900) */\n 'primary-50'?: string;\n 'primary-100'?: string;\n 'primary-200'?: string;\n 'primary-300'?: string;\n 'primary-400'?: string;\n 'primary-500'?: string;\n 'primary-600'?: string;\n 'primary-700'?: string;\n 'primary-800'?: string;\n 'primary-900'?: string;\n\n /* Required — accent, secondary, muted */\n accent: string;\n 'accent-foreground': string;\n secondary: string;\n 'secondary-foreground': string;\n muted: string;\n\n /* Required — border, form */\n border: string;\n 'border-strong': string;\n input: string;\n ring: string;\n\n /* Required — feedback */\n destructive: string;\n 'destructive-foreground': string;\n 'destructive-border': string;\n 'destructive-ring': string;\n success: string;\n 'success-foreground': string;\n 'success-border': string;\n 'success-indicator': string;\n warning: string;\n 'warning-foreground': string;\n 'warning-border': string;\n info: string;\n 'info-foreground': string;\n 'info-border': string;\n\n [key: string]: string | undefined;\n}\n\nexport interface ThemeFontFamily {\n primary: string;\n mono: string;\n display?: string;\n [key: string]: string | undefined;\n}\n\nexport interface ThemeWordmark {\n fontFamily: string;\n weight: string;\n width: string;\n}\n\nexport interface ThemeTypography {\n fontFamily: ThemeFontFamily;\n wordmark?: ThemeWordmark;\n scale?: Record<string, string>; /* xs sm base md lg xl 2xl 3xl 4xl 5xl 6xl 7xl */\n tracking?: Record<string, string>; /* tight display normal wide label */\n leading?: Record<string, string>; /* none tight snug normal relaxed */\n}\n\nexport interface ThemeBorderRadius {\n sm: string;\n md: string;\n lg: string;\n xl?: string;\n full?: string;\n [key: string]: string | undefined;\n}\n\nexport interface ThemeSpacing {\n 'container-max'?: string;\n 'section-y'?: string;\n 'header-h'?: string;\n 'sidebar-w'?: string;\n [key: string]: string | undefined;\n}\n\nexport interface ThemeZIndex {\n base?: string;\n elevated?: string;\n dropdown?: string;\n sticky?: string;\n overlay?: string;\n modal?: string;\n toast?: string;\n [key: string]: string | undefined;\n}\n\nexport interface ThemeModes {\n [mode: string]: { colors: Partial<ThemeColors> };\n}\n\nexport interface ThemeTokens {\n colors: ThemeColors;\n typography: ThemeTypography;\n borderRadius: ThemeBorderRadius;\n spacing?: ThemeSpacing;\n zIndex?: ThemeZIndex;\n modes?: ThemeModes;\n}\n\nexport interface ThemeConfig {\n name: string;\n tokens: ThemeTokens;\n}\n```\n\n**Rule:** `theme.json` is the single source of truth. All layers downstream (engine injection, `:root` bridge, `@theme` bridge, React JSX) are read-only consumers. No layer below `theme.json` may hardcode a value that belongs to the theme contract.\n\n**Rule:** Brand-specific extension keys (e.g. `colors.primary-50` through `primary-900`, custom spacing tokens) are always allowed as additive extensions within the canonical groups. They must not replace the required semantic keys.\n\n---\n\n## A.3 Schema Contract (SECTION_SCHEMAS)\n\n**Location:** `src/lib/schemas.ts` (or equivalent).\n\n**Contract:**\n\n- **SECTION_SCHEMAS** is a **single object** whose keys are **SectionType** and whose values are **Zod schemas for the section data** (not settings, unless the Form Factory contract expects a combined or per-type settings schema; then each value may be the data schema only, and settings may be defined per capsule and aggregated elsewhere if needed).\n- The Tenant **must** re-export **BaseSectionData**, **BaseArrayItem**, and optionally **BaseSectionSettingsSchema** from `src/lib/base-schemas.ts` (or equivalent). Each capsule’s data schema **must** extend BaseSectionData; each array item schema **must** extend or include BaseArrayItem.\n- **SECTION_SCHEMAS** is typed as `Record<SectionType, ZodType>` or `{ [K in SectionType]: ZodType }` so that keys match the registry and SectionDataRegistry.\n\n**Export:** The app imports **SECTION_SCHEMAS** and passes it as **config.schemas** to JsonPagesEngine. The Form Factory traverses these schemas to build editors.\n\n**Perché servono (A.3):** Un unico oggetto SECTION_SCHEMAS con chiavi = SectionType e valori = schema data permette al Form Factory di costruire form per tipo senza convenzioni ad hoc; i base schema garantiscono anchorId e id su item. Senza questo contratto, l’Inspector non saprebbe quali campi mostrare né come validare.\n\n---\n\n## A.4 File Paths & Data Layout\n\nPurpose Path (conventional) Description Site config `src/data/config/site.json` SiteConfig (identity, header, footer, pages list). Menu config `src/data/config/menu.json` MenuConfig (e.g. main nav). Theme config `src/data/config/theme.json` ThemeConfig (tokens). Page data `src/data/pages/<slug>.json` One file per page; content is PageConfig (slug, meta, sections). Base schemas `src/lib/base-schemas.ts` BaseSectionData, BaseArrayItem, BaseSectionSettingsSchema. Schema aggregate `src/lib/schemas.ts` SECTION_SCHEMAS; re-exports base schemas. Registry `src/lib/ComponentRegistry.tsx` ComponentRegistry object. Add-section config `src/lib/addSectionConfig.ts` addSectionConfig (AddSectionConfig). Tenant types & augmentation `src/types.ts` SectionComponentPropsMap, PageConfig, SiteConfig, MenuConfig, ThemeConfig; **declare module '@olonjs/core'** for SectionDataRegistry and SectionSettingsRegistry; re-export from `@olonjs/core`. Bootstrap `src/App.tsx` Imports config (site, theme, menu, pages), optional `refDocuments`, registry, schemas, addSection, themeCss; builds JsonPagesConfig; renders .\n\nThe app entry (e.g. **main.tsx**) renders **App**. No other bootstrap contract is specified; the Tenant may use Vite aliases (e.g. **@/**) for the paths above.\n\n**Perché servono (A.4):** Path fissi (data/config, data/pages, lib/schemas, types.ts, App.tsx) permettono a CLI, tooling e agenti di trovare sempre gli stessi file; l’onboarding e la generazione da spec sono deterministici. Senza convenzione, ogni tenant sarebbe una struttura diversa.\n\n---\n\n## A.5 Integration Checklist (Code-Generation)\n\nWhen generating or auditing a tenant, ensure the following in order:\n\n 1. **Capsules** — For each section type, create `src/components/<type>/` with View.tsx, schema.ts, types.ts, index.ts. Data schema extends BaseSectionData; array items extend BaseArrayItem; View complies with CIP and IDAC (§6.2–6.3 for non-reserved types).\n 2. **Base schemas** — **src/lib/base-schemas.ts** exports BaseSectionData, BaseArrayItem, BaseSectionSettingsSchema (and optional CtaSchema or similar shared fragments).\n 3. **types.ts** — Define SectionComponentPropsMap (header with **menu**), PageConfig, SiteConfig, MenuConfig, ThemeConfig; **declare module '@olonjs/core'** and augment SectionDataRegistry and SectionSettingsRegistry; re-export from `@olonjs/core`.\n 4. **ComponentRegistry** — Import every View; build object **{ \\[K in SectionType\\]: ViewComponent }**; type as **{ \\[K in SectionType\\]: React.FC&lt;SectionComponentPropsMap\\[K\\]&gt; }**.\n 5. **schemas.ts** — Import base schemas and each capsule’s data schema; export SECTION_SCHEMAS as **{ \\[K in SectionType\\]: SchemaK }**; export SectionType as **keyof typeof SECTION_SCHEMAS** if not using Core’s SectionType.\n 6. **addSectionConfig** — addableSectionTypes, sectionTypeLabels, getDefaultSectionData; export as AddSectionConfig.\n 7. **App.tsx** — Import site, theme, menu, pages from data paths; expose optional `refDocuments` for recursive `$ref` resolution; build config (tenantId, registry, schemas, pages, siteConfig, themeConfig, menuConfig, refDocuments, themeCss: { tenant }, addSection); render JsonPagesEngine.\n 8. **Data files** — Create or update site.json, menu.json, theme.json, and one or more **.json** under the paths in A.4. Ensure JSON shapes match SiteConfig, MenuConfig, ThemeConfig, PageConfig.\n 9. **Runtime theme publication** — Publish the theme contract as runtime CSS custom properties before themed sections render.\n10. **Tenant CSS** — Include TOCC (§7) selectors in global CSS so the Stage overlay is visible, and bridge semantic theme variables where needed.\n11. **Reserved types** — Header and footer capsules receive props per SectionComponentPropsMap; menu is resolved from the JSON graph via `$ref` (with `refDocuments` support) and falls back to `menuConfig` values when direct resolution is unavailable.\n\n**Perché servono (A.5):** La checklist in ordine evita di dimenticare passi (es. augmentation prima del registry, TOCC dopo le View) e rende la spec sufficiente per generare o verificare un tenant senza codebase di riferimento.\n\n---\n\n## A.6 v1.3 Path/Nested Strictness Addendum (breaking)\n\nThis addendum extends Appendix A without removing prior v1.2 obligations:\n\n1. **Type exports** — Core and/or shared types module should expose `SelectionPathSegment` and `SelectionPath` for Studio messaging and Inspector expansion logic.\n2. **Protocol migration** — Replace flat payload fields `itemField` / `itemId` with `itemPath?: SelectionPath` in strict v1.3 channels.\n3. **Nested array compliance** — For editable object arrays, item identity must be stable (`id`) and propagated to DOM attributes (`data-jp-item-id`), schema items (BaseArrayItem), and selection path segments (`itemId` when segment targets array item).\n4. **Backward compatibility policy** — Legacy flat fields may exist only in transitional adapters outside strict mode; normative v1.3 contract is path-only.\n\n---\n\n## A.7 v1.5 Local Design Tokens Implementation Addendum\n\nThis addendum extends Appendix A without removing prior v1.3 obligations:\n\n1. **Theme source of truth** — Tenant theme values belong in `src/data/config/theme.json`.\n2. **Runtime publication** — Core and/or tenant bootstrap **must** expose those values as runtime CSS custom properties before section rendering.\n3. **Local scope** — A themed section must define `--local-*` variables on its root for the color/radius concerns it owns.\n4. **Class consumption** — Section-owned color/radius utilities must consume `var(--local-*)`, not raw hardcoded theme values.\n5. **Typography policy** — Fonts must consume the published semantic font chain; local font tokens are optional and only for local remapping.\n6. **Migration policy** — Hardcoded colors/radii may exist only as temporary compatibility shims or purely decorative exceptions, not as the primary section contract.\n\nCanonical implementation pattern:\n\n```text\ntheme.json -> published runtime theme vars -> section --local-* -> JSX classes\n```\n\nCanonical typography pattern:\n\n```text\ntheme.json -> published semantic font vars -> tenant font utility/variable -> section typography\n```\n\nMinimal compliant example:\n\n```tsx\n<section\n style={{\n '--local-bg': 'var(--background)',\n '--local-text': 'var(--foreground)',\n '--local-primary': 'var(--primary)',\n '--local-radius-md': 'var(--theme-radius-md)',\n } as React.CSSProperties}\n className=\"bg-[var(--local-bg)]\"\n>\n <h2 className=\"font-display text-[var(--local-text)]\">Title</h2>\n <a className=\"bg-[var(--local-primary)] rounded-[var(--local-radius-md)]\">CTA</a>\n</section>\n```\n\nDeterministic compliance checklist:\n\n1. Canonical semantic theme keys exist.\n2. Runtime publication exists.\n3. Section-local color/radius scope exists.\n4. Section-owned color/radius classes consume `var(--local-*)`.\n5. Fonts consume the semantic published font chain.\n6. Primary themed values are not hardcoded.\n\n---\n\n**Validation:** Align with current `@olonjs/core` exports (SectionType, MenuItem, AddSectionConfig, JsonPagesConfig, and in v1.3+ path types for Studio selection), with the deterministic `ThemeConfig` contract, and with the runtime theme publication contract used by tenant CSS.\\\n**Distribution:** Core via `.yalc`; tenant projections via `@olonjs/cli`. This annex makes the spec **necessary and sufficient** for tenant code-generation and governance at enterprise grade."
11590
11513
  },
11591
11514
  "settings": {}
11592
11515
  }
11593
11516
  ]
11594
- }
11595
-
11517
+ }
11596
11518
  END_OF_FILE_CONTENT
11597
11519
  echo "Creating src/data/pages/home.json..."
11598
11520
  cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
@@ -11613,19 +11535,21 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
11613
11535
  "headline": "Contract Layer",
11614
11536
  "subline": "for the agentic web.",
11615
11537
  "body": "AI agents are becoming operational actors in commerce, marketing, and support. They need more than content — they need a contract. OlonJS is the deterministic machine contract for websites: every site typed, structured, and addressable by design. No custom glue. No fragile integrations. Just a contract any agent can read and operate.",
11616
- "cta": {
11617
- "primary": {
11618
- "label": "Get started",
11619
- "href": "#getstarted"
11620
- },
11621
- "secondary": {
11622
- "label": "GitHub",
11623
- "href": "https://github.com/olonjs/core"
11624
- },
11625
- "ghost": {
11626
- "label": "Explore platform",
11627
- "href": "#architecture"
11628
- }
11538
+ "primaryCta": {
11539
+ "label": "Get started",
11540
+ "href": "#getstarted"
11541
+ },
11542
+ "secondaryCta": {
11543
+ "label": "GitHub",
11544
+ "href": "https://github.com/olonjs/core"
11545
+ },
11546
+ "ghostCta": {
11547
+ "label": "Explore platform",
11548
+ "href": "#architecture"
11549
+ },
11550
+ "image": {
11551
+ "url": "/assets/images/plug-graded-square.jpg",
11552
+ "alt": "Olon interface port engraved into a dark stone surface"
11629
11553
  }
11630
11554
  }
11631
11555
  },
@@ -11802,6 +11726,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
11802
11726
  }
11803
11727
  ]
11804
11728
  }
11729
+
11805
11730
  END_OF_FILE_CONTENT
11806
11731
  echo "Creating src/data/pages/home_.json..."
11807
11732
  cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home_.json"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olonjs/cli",
3
- "version": "3.0.118",
3
+ "version": "3.0.120",
4
4
  "description": "The Sovereign CLI Engine for OlonJS.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -6,6 +6,7 @@ import path from 'path';
6
6
  import { execa } from 'execa';
7
7
  import ora from 'ora';
8
8
  import { fileURLToPath } from 'url';
9
+ import os from 'os';
9
10
 
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = path.dirname(__filename);
@@ -228,4 +229,73 @@ async function injectInfraFiles(targetDir, name) {
228
229
  await fs.writeJson(path.join(targetDir, 'components.json'), shadcnConfig, { spaces: 2 });
229
230
  }
230
231
 
232
+ program
233
+ .command('init-mcp')
234
+ .description('Automatically configure Cursor MCP settings for OlonJS')
235
+ .action(async () => {
236
+ console.log(chalk.blue.bold('\nInitializing OlonJS MCP for Cursor...\n'));
237
+
238
+ const isWin = process.platform === 'win32';
239
+ const commandName = isWin ? 'npx.cmd' : 'npx';
240
+
241
+ const mcpConfig = {
242
+ command: commandName,
243
+ args: ['-y', '@olonjs/mcp@latest', 'http://localhost:5174']
244
+ };
245
+
246
+ const homeDir = os.homedir();
247
+ const pathsToCheck = [
248
+ path.join(homeDir, '.cursor', 'mcp.json'),
249
+ ];
250
+
251
+ if (isWin) {
252
+ pathsToCheck.push(path.join(homeDir, 'AppData', 'Roaming', 'Cursor', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
253
+ } else if (process.platform === 'darwin') {
254
+ pathsToCheck.push(path.join(homeDir, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
255
+ } else {
256
+ pathsToCheck.push(path.join(homeDir, '.config', 'Cursor', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
257
+ }
258
+
259
+ let updatedCount = 0;
260
+
261
+ for (const configPath of pathsToCheck) {
262
+ try {
263
+ let configData = { mcpServers: {} };
264
+
265
+ if (fs.existsSync(configPath)) {
266
+ const content = await fs.readFile(configPath, 'utf-8');
267
+ try {
268
+ configData = JSON.parse(content);
269
+ } catch (e) {
270
+ console.log(chalk.yellow(`Warning: Could not parse ${configPath}. Creating new object.`));
271
+ }
272
+ } else {
273
+ // If the file doesn't exist, we ensure the directory exists first
274
+ await fs.ensureDir(path.dirname(configPath));
275
+ }
276
+
277
+ if (!configData.mcpServers) {
278
+ configData.mcpServers = {};
279
+ }
280
+
281
+ configData.mcpServers['OlonJS'] = mcpConfig;
282
+
283
+ await fs.writeFile(configPath, JSON.stringify(configData, null, 2));
284
+ console.log(chalk.green(`✓ Updated MCP configuration at: ${configPath}`));
285
+ updatedCount++;
286
+ } catch (err) {
287
+ console.log(chalk.gray(`Skipped ${configPath} (not found or accessible)`));
288
+ }
289
+ }
290
+
291
+ if (updatedCount === 0) {
292
+ console.log(chalk.red('\nCould not find any Cursor MCP configuration files to update.'));
293
+ console.log('You can manually add the following JSON to your MCP settings:');
294
+ console.log(JSON.stringify({ "OlonJS": mcpConfig }, null, 2));
295
+ } else {
296
+ console.log(chalk.green.bold('\nOlonJS MCP configured successfully!'));
297
+ console.log(chalk.cyan('Please restart Cursor (or run "Developer: Reload Window") to apply the changes.'));
298
+ }
299
+ });
300
+
231
301
  program.parse();