create-ailk 0.12.1 → 0.14.0

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.
Files changed (163) hide show
  1. package/README.md +12 -4
  2. package/component-catalog.json +247 -33
  3. package/component-catalog.md +10 -6
  4. package/dist/apply-design.d.ts +21 -2
  5. package/dist/apply-design.js +120 -19
  6. package/dist/cli.js +1 -0
  7. package/dist/design-compile.d.ts +70 -2
  8. package/dist/design-compile.js +97 -1
  9. package/dist/design-home.d.ts +74 -0
  10. package/dist/design-home.js +256 -0
  11. package/dist/design-package.d.ts +39 -2
  12. package/dist/design-package.js +223 -9
  13. package/dist/design-presets.d.ts +17 -1
  14. package/dist/design-presets.js +17 -5
  15. package/dist/design-scaffold.d.ts +6 -25
  16. package/dist/design-scaffold.js +26 -74
  17. package/dist/index.d.ts +1 -0
  18. package/dist/lib/design-checks.d.ts +12 -1
  19. package/dist/lib/design-checks.js +38 -2
  20. package/dist/lib/module-license-gate.d.ts +9 -0
  21. package/dist/lib/module-license-gate.js +18 -0
  22. package/dist/module-architecture.js +27 -0
  23. package/dist/programmatic.js +70 -26
  24. package/dist/site-chrome.d.ts +38 -0
  25. package/dist/site-chrome.js +109 -2
  26. package/dist/site-fonts.d.ts +21 -0
  27. package/dist/site-fonts.js +54 -0
  28. package/dist/site-roster.d.ts +16 -0
  29. package/dist/site-roster.js +81 -0
  30. package/dist/starter-grid.js +15 -0
  31. package/dist/surfaces.js +12 -0
  32. package/dist/team-profiles.d.ts +29 -0
  33. package/dist/team-profiles.js +43 -0
  34. package/package.json +3 -3
  35. package/release-scope.json +1 -1
  36. package/site-templates/_base/content/en/TeamList/team.mdx +68 -0
  37. package/site-templates/_base/content/en/TeamMember/first-member.mdx +22 -0
  38. package/site-templates/_base/content/en/TeamMember/second-member.mdx +22 -0
  39. package/site-templates/_base/content/en/TeamMember/third-member.mdx +22 -0
  40. package/site-templates/designs/minimalism/DESIGN.md +164 -34
  41. package/site-templates/designs/minimalism/home/local-business-service.mdx +119 -0
  42. package/site-templates/designs/minimalism/home/organization-product.mdx +132 -0
  43. package/site-templates/designs/minimalism/home/organization-service.mdx +131 -0
  44. package/site-templates/designs/minimalism/home/person-service.mdx +101 -0
  45. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +34 -1
  46. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +20 -0
  47. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +34 -1
  48. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +22 -1
  49. package/site-templates/marketing-site-free/ailk-template.json +1 -1
  50. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +34 -1
  51. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -7
  52. package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
  53. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +34 -1
  54. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -7
  55. package/site-templates/shapes/organization-service/ailk-template.json +1 -1
  56. package/templates/.env.example +23 -9
  57. package/templates/README.md +2 -0
  58. package/templates/apps/api/CLAUDE.md +24 -17
  59. package/templates/apps/api/src/__tests__/factories/agent-caller.ts +98 -0
  60. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +321 -0
  61. package/templates/apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts +117 -0
  62. package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +39 -14
  63. package/templates/apps/api/src/config/index.ts +35 -20
  64. package/templates/apps/api/src/lib/__mocks__/prisma-client.js +3 -0
  65. package/templates/apps/api/src/lib/__mocks__/prisma.ts +14 -0
  66. package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +468 -0
  67. package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +32 -2
  68. package/templates/apps/api/src/lib/agent-store.ts +521 -0
  69. package/templates/apps/api/src/lib/spoke-store.ts +13 -2
  70. package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +384 -1
  71. package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +61 -0
  72. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +176 -0
  73. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +76 -0
  74. package/templates/apps/api/src/middleware/auth.ts +219 -5
  75. package/templates/apps/api/src/middleware/default-tenant.ts +29 -8
  76. package/templates/apps/api/src/middleware/rate-limit.ts +35 -13
  77. package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +76 -15
  78. package/templates/apps/api/src/routes/leads/index.ts +14 -6
  79. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -6
  80. package/templates/apps/api/src/routes/project-listings/index.ts +14 -9
  81. package/templates/apps/api/src/routes/project-listings/me.ts +4 -3
  82. package/templates/apps/api/src/routes/project-listings/patch-copy.ts +7 -5
  83. package/templates/apps/api/src/routes/workspaces/__tests__/inbox-wire-schemas.test.ts +198 -0
  84. package/templates/apps/api/src/server.ts +27 -0
  85. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +374 -57
  86. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +25 -2
  87. package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +303 -17
  88. package/templates/apps/api/src/services/audit-log.ts +278 -47
  89. package/templates/apps/api/src/services/default-tenant.ts +55 -5
  90. package/templates/apps/api/src/services/leads-inbox.ts +12 -21
  91. package/templates/apps/api/src/services/project-listings.ts +1 -21
  92. package/templates/apps/api/src/services/public-site-resolver.ts +103 -13
  93. package/templates/apps/mcp/CLAUDE.md +44 -5
  94. package/templates/apps/mcp/__tests__/parity.test.ts +53 -1
  95. package/templates/apps/mcp/__tests__/workspace-surface.test.ts +364 -0
  96. package/templates/apps/mcp/package.json +1 -1
  97. package/templates/apps/mcp/src/http-transport.ts +107 -1
  98. package/templates/apps/mcp/src/workspace-server.ts +193 -0
  99. package/templates/apps/web/.env.example +64 -37
  100. package/templates/apps/web/CLAUDE.md +11 -0
  101. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +10 -0
  102. package/templates/apps/web/app/[locale]/(authed)/account/settings/__tests__/theme-modes.test.ts +42 -0
  103. package/templates/apps/web/app/[locale]/(authed)/account/settings/theme-modes.ts +8 -0
  104. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +2 -2
  105. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  106. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +14 -1
  107. package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +69 -0
  108. package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +64 -0
  109. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +4 -4
  110. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +15 -3
  111. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +17 -6
  112. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +14 -1
  113. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +2 -2
  114. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +4 -6
  115. package/templates/apps/web/app/[locale]/layout.tsx +156 -53
  116. package/templates/apps/web/app/[locale]/page.tsx +37 -0
  117. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  118. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +4 -6
  119. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +15 -2
  120. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +43 -6
  121. package/templates/apps/web/app/[locale]/services/page.tsx +14 -2
  122. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +2 -2
  123. package/templates/apps/web/app/[locale]/team/[slug]/page.tsx +45 -0
  124. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +155 -0
  125. package/templates/apps/web/app/[locale]/team/page.tsx +49 -0
  126. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +2 -2
  127. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +14 -1
  128. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +2 -2
  129. package/templates/apps/web/app/not-found.tsx +47 -0
  130. package/templates/apps/web/app/v1/[...path]/route.ts +16 -0
  131. package/templates/apps/web/lib/__tests__/profile-back-link.test.ts +95 -0
  132. package/templates/apps/web/lib/__tests__/relay.test.ts +515 -0
  133. package/templates/apps/web/lib/__tests__/site-theme-head.test.ts +70 -0
  134. package/templates/apps/web/lib/__tests__/site-theme.test.ts +64 -1
  135. package/templates/apps/web/lib/profile-back-link.ts +51 -0
  136. package/templates/apps/web/lib/relay.ts +279 -0
  137. package/templates/apps/web/lib/site-chrome.ts +12 -0
  138. package/templates/apps/web/lib/site-fonts.ts +15 -0
  139. package/templates/apps/web/lib/site-logo.tsx +39 -1
  140. package/templates/apps/web/lib/site-theme-head.ts +33 -0
  141. package/templates/apps/web/lib/site-theme.ts +13 -1
  142. package/templates/apps/web/messages/en.json +7 -1
  143. package/templates/apps/web/middleware.ts +5 -4
  144. package/templates/apps/web/next.config.mjs +6 -0
  145. package/templates/database/CHANGELOG.md +22 -0
  146. package/templates/database/__tests__/agents.db.test.ts +235 -0
  147. package/templates/database/auth/schema.prisma +61 -0
  148. package/templates/database/migrations/20261005120000_agents/migration.sql +84 -0
  149. package/templates/database/package.json +1 -1
  150. package/versions.json +35 -35
  151. package/site-templates/designs/minimalism/sample/README.md +0 -48
  152. package/site-templates/designs/minimalism/sample/content/en/HomePage/home.mdx +0 -244
  153. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-1.svg +0 -7
  154. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-2.svg +0 -7
  155. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-3.svg +0 -7
  156. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-4.svg +0 -7
  157. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-5.svg +0 -7
  158. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-6.svg +0 -7
  159. package/site-templates/designs/minimalism/sample/public/hero-backdrop.svg +0 -17
  160. package/site-templates/designs/minimalism/sample/public/logo-placeholder-1.svg +0 -6
  161. package/site-templates/designs/minimalism/sample/public/logo-placeholder-2.svg +0 -6
  162. package/site-templates/designs/minimalism/sample/public/logo-placeholder-3.svg +0 -6
  163. package/site-templates/designs/minimalism/sample/public/wordmark-placeholder.svg +0 -28
@@ -1,3 +1,4 @@
1
+ import { type HomeSeedOutcome } from "./design-home.js";
1
2
  export interface ApplyDesignOptions {
2
3
  /** The design: a shipped name, or a directory holding a DESIGN.md. */
3
4
  design: string;
@@ -6,6 +7,8 @@ export interface ApplyDesignOptions {
6
7
  /** Package-rooted inputs, supplied by the caller (cli.ts from its PKG_ROOT). */
7
8
  designsDir: string;
8
9
  siteTemplatesDir: string;
10
+ /** The overlay's `templates/` (#7743 D7) — where a treeless shape's home lives. Default: `templates` beside `siteTemplatesDir`. */
11
+ templatesDir?: string;
9
12
  catalogPath: string;
10
13
  /** Where a relative design path resolves from. Default process.cwd(). */
11
14
  cwd?: string;
@@ -21,6 +24,9 @@ export type ApplyDesignChange = {
21
24
  } | {
22
25
  path: string;
23
26
  kind: "layout";
27
+ } | {
28
+ path: string;
29
+ kind: "fonts";
24
30
  } | {
25
31
  path: string;
26
32
  kind: "design";
@@ -28,6 +34,11 @@ export type ApplyDesignChange = {
28
34
  path: string;
29
35
  kind: "preset";
30
36
  fields: string[];
37
+ } | {
38
+ path: string;
39
+ kind: "home";
40
+ action: "seeded" | "restored";
41
+ fields: string[];
31
42
  } | {
32
43
  path: string;
33
44
  kind: "manifest";
@@ -39,15 +50,23 @@ export interface ApplyDesignResult {
39
50
  /** In write order (D4); [] when nothing changed. */
40
51
  changes: ApplyDesignChange[];
41
52
  manifest: "recorded" | "unchanged" | "no-roster" | "legacy-roster";
53
+ /** #7743 D7 — what the design did to the home page's sections. */
54
+ home: HomeSeedOutcome;
55
+ /** #7743 D7 — the design ships a home arc for this site, so a home it leaves alone is reported. */
56
+ homeArc?: true;
42
57
  /** The site-relative roster file, when there is one. */
43
58
  rosterPath?: string;
59
+ /** #7720 D4.2 — the distinct families written to site-fonts.ts, display first; set only when faces are written. */
60
+ fonts?: string[];
61
+ /** #7720 D4.2 — faces were written, but the site's layout.tsx does not read lib/site-fonts. */
62
+ fontsUnwired?: true;
44
63
  }
45
64
  export interface ApplyDesignArgs {
46
65
  design: string | undefined;
47
66
  target: string | undefined;
48
67
  help: boolean;
49
68
  }
50
- export declare const APPLY_DESIGN_HELP = "create-ailk apply-design \u2014 make an existing site wear a design.\n\nUsage:\n create-ailk apply-design <name> [--target <site-dir>]\n create-ailk apply-design <path> [--target <site-dir>]\n\nArguments:\n <name|path> A shipped design (standard, minimalism), or a\n directory holding a DESIGN.md. A relative path\n resolves from the current directory.\n\nOptions:\n --target <site-dir> An already-scaffolded repo (contains\n architecture.yaml). Defaults to the current dir.\n --help, -h Print this message.\n\nWrites only the look: DESIGN.md (replaced whole by the design's, keeping the\nsite's Source template and Version lines), the theme: block of\ncontent/_site.mdx, the generated apps/web/lib/site-chrome.ts, the look fields\nof sections the site's pages already place, and the design/layout/theme record\nin .ailk-site-manifest.json.\nNever adds, removes or renames a route or page, never touches a content field\nor the brand: block, and leaves every page's structured data unchanged.\n\nA design other than standard needs a Pro or Pro Plus key: the site's own\nailk.key (put one there with create-ailk license set <key>), else\nAILK_LICENSE_KEY. A malformed package, a preset the validator refuses or a\nmissing key is refused before any file is written.\n\nA preset sets the look fields it names and never unsets one: a field it does\nnot name keeps whatever value the page has. Running it again changes nothing.\n";
69
+ export declare const APPLY_DESIGN_HELP = "create-ailk apply-design \u2014 make an existing site wear a design.\n\nUsage:\n create-ailk apply-design <name> [--target <site-dir>]\n create-ailk apply-design <path> [--target <site-dir>]\n\nArguments:\n <name|path> A shipped design (standard, minimalism), or a\n directory holding a DESIGN.md. A relative path\n resolves from the current directory.\n\nOptions:\n --target <site-dir> An already-scaffolded repo (contains\n architecture.yaml). Defaults to the current dir.\n --help, -h Print this message.\n\nWrites only the look: DESIGN.md (replaced whole by the design's, keeping the\nsite's Source template and Version lines), the theme: block of\ncontent/_site.mdx, the generated apps/web/lib/site-chrome.ts, the generated\napps/web/lib/site-fonts.ts, the look fields of sections the site's pages\nalready place, and the design/layout/theme record in .ailk-site-manifest.json.\nNever adds, removes or renames a route or page, never touches a content field\n(except a placeholder home's sections, below) or the brand: block, and leaves\nevery other page's structured data unchanged.\n\nA design may seed the placeholder home's sections \u2014 its home arc \u2014 while they\nare still a placeholder: the shape's own, or a design's arc of them. It never\nchanges a home whose sections were edited; it reports it and leaves it. A\nseeded home takes the design's looks; look values on the sections it replaced\nare not kept. A design that ships no arc restores the shape's own sections on\na home that still wears another design's untouched arc.\n\nA design other than standard needs a Pro or Pro Plus key: the site's own\nailk.key (put one there with create-ailk license set <key>), else\nAILK_LICENSE_KEY. A malformed package, a preset the validator refuses or a\nmissing key is refused before any file is written.\n\nA preset sets the look fields it names and never unsets one: a field it does\nnot name keeps whatever value the page has. Running it again changes nothing.\n";
51
70
  /**
52
71
  * Parse the argv AFTER the `apply-design` word (D1). Strict `node:util`
53
72
  * parseArgs, like `parseSyncRoutesArgs`: exactly one positional, the design
@@ -57,7 +76,7 @@ export declare function parseApplyDesignArgs(argv: string[]): ApplyDesignArgs;
57
76
  /**
58
77
  * Apply a design to an existing site (D2–D7). Refuses before any write
59
78
  * (C1–C6); otherwise writes only the files whose text changes — page files,
60
- * `site-chrome.ts`, `DESIGN.md`, `_site.mdx`, then the manifest last.
79
+ * `site-chrome.ts`, `site-fonts.ts`, `DESIGN.md`, `_site.mdx`, then the manifest last.
61
80
  */
62
81
  export declare function applyDesign(opts: ApplyDesignOptions): Promise<ApplyDesignResult>;
63
82
  /** The changed-file report (D9). */
@@ -7,7 +7,9 @@
7
7
  * resolver and theme splicer, #6816's preset validator and applier, #6760's
8
8
  * design gate and #6813's manifest writer. Every refusal happens before the
9
9
  * first write (D3); every new text is planned in memory, then written in one
10
- * pass (D4). Routes, content fields, `brand:` and structured data never move.
10
+ * pass (D4). Routes, content fields, `brand:` and structured data never move —
11
+ * except a placeholder home's `sections:`, which a design's home arc may
12
+ * replace (#7743 D7).
11
13
  *
12
14
  * Reads no module URL of its own — every package directory it reads is passed in
13
15
  * — so Jest's CJS transform can import it, unlike `programmatic.ts`.
@@ -16,7 +18,8 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSy
16
18
  import { dirname, join, relative, resolve, sep } from "node:path";
17
19
  import { parseArgs } from "node:util";
18
20
  import { planDesignMd } from "./apply-design-md.js";
19
- import { compileDesignTheme } from "./design-compile.js";
21
+ import { compileDesignFonts, compileDesignTheme } from "./design-compile.js";
22
+ import { HOME_PAGE_PATH, designHomeSeeds, planDesignHome, shapeHomePath, shippedHomeSeeds, } from "./design-home.js";
20
23
  import { applyDesignPresets, readSectionLookCatalog, shippedPageTypes, validateDesignPresets, } from "./design-presets.js";
21
24
  import { resolveDesign, spliceThemeBlock } from "./design-scaffold.js";
22
25
  import { LICENSE_KEY_FILE } from "./lib/license-key-files.js";
@@ -24,7 +27,10 @@ import { CONVERSION_HINT_URL, LicenseRequiredError, assertDesignAllowed, deriveL
24
27
  import { LEVEL_LABEL } from "./lib/module-tier.js";
25
28
  import { ParseError } from "./parse-args.js";
26
29
  import { SITE_CHROME_PATH, renderSiteChrome, resolveSiteChrome, } from "./site-chrome.js";
27
- import { SITE_ROSTER_PATH, planSiteManifestDesign, readSiteRoster, } from "./site-roster.js";
30
+ import { SITE_FONTS_PATH, renderSiteFonts } from "./site-fonts.js";
31
+ import { SITE_ROSTER_PATH, planHomeRecord, planSiteManifestDesign, readSiteRoster, } from "./site-roster.js";
32
+ /** The site's locale layout — the one reader of the generated site-fonts.ts. */
33
+ const SITE_LAYOUT_PATH = "apps/web/app/[locale]/layout.tsx";
28
34
  export const APPLY_DESIGN_HELP = `create-ailk apply-design — make an existing site wear a design.
29
35
 
30
36
  Usage:
@@ -43,11 +49,19 @@ Options:
43
49
 
44
50
  Writes only the look: DESIGN.md (replaced whole by the design's, keeping the
45
51
  site's Source template and Version lines), the theme: block of
46
- content/_site.mdx, the generated apps/web/lib/site-chrome.ts, the look fields
47
- of sections the site's pages already place, and the design/layout/theme record
48
- in .ailk-site-manifest.json.
52
+ content/_site.mdx, the generated apps/web/lib/site-chrome.ts, the generated
53
+ apps/web/lib/site-fonts.ts, the look fields of sections the site's pages
54
+ already place, and the design/layout/theme record in .ailk-site-manifest.json.
49
55
  Never adds, removes or renames a route or page, never touches a content field
50
- or the brand: block, and leaves every page's structured data unchanged.
56
+ (except a placeholder home's sections, below) or the brand: block, and leaves
57
+ every other page's structured data unchanged.
58
+
59
+ A design may seed the placeholder home's sections — its home arc — while they
60
+ are still a placeholder: the shape's own, or a design's arc of them. It never
61
+ changes a home whose sections were edited; it reports it and leaves it. A
62
+ seeded home takes the design's looks; look values on the sections it replaced
63
+ are not kept. A design that ships no arc restores the shape's own sections on
64
+ a home that still wears another design's untouched arc.
51
65
 
52
66
  A design other than standard needs a Pro or Pro Plus key: the site's own
53
67
  ailk.key (put one there with create-ailk license set <key>), else
@@ -118,7 +132,7 @@ function readSiteKey(targetAbs, env) {
118
132
  /**
119
133
  * Apply a design to an existing site (D2–D7). Refuses before any write
120
134
  * (C1–C6); otherwise writes only the files whose text changes — page files,
121
- * `site-chrome.ts`, `DESIGN.md`, `_site.mdx`, then the manifest last.
135
+ * `site-chrome.ts`, `site-fonts.ts`, `DESIGN.md`, `_site.mdx`, then the manifest last.
122
136
  */
123
137
  export async function applyDesign(opts) {
124
138
  const targetAbs = resolve(opts.targetDir);
@@ -146,13 +160,18 @@ export async function applyDesign(opts) {
146
160
  const { pkg } = resolved;
147
161
  const hasPresets = Object.keys(pkg.sections).length > 0;
148
162
  // C4 — the presets, validated exactly as scaffold() validates them.
163
+ let lookCatalog;
164
+ const catalog = () => (lookCatalog ??= readSectionLookCatalog(opts.catalogPath));
149
165
  if (hasPresets) {
150
166
  validateDesignPresets(pkg.sections, {
151
- catalog: readSectionLookCatalog(opts.catalogPath),
167
+ catalog: catalog(),
152
168
  pageTypes: shippedPageTypes(opts.siteTemplatesDir),
153
169
  file: join(resolved.dir, "DESIGN.md"),
154
170
  });
155
171
  }
172
+ // #7743 D7 — the home seeds (the design's and every shipped design's), validated beside the presets.
173
+ const designSeeds = designHomeSeeds(resolved, { designsDir: opts.designsDir, catalog });
174
+ const shippedSeeds = shippedHomeSeeds({ designsDir: opts.designsDir, catalog });
156
175
  // C5 — the design gate, fed the site's own key (D6). A Free design reads no key.
157
176
  if (!resolved.free) {
158
177
  const key = readSiteKey(targetAbs, env);
@@ -179,11 +198,45 @@ export async function applyDesign(opts) {
179
198
  const chromePath = join(targetAbs, SITE_CHROME_PATH);
180
199
  const chromeCurrent = existsSync(chromePath) ? readFileSync(chromePath, "utf8") : undefined;
181
200
  const chromeNext = renderSiteChrome(chrome);
201
+ // #7720 D4.2 — a face-less design never adds the file to a site that lacks it.
202
+ const fonts = compileDesignFonts(pkg);
203
+ const fontsPath = join(targetAbs, SITE_FONTS_PATH);
204
+ const fontsCurrent = existsSync(fontsPath) ? readFileSync(fontsPath, "utf8") : undefined;
205
+ const fontsNext = renderSiteFonts(fonts);
206
+ const writeFonts = fontsNext !== fontsCurrent && (fontsCurrent !== undefined || fonts !== undefined);
207
+ const fontFamilies = writeFonts && fonts !== undefined
208
+ ? [...new Set([fonts.display?.family, fonts.body?.family])].filter((family) => family !== undefined)
209
+ : undefined;
210
+ const layoutPath = join(targetAbs, SITE_LAYOUT_PATH);
211
+ const fontsUnwired = fontFamilies !== undefined &&
212
+ !(existsSync(layoutPath) && readFileSync(layoutPath, "utf8").includes("lib/site-fonts"));
182
213
  const designMdPath = join(targetAbs, "DESIGN.md");
183
214
  const designMdCurrent = existsSync(designMdPath) ? readFileSync(designMdPath, "utf8") : undefined;
184
215
  const designMdNext = planDesignMd(resolved.dir, targetAbs);
216
+ // #7743 D7 — the home is planned before the presets, so a seeded home is
217
+ // written once, carrying both. The one `planDesignHome` call site here.
218
+ const shape = roster?.template.shape;
219
+ const target = designSeeds.find((seed) => seed.shape === shape);
220
+ // The arc this site would take: the shape's seed, or (with no shape to pick one) any seed at all.
221
+ const homeArc = target !== undefined || (shape === undefined && designSeeds.length > 0);
222
+ const homePath = join(targetAbs, ...HOME_PAGE_PATH.split("/"));
223
+ const current = existsSync(homePath) ? readFileSync(homePath, "utf8") : undefined;
224
+ const homePlan = planDesignHome({
225
+ current,
226
+ shape,
227
+ target,
228
+ shapeHome: shape === undefined || current === undefined
229
+ ? ""
230
+ : readFileSync(shapeHomePath(shape, {
231
+ siteTemplatesDir: opts.siteTemplatesDir,
232
+ templatesDir: opts.templatesDir ?? join(dirname(opts.siteTemplatesDir), "templates"),
233
+ }), "utf8"),
234
+ shippedSeeds,
235
+ catalog: catalog(),
236
+ });
237
+ const homeAction = homePlan.outcome === "seeded" || homePlan.outcome === "restored" ? homePlan.outcome : undefined;
185
238
  const pageWrites = [];
186
- if (hasPresets && roster !== undefined) {
239
+ if (roster !== undefined && (hasPresets || homeAction !== undefined)) {
187
240
  const pageTypes = new Set();
188
241
  for (const row of roster.rows) {
189
242
  const pageType = row.value["pageType"];
@@ -191,17 +244,28 @@ export async function applyDesign(opts) {
191
244
  pageTypes.add(pageType);
192
245
  }
193
246
  const contentDir = join(targetAbs, "content");
247
+ // An authored home kept from a design's arc is reported as kept, so no preset dresses it either (D3, F4).
248
+ const homeKept = homePlan.outcome === "authored" && homeArc;
194
249
  const pages = listMdxFiles(contentDir)
195
- .filter((path) => path !== siteMdxPath)
196
- .map((path) => ({ path, text: readFileSync(path, "utf8") }));
197
- const presetResult = applyDesignPresets(pages, pkg.sections, { pageTypes });
198
- for (const page of presetResult.pages) {
250
+ .filter((path) => path !== siteMdxPath && !(homeKept && path === homePath))
251
+ .map((path) => ({
252
+ path,
253
+ text: path === homePath && homePlan.text !== undefined ? homePlan.text : readFileSync(path, "utf8"),
254
+ }));
255
+ const presetResult = hasPresets
256
+ ? applyDesignPresets(pages, pkg.sections, { pageTypes })
257
+ : { pages: [], applied: [] };
258
+ for (const page of pages) {
259
+ const preset = presetResult.pages.find((p) => p.path === page.path);
260
+ const home = page.path === homePath ? homeAction : undefined;
261
+ if (preset === undefined && home === undefined)
262
+ continue;
199
263
  const fields = [
200
264
  ...new Set(presetResult.applied
201
265
  .filter((a) => a.path === page.path)
202
266
  .map((a) => `${a.sectionType}.${a.field}`)),
203
267
  ].sort();
204
- pageWrites.push({ path: page.path, text: page.text, fields });
268
+ pageWrites.push({ path: page.path, text: preset?.text ?? page.text, fields, ...(home !== undefined ? { home } : {}) });
205
269
  }
206
270
  }
207
271
  let manifest = "no-roster";
@@ -210,23 +274,37 @@ export async function applyDesign(opts) {
210
274
  if (roster !== undefined && rosterAbs !== undefined) {
211
275
  if (roster.path === SITE_ROSTER_PATH) {
212
276
  manifestNext = planSiteManifestDesign(readFileSync(rosterAbs, "utf8"), { design: resolved.recorded, layout: chrome, theme }, rosterAbs);
277
+ // #7743 D6 — the `/` row records the home as written, in the same single write.
278
+ const home = pageWrites.find((page) => page.home !== undefined);
279
+ if (home !== undefined) {
280
+ manifestNext =
281
+ planHomeRecord(manifestNext ?? readFileSync(rosterAbs, "utf8"), home.text, catalog()) ?? manifestNext;
282
+ }
213
283
  manifest = manifestNext === undefined ? "unchanged" : "recorded";
214
284
  }
215
285
  else {
216
286
  manifest = "legacy-roster";
217
287
  }
218
288
  }
219
- // Write — page files, site-chrome.ts, DESIGN.md, _site.mdx, the manifest last (D4).
289
+ // Write — page files, site-chrome.ts, site-fonts.ts, DESIGN.md, _site.mdx, the manifest last (D4).
220
290
  const changes = [];
221
291
  for (const page of pageWrites) {
222
292
  writeFileSync(page.path, page.text);
223
- changes.push({ path: siteRelative(targetAbs, page.path), kind: "preset", fields: page.fields });
293
+ const path = siteRelative(targetAbs, page.path);
294
+ changes.push(page.home !== undefined
295
+ ? { path, kind: "home", action: page.home, fields: page.fields }
296
+ : { path, kind: "preset", fields: page.fields });
224
297
  }
225
298
  if (chromeNext !== chromeCurrent) {
226
299
  mkdirSync(dirname(chromePath), { recursive: true });
227
300
  writeFileSync(chromePath, chromeNext);
228
301
  changes.push({ path: SITE_CHROME_PATH, kind: "layout" });
229
302
  }
303
+ if (writeFonts) {
304
+ mkdirSync(dirname(fontsPath), { recursive: true });
305
+ writeFileSync(fontsPath, fontsNext);
306
+ changes.push({ path: SITE_FONTS_PATH, kind: "fonts" });
307
+ }
230
308
  if (designMdNext !== designMdCurrent) {
231
309
  writeFileSync(designMdPath, designMdNext);
232
310
  changes.push({ path: "DESIGN.md", kind: "design" });
@@ -245,7 +323,11 @@ export async function applyDesign(opts) {
245
323
  display: resolved.display,
246
324
  changes,
247
325
  manifest,
326
+ home: homePlan.outcome,
327
+ ...(homeArc ? { homeArc: true } : {}),
248
328
  ...(roster !== undefined ? { rosterPath: roster.path } : {}),
329
+ ...(fontFamilies !== undefined ? { fonts: fontFamilies } : {}),
330
+ ...(fontsUnwired ? { fontsUnwired: true } : {}),
249
331
  };
250
332
  }
251
333
  /** The changed-file report (D9). */
@@ -257,11 +339,30 @@ export function formatApplyDesignReport(result) {
257
339
  else {
258
340
  lines.push(`[create-ailk] Applied design "${result.design}" to ${result.targetDir}`);
259
341
  for (const change of result.changes) {
260
- const suffix = change.kind === "preset" ? ` (${change.fields.join(", ")})` : "";
342
+ const suffix = change.kind === "preset"
343
+ ? ` (${change.fields.join(", ")})`
344
+ : change.kind === "home"
345
+ ? ` (${[change.action, change.fields.join(", ")].filter((part) => part !== "").join("; ")})`
346
+ : "";
261
347
  lines.push(` ${change.kind.padEnd(8)} ${change.path}${suffix}`);
262
348
  }
263
349
  const n = result.changes.length;
264
- lines.push(`${n} file${n === 1 ? "" : "s"} changed. Routes, page content and structured data are unchanged.`);
350
+ const homeWritten = result.changes.some((change) => change.kind === "home");
351
+ lines.push(`${n} file${n === 1 ? "" : "s"} changed. ${homeWritten
352
+ ? "Routes are unchanged; the home page's sections were replaced."
353
+ : "Routes and structured data are unchanged."}`);
354
+ }
355
+ if (result.home === "authored" && result.homeArc === true) {
356
+ lines.push(`home kept: ${HOME_PAGE_PATH} has authored sections — the ${result.design} home arc was not applied`);
357
+ }
358
+ else if (result.home === "no-shape" && result.homeArc === true) {
359
+ lines.push("home not seeded: the site records no shape");
360
+ }
361
+ if (result.fonts !== undefined) {
362
+ lines.push(`fonts: ${result.fonts.join(", ")} load at next build through next/font/google — a family that is not a variable Google font fails there`);
363
+ }
364
+ if (result.fontsUnwired === true) {
365
+ lines.push(`fonts not loaded: ${SITE_LAYOUT_PATH} predates lib/site-fonts — update the layout from the current create-ailk overlay`);
265
366
  }
266
367
  if (result.manifest === "legacy-roster") {
267
368
  lines.push(`manifest not recorded: ${result.rosterPath} predates the design record`);
package/dist/cli.js CHANGED
@@ -201,6 +201,7 @@ async function main() {
201
201
  targetDir: args.target ?? process.cwd(),
202
202
  designsDir: resolve(PKG_ROOT, "site-templates", "designs"),
203
203
  siteTemplatesDir: resolve(PKG_ROOT, "site-templates"),
204
+ templatesDir: resolve(PKG_ROOT, "templates"),
204
205
  catalogPath: resolve(PKG_ROOT, "component-catalog.json"),
205
206
  });
206
207
  process.stdout.write(formatApplyDesignReport(result));
@@ -15,7 +15,7 @@
15
15
  * this compiles to what `SiteThemeSchema` accepts is test-only, by relative
16
16
  * path, in `design-compile.test.ts`.
17
17
  */
18
- import type { DesignPackage } from "./design-package.js";
18
+ import { type DesignPackage } from "./design-package.js";
19
19
  /** The nine runtime colour slots this compile emits, per mode (D4). */
20
20
  export type RuntimeColorSlot = "bg" | "bgMuted" | "surface" | "border" | "text" | "textMuted" | "primary" | "primaryFg" | "accent";
21
21
  /** One mode's compiled palette — the shape `SiteThemeSchema`'s partial palette accepts. */
@@ -28,11 +28,72 @@ export interface CompiledDesignTheme {
28
28
  eyebrowCase?: "upper" | "sentence";
29
29
  placeholders?: "outline" | "fill";
30
30
  checkmarks?: "circle" | "plain";
31
+ /** #7741 D7 — the button shape, copied from `treatments.buttonShape`. */
32
+ buttonShape?: "control" | "pill";
33
+ /** #7739 D2 — the ink of headings and labels on a dark ground, verbatim from the package. */
34
+ darkInk?: "fg" | "accent";
35
+ /** #7739 D2 — the stat-numeral treatment, verbatim from the package. */
36
+ figures?: "platform" | "display";
37
+ /**
38
+ * #7720 D2 — the display leading pair, present only when the display role
39
+ * declares `lineHeightMobile`; or, under `typeScale: design` (#7739 D2.2),
40
+ * every declared field of the five compiled roles.
41
+ */
42
+ typography?: CompiledTypography;
43
+ /**
44
+ * #7719 D6 — `"site"` paints dark-scoped bands with the site's own dark
45
+ * colours. Derived (never authored): emitted when the package names the
46
+ * `solid-dark` surface or declares `darkInk: accent` (#7739 D2.3); absent otherwise.
47
+ */
48
+ darkBands?: "platform" | "site";
31
49
  colors: {
32
50
  light: CompiledColorPalette;
33
51
  dark: CompiledColorPalette;
34
52
  };
35
53
  }
54
+ /**
55
+ * The compiled type roles (#7720, #7739 D2.2) — the shape `SiteThemeSchema`'s
56
+ * `typography` accepts. Each role is present only when it carries a field.
57
+ */
58
+ export interface CompiledTypography {
59
+ display?: {
60
+ fontSize?: string;
61
+ fontSizeMobile?: string;
62
+ fontWeight?: number;
63
+ lineHeight?: number;
64
+ lineHeightMobile?: number;
65
+ };
66
+ "heading-section"?: {
67
+ fontSize?: string;
68
+ fontSizeMobile?: string;
69
+ fontWeight?: number;
70
+ };
71
+ "heading-item"?: {
72
+ fontSize?: string;
73
+ fontSizeMobile?: string;
74
+ fontWeight?: number;
75
+ };
76
+ body?: {
77
+ fontSize?: string;
78
+ lineHeight?: number;
79
+ };
80
+ eyebrow?: {
81
+ fontSize?: string;
82
+ fontWeight?: number;
83
+ };
84
+ }
85
+ /** One sourced face (#7720 D3.1): the first family, unquoted, and the rest of the authored stack. */
86
+ export interface CompiledFace {
87
+ source: "google";
88
+ family: string;
89
+ /** The authored stack after the first comma, trimmed and verbatim; "" when there is none. */
90
+ fallback: string;
91
+ }
92
+ /** `compileDesignFonts`'s return shape — the faces `apps/web/lib/site-fonts.ts` loads. */
93
+ export interface CompiledDesignFonts {
94
+ display?: CompiledFace;
95
+ body?: CompiledFace;
96
+ }
36
97
  /** Every authored colour key the map may read from (design-package.ts D3). */
37
98
  type DesignAuthoredColorKey = "bg" | "fg" | "surface" | "surface-secondary" | "border" | "text-muted" | "brand" | "fg-on-brand" | "accent" | "bg-dark" | "fg-dark" | "border-dark" | "brand-dark" | "surface-dark" | "surface-secondary-dark" | "text-muted-dark" | "fg-on-brand-dark" | "accent-dark";
38
99
  /** The five named lossy cases with a row (D4) — an authored key absent falls back. */
@@ -59,7 +120,7 @@ export declare const DESIGN_COLOR_MAP: readonly DesignColorMapRow[];
59
120
  */
60
121
  export declare const DESIGN_COMPILE_LOSSY: {
61
122
  readonly L6: "accentFg, destructive and destructiveFg have no authored source and are never emitted by this compile — the partial palette inherits the platform default.";
62
- readonly L7: "typography, rounded and spacing are carried in the package and not compiled — SiteThemeSchema rejects fonts/radius/spacing by name.";
123
+ readonly L7: "typography, rounded and spacing are carried in the package and not compiled — SiteThemeSchema rejects fonts/radius/spacing by name. Three exceptions: the display role's leading pair (lineHeight, lineHeightMobile) compiles into theme.typography.display when lineHeightMobile is declared (#7720); the display/body faces a fontSource names compile to apps/web/lib/site-fonts.ts (#7720); and under treatments.typeScale: design the display, heading-section, heading-item, body and eyebrow roles' sizes, phone sizes, weights and leadings compile into theme.typography (#7739) — never their families or letterSpacing.";
63
124
  readonly L8: "an absent treatments key is omitted from the compiled theme block, not defaulted.";
64
125
  };
65
126
  /**
@@ -68,4 +129,11 @@ export declare const DESIGN_COMPILE_LOSSY: {
68
129
  * performs no I/O.
69
130
  */
70
131
  export declare function compileDesignTheme(pkg: DesignPackage): CompiledDesignTheme;
132
+ /**
133
+ * The faces a package's display and body roles source (#7720 D3.1), or
134
+ * `undefined` when neither declares `fontSource`. Pure, like
135
+ * `compileDesignTheme`; `readDesignPackage` has already constrained every
136
+ * stack it reads here.
137
+ */
138
+ export declare function compileDesignFonts(pkg: DesignPackage): CompiledDesignFonts | undefined;
71
139
  export {};
@@ -1,3 +1,32 @@
1
+ /**
2
+ * Compile a `DesignPackage` (design-package.ts) into the `theme:` block a
3
+ * site's `_site.mdx` carries (#6812 D4, spec docs/specs/6812-the-design-package-design-md-spec.md).
4
+ *
5
+ * `compileDesignTheme` is pure and idempotent: it reads `pkg` only, never
6
+ * mutates it, and has no I/O. The authored→runtime key map is the single
7
+ * exported table `DESIGN_COLOR_MAP` below — every lossy translation (a
8
+ * runtime slot the authored quartet has no direct source for) is named as a
9
+ * `fallback` + `lossy` case, and the cases with no row at all are named in
10
+ * `DESIGN_COMPILE_LOSSY`.
11
+ *
12
+ * This module imports nothing from `@working-theory/site-config` — the
13
+ * `theme:` block's shape is reproduced structurally here so `create-ailk`
14
+ * gains no runtime dependency (§ 1.5); the cross-package read that proves
15
+ * this compiles to what `SiteThemeSchema` accepts is test-only, by relative
16
+ * path, in `design-compile.test.ts`.
17
+ */
18
+ import { unquoteFamily } from "./design-package.js";
19
+ /**
20
+ * #7739 D2.2 — the fields `typeScale: design` compiles, per role, in emission
21
+ * order. Roles are emitted in this table's order.
22
+ */
23
+ const COMPILED_TYPE_FIELDS = {
24
+ display: ["fontSize", "fontSizeMobile", "fontWeight", "lineHeight", "lineHeightMobile"],
25
+ "heading-section": ["fontSize", "fontSizeMobile", "fontWeight"],
26
+ "heading-item": ["fontSize", "fontSizeMobile", "fontWeight"],
27
+ body: ["fontSize", "lineHeight"],
28
+ eyebrow: ["fontSize", "fontWeight"],
29
+ };
1
30
  /**
2
31
  * The authored→runtime key map (D4) — 18 rows, one per emitted runtime slot
3
32
  * per mode, in the overlay block's key order (bg bgMuted surface border text
@@ -62,9 +91,46 @@ export const DESIGN_COLOR_MAP = [
62
91
  */
63
92
  export const DESIGN_COMPILE_LOSSY = {
64
93
  L6: "accentFg, destructive and destructiveFg have no authored source and are never emitted by this compile — the partial palette inherits the platform default.",
65
- L7: "typography, rounded and spacing are carried in the package and not compiled — SiteThemeSchema rejects fonts/radius/spacing by name.",
94
+ L7: "typography, rounded and spacing are carried in the package and not compiled — SiteThemeSchema rejects fonts/radius/spacing by name. Three exceptions: the display role's leading pair (lineHeight, lineHeightMobile) compiles into theme.typography.display when lineHeightMobile is declared (#7720); the display/body faces a fontSource names compile to apps/web/lib/site-fonts.ts (#7720); and under treatments.typeScale: design the display, heading-section, heading-item, body and eyebrow roles' sizes, phone sizes, weights and leadings compile into theme.typography (#7739) — never their families or letterSpacing.",
66
95
  L8: "an absent treatments key is omitted from the compiled theme block, not defaulted.",
67
96
  };
97
+ /** Whether a package names the `solid-dark` surface — in its deck grounds or any section preset (#7719 D6). */
98
+ function usesSolidDark(pkg) {
99
+ if (pkg.layout.deckGrounds?.includes("solid-dark"))
100
+ return true;
101
+ return Object.values(pkg.sections).some((sections) => Object.values(sections).some((preset) => typeof preset === "object" &&
102
+ preset !== null &&
103
+ preset.surface === "solid-dark"));
104
+ }
105
+ /** The `typography` group a package compiles (#7720 D2, #7739 D2.2), or `undefined`. */
106
+ function compileTypography(pkg) {
107
+ if (pkg.treatments?.typeScale === "design") {
108
+ const result = {};
109
+ for (const [role, fields] of Object.entries(COMPILED_TYPE_FIELDS)) {
110
+ const def = pkg.typography[role];
111
+ if (def === undefined)
112
+ continue;
113
+ const compiled = {};
114
+ for (const field of fields) {
115
+ const value = def[field];
116
+ if (value !== undefined)
117
+ compiled[field] = value;
118
+ }
119
+ if (Object.keys(compiled).length > 0)
120
+ result[role] = compiled;
121
+ }
122
+ return Object.keys(result).length > 0 ? result : undefined;
123
+ }
124
+ const display = pkg.typography["display"];
125
+ if (display?.lineHeightMobile === undefined)
126
+ return undefined;
127
+ return {
128
+ display: {
129
+ ...(display.lineHeight !== undefined && { lineHeight: display.lineHeight }),
130
+ lineHeightMobile: display.lineHeightMobile,
131
+ },
132
+ };
133
+ }
68
134
  /**
69
135
  * Translate a `DesignPackage` into the `theme:` block `SiteThemeSchema.parse`
70
136
  * accepts (D4). Pure and idempotent — reads `pkg` only, mutates nothing, and
@@ -88,11 +154,41 @@ export function compileDesignTheme(pkg) {
88
154
  target[row.runtime] = value;
89
155
  }
90
156
  const treatments = pkg.treatments ?? {};
157
+ const typography = compileTypography(pkg);
158
+ // #7739 D2.3 — accent ink needs the site's own palette on the band, or the
159
+ // accent there would be the platform's.
160
+ const darkBands = usesSolidDark(pkg) || treatments.darkInk === "accent";
91
161
  return {
92
162
  ...(treatments.corners !== undefined && { corners: treatments.corners }),
93
163
  ...(treatments.eyebrowCase !== undefined && { eyebrowCase: treatments.eyebrowCase }),
94
164
  ...(treatments.placeholders !== undefined && { placeholders: treatments.placeholders }),
95
165
  ...(treatments.checkmarks !== undefined && { checkmarks: treatments.checkmarks }),
166
+ ...(treatments.buttonShape !== undefined && { buttonShape: treatments.buttonShape }),
167
+ ...(treatments.darkInk !== undefined && { darkInk: treatments.darkInk }),
168
+ ...(treatments.figures !== undefined && { figures: treatments.figures }),
169
+ ...(typography !== undefined && { typography }),
170
+ ...(darkBands && { darkBands: "site" }),
96
171
  colors: { light, dark },
97
172
  };
98
173
  }
174
+ /**
175
+ * The faces a package's display and body roles source (#7720 D3.1), or
176
+ * `undefined` when neither declares `fontSource`. Pure, like
177
+ * `compileDesignTheme`; `readDesignPackage` has already constrained every
178
+ * stack it reads here.
179
+ */
180
+ export function compileDesignFonts(pkg) {
181
+ const fonts = {};
182
+ for (const role of ["display", "body"]) {
183
+ const def = pkg.typography[role];
184
+ if (def?.fontSource === undefined || def.fontFamily === undefined)
185
+ continue;
186
+ const comma = def.fontFamily.indexOf(",");
187
+ fonts[role] = {
188
+ source: def.fontSource,
189
+ family: unquoteFamily(comma === -1 ? def.fontFamily : def.fontFamily.slice(0, comma)),
190
+ fallback: comma === -1 ? "" : def.fontFamily.slice(comma + 1).trim(),
191
+ };
192
+ }
193
+ return fonts.display !== undefined || fonts.body !== undefined ? fonts : undefined;
194
+ }
@@ -0,0 +1,74 @@
1
+ import { type SectionLookCatalog } from "./design-presets.js";
2
+ import type { ResolvedDesign } from "./design-scaffold.js";
3
+ /** Where a site's home page lives, site-relative. */
4
+ export declare const HOME_PAGE_PATH = "content/en/HomePage/home.mdx";
5
+ /** One `home/<shape>.mdx` file of a design package (D1). */
6
+ export interface HomeSeed {
7
+ readonly shape: string;
8
+ /** Absolute path, for refusals. */
9
+ readonly file: string;
10
+ readonly text: string;
11
+ }
12
+ export type HomeSeedOutcome = "seeded" | "restored" | "unchanged" | "authored" | "no-shape" | "no-home";
13
+ /**
14
+ * Read and validate `<dir>/home/*` (D1). Refuses with a `DesignPackageError`
15
+ * naming the file, before any write: a basename that is not a recognized
16
+ * shape, a page whose `type` is not `HomePage`, `sections` that cannot be
17
+ * located (or are not in the flow form a carry can splice), a section type
18
+ * the catalog does not carry, or a `logoCloud` whose `logos` is not `[]`.
19
+ */
20
+ export declare function readDesignHomeSeeds(dir: string, opts: {
21
+ catalog: () => SectionLookCatalog;
22
+ }): HomeSeed[];
23
+ /**
24
+ * The seeds a resolved design uses (D1): its own `home/`, else — a
25
+ * byte-identical copy of a shipped design IS that design — the shipped
26
+ * design's, else none.
27
+ */
28
+ export declare function designHomeSeeds(resolved: ResolvedDesign, opts: {
29
+ designsDir: string;
30
+ catalog: () => SectionLookCatalog;
31
+ }): HomeSeed[];
32
+ /** Every shipped design's seeds (D3's placeholder set reads them all). */
33
+ export declare function shippedHomeSeeds(opts: {
34
+ designsDir: string;
35
+ catalog: () => SectionLookCatalog;
36
+ }): HomeSeed[];
37
+ /** The shape's own home: its shape tree's, else the overlay's (spec § 1.4). */
38
+ export declare function shapeHomePath(shape: string, opts: {
39
+ siteTemplatesDir: string;
40
+ templatesDir: string;
41
+ }): string;
42
+ /**
43
+ * A home's content fingerprint (D3): per section in order, its type and its
44
+ * non-look `data` entries as `[key, normalized raw value]`, sorted by key.
45
+ * A reflow, a look-field change or a preset leaves it unchanged; ids, the
46
+ * title, the description and the body are not in it.
47
+ */
48
+ export declare function fingerprint(text: string, catalog: SectionLookCatalog, file?: string): string;
49
+ /**
50
+ * The seed's `sections:` value with D4's carry applied from `home`: a seed
51
+ * `logoCloud` (its `logos: []`) takes the raw `logos` of the home's first
52
+ * `logoCloud`, or is dropped when the home has none.
53
+ */
54
+ export declare function applySeed(home: string, seed: HomeSeed, file?: string): string;
55
+ /**
56
+ * Plan a site home under a design (D3). Writes nothing: `text` is the new
57
+ * home when the outcome is `seeded` or `restored`, and absent otherwise.
58
+ */
59
+ export declare function planDesignHome(opts: {
60
+ /** The site's content/en/HomePage/home.mdx, if any. */
61
+ current: string | undefined;
62
+ /** The roster's template shape. */
63
+ shape: string | undefined;
64
+ /** The applied design's seed for that shape (D1). */
65
+ target: HomeSeed | undefined;
66
+ /** The text of the shape's own home file (spec § 1.4). */
67
+ shapeHome: string;
68
+ /** Every shipped design's seeds (filtered to `shape` here). */
69
+ shippedSeeds: readonly HomeSeed[];
70
+ catalog: SectionLookCatalog;
71
+ }): {
72
+ outcome: HomeSeedOutcome;
73
+ text?: string;
74
+ };