create-ailk 0.8.0 → 0.9.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 (211) hide show
  1. package/README.md +68 -0
  2. package/component-catalog.json +279 -45
  3. package/dist/add-module.d.ts +20 -0
  4. package/dist/add-module.js +43 -7
  5. package/dist/add-route.d.ts +56 -0
  6. package/dist/add-route.js +286 -0
  7. package/dist/apply-design-md.d.ts +7 -0
  8. package/dist/apply-design-md.js +50 -0
  9. package/dist/apply-design.d.ts +64 -0
  10. package/dist/apply-design.js +273 -0
  11. package/dist/claude-md.d.ts +111 -0
  12. package/dist/claude-md.js +334 -0
  13. package/dist/cli.js +92 -4
  14. package/dist/derive-architecture.js +41 -36
  15. package/dist/design-compile.d.ts +71 -0
  16. package/dist/design-compile.js +98 -0
  17. package/dist/design-package.d.ts +83 -0
  18. package/dist/design-package.js +468 -0
  19. package/dist/design-presets.d.ts +86 -0
  20. package/dist/design-presets.js +642 -0
  21. package/dist/design-scaffold.d.ts +76 -0
  22. package/dist/design-scaffold.js +220 -0
  23. package/dist/index.d.ts +2 -1
  24. package/dist/index.js +4 -1
  25. package/dist/legacy-nav-renderers.d.ts +45 -0
  26. package/dist/legacy-nav-renderers.js +545 -0
  27. package/dist/lib/auth-secret-file.d.ts +19 -0
  28. package/dist/lib/auth-secret-file.js +112 -0
  29. package/dist/lib/design-checks.d.ts +78 -0
  30. package/dist/lib/design-checks.js +174 -0
  31. package/dist/lib/license-key-files.d.ts +9 -0
  32. package/dist/lib/license-key-files.js +2 -2
  33. package/dist/lib/module-license-gate.d.ts +19 -9
  34. package/dist/lib/module-license-gate.js +19 -8
  35. package/dist/lib/render-verdict.d.ts +52 -0
  36. package/dist/lib/render-verdict.js +77 -0
  37. package/dist/lib/scaffold-render-sweep.d.ts +105 -0
  38. package/dist/lib/scaffold-render-sweep.js +236 -0
  39. package/dist/module-architecture.d.ts +19 -16
  40. package/dist/module-architecture.js +64 -21
  41. package/dist/parse-args.d.ts +12 -2
  42. package/dist/parse-args.js +74 -13
  43. package/dist/programmatic.d.ts +30 -6
  44. package/dist/programmatic.js +206 -17
  45. package/dist/site-chrome.d.ts +19 -9
  46. package/dist/site-chrome.js +23 -13
  47. package/dist/site-manifest.d.ts +25 -0
  48. package/dist/site-manifest.js +254 -0
  49. package/dist/site-roster.d.ts +41 -0
  50. package/dist/site-roster.js +141 -8
  51. package/dist/starter-grid.d.ts +32 -16
  52. package/dist/starter-grid.js +129 -33
  53. package/dist/surfaces.d.ts +41 -9
  54. package/dist/surfaces.js +200 -83
  55. package/dist/template-manifest.d.ts +30 -13
  56. package/dist/template-manifest.js +49 -24
  57. package/package.json +5 -4
  58. package/site-templates/_base/content/en/UseCase/persona-1.mdx +7 -6
  59. package/site-templates/_base/content/en/UseCase/persona-2.mdx +7 -6
  60. package/site-templates/_base/content/en/UseCase/persona-3.mdx +7 -6
  61. package/site-templates/{minimalism-site/TEMPLATE-MANIFEST.json → aliases/minimalism-site.TEMPLATE-MANIFEST.json} +72 -40
  62. package/site-templates/designs/minimalism/DESIGN.md +98 -6
  63. package/site-templates/designs/minimalism/sample/README.md +48 -0
  64. package/site-templates/designs/standard/DESIGN.md +98 -6
  65. package/site-templates/marketing-site/DESIGN.md +98 -6
  66. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +60 -27
  67. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +50 -30
  68. package/site-templates/marketing-site-free/DESIGN.md +98 -6
  69. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +60 -24
  70. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +44 -27
  71. package/site-templates/marketing-site-free/ailk-template.json +1 -1
  72. package/site-templates/saas-product/DESIGN.md +98 -6
  73. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +127 -18
  74. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +74 -20
  75. package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
  76. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +127 -18
  77. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +74 -20
  78. package/site-templates/shapes/organization-service/ailk-template.json +1 -1
  79. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +127 -18
  80. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +75 -21
  81. package/site-templates/shapes/person-service/ailk-template.json +1 -1
  82. package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +1 -1
  83. package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +2 -2
  84. package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +4 -2
  85. package/site-templates/shapes/person-service/content/en/ServicesList/services.mdx +1 -1
  86. package/templates/.claude/rules/architecture.md +7 -0
  87. package/templates/.claude/skills/README.md +3 -2
  88. package/templates/.claude/skills/add-auth/references/env-vars.md +22 -15
  89. package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +3 -2
  90. package/templates/.claude/skills/add-page/SKILL.md +241 -238
  91. package/templates/.claude/skills/add-page/references/page-types.md +85 -76
  92. package/templates/.claude/skills/add-page/scripts/site-pages.mjs +87 -0
  93. package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +99 -0
  94. package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +8 -18
  95. package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +43 -0
  96. package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +52 -0
  97. package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +38 -0
  98. package/templates/.claude/skills/check-site/SKILL.md +23 -0
  99. package/templates/.claude/skills/fill-page/SKILL.md +26 -7
  100. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +1 -1
  101. package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +28 -11
  102. package/templates/.claude/skills/update-page/SKILL.md +117 -0
  103. package/templates/.claude/skills/update-page/scripts/update-page.ts +640 -0
  104. package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +31 -0
  105. package/templates/.claude/skills/upgrade-plan/SKILL.md +12 -8
  106. package/templates/.github/workflows/ci.yml +4 -0
  107. package/templates/.prettierignore +1 -0
  108. package/templates/CLAUDE.md +146 -103
  109. package/templates/DESIGN.md +98 -6
  110. package/templates/README.md +50 -38
  111. package/templates/apps/api/.env.example +2 -1
  112. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +2 -0
  113. package/templates/apps/api/src/__tests__/server.test.ts +116 -0
  114. package/templates/apps/api/src/lib/stripe.ts +9 -0
  115. package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +140 -0
  116. package/templates/apps/api/src/middleware/auth.ts +54 -0
  117. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +5 -2
  118. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +628 -0
  119. package/templates/apps/api/src/routes/checkout/sessions.ts +222 -24
  120. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -1
  121. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +5 -1
  122. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +521 -20
  123. package/templates/apps/api/src/routes/webhooks/stripe.ts +124 -32
  124. package/templates/apps/api/src/server.ts +28 -0
  125. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +90 -15
  126. package/templates/apps/api/src/services/checkout-buyer.ts +72 -0
  127. package/templates/apps/api/src/services/revalidation.ts +60 -15
  128. package/templates/apps/mcp/__tests__/tools/create_project.test.ts +30 -24
  129. package/templates/apps/mcp/src/tools/purchase.ts +4 -2
  130. package/templates/apps/web/.env.example +13 -0
  131. package/templates/apps/web/CLAUDE.md +22 -28
  132. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +1 -1
  133. package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +24 -0
  134. package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +17 -6
  135. package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +110 -0
  136. package/templates/apps/web/app/[locale]/[slug]/[detail]/page.tsx +60 -0
  137. package/templates/apps/web/app/[locale]/[slug]/page.tsx +1 -1
  138. package/templates/apps/web/app/[locale]/alternatives/__tests__/page.test.tsx +161 -0
  139. package/templates/apps/web/app/[locale]/alternatives/page.tsx +104 -0
  140. package/templates/apps/web/app/[locale]/{use-cases → for}/[slug]/page.tsx +4 -3
  141. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +73 -0
  142. package/templates/apps/web/app/[locale]/{use-cases → for}/page.tsx +8 -7
  143. package/templates/apps/web/app/[locale]/glossary/page.tsx +1 -1
  144. package/templates/apps/web/app/[locale]/layout.tsx +35 -19
  145. package/templates/apps/web/app/[locale]/newsletter/page.tsx +1 -1
  146. package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +1 -1
  147. package/templates/apps/web/app/[locale]/portfolio/page.tsx +1 -1
  148. package/templates/apps/web/app/[locale]/vs/[slug]/__tests__/page.test.tsx +112 -0
  149. package/templates/apps/web/app/[locale]/vs/[slug]/page.tsx +70 -0
  150. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +236 -44
  151. package/templates/apps/web/app/api/revalidate/route.ts +78 -36
  152. package/templates/apps/web/lib/__tests__/site-brand.test.ts +79 -0
  153. package/templates/apps/web/lib/__tests__/site-theme.test.ts +104 -11
  154. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +21 -4
  155. package/templates/apps/web/lib/locale-guard.ts +18 -0
  156. package/templates/apps/web/lib/meet-placeholder.ts +5 -0
  157. package/templates/apps/web/lib/nav-config.ts +14 -13
  158. package/templates/apps/web/lib/site-brand.tsx +13 -9
  159. package/templates/apps/web/lib/site-logo.tsx +36 -0
  160. package/templates/apps/web/lib/site-theme.ts +46 -26
  161. package/templates/apps/web/messages/en.json +23 -9
  162. package/templates/apps/web/middleware.ts +28 -4
  163. package/templates/content/en/BookCall/book-a-call.mdx +80 -0
  164. package/templates/content/en/BookCall/onsite-estimate.mdx +56 -0
  165. package/templates/content/en/BookCall/onsite-service.mdx +56 -0
  166. package/templates/content/en/BookCall/start-project.mdx +56 -0
  167. package/templates/content/en/ContactPage/contact.mdx +40 -1
  168. package/templates/content/en/HomePage/home.mdx +2 -6
  169. package/templates/content/en/LandingPage/meet.mdx +59 -0
  170. package/templates/content/en/UseCasesList/{use-cases.mdx → for.mdx} +19 -18
  171. package/templates/database/CHANGELOG.md +7 -0
  172. package/templates/database/package.json +1 -1
  173. package/templates/package.json +2 -1
  174. package/templates/tools/check-claude-md.ts +128 -0
  175. package/versions.json +33 -33
  176. package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -52
  177. package/site-templates/minimalism-site/DESIGN.md +0 -162
  178. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +0 -458
  179. package/site-templates/minimalism-site/ailk-template.json +0 -23
  180. package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +0 -110
  181. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -52
  182. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +0 -34
  183. package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +0 -57
  184. package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +0 -77
  185. package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +0 -73
  186. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -123
  187. package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +0 -52
  188. package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +0 -52
  189. package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +0 -52
  190. package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +0 -52
  191. package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +0 -38
  192. package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +0 -38
  193. package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +0 -38
  194. package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +0 -73
  195. package/site-templates/minimalism-site/flows/referral.flow.json +0 -71
  196. package/site-templates/minimalism-site/provenance.json +0 -4
  197. package/templates/.claude/skills/add-page/templates/route.tsx.tmpl +0 -28
  198. package/templates/apps/web/app/[locale]/book-a-call/page.tsx +0 -50
  199. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +0 -72
  200. /package/site-templates/{minimalism-site → designs/minimalism/sample}/content/en/HomePage/home.mdx +0 -0
  201. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-1.svg +0 -0
  202. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-2.svg +0 -0
  203. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-3.svg +0 -0
  204. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-4.svg +0 -0
  205. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-5.svg +0 -0
  206. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-6.svg +0 -0
  207. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/hero-backdrop.svg +0 -0
  208. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-1.svg +0 -0
  209. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-2.svg +0 -0
  210. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-3.svg +0 -0
  211. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/wordmark-placeholder.svg +0 -0
@@ -0,0 +1,254 @@
1
+ /**
2
+ * The site manifest's record of what the scaffold IS (#6944) — written into
3
+ * `<site>/.ailk-site-manifest.json` by `scaffold()` once every layer has
4
+ * landed.
5
+ *
6
+ * A template scaffold's manifest arrives as its template's generated
7
+ * `TEMPLATE-MANIFEST.json` (#6658 D7): the template's own `requires` and
8
+ * rows, and nothing about the run that installed it. A Pro `--shape` run of a
9
+ * Free template recorded `requires: ["marketing"]` and the Free rows, missing
10
+ * every page its module bundles landed. `planSiteManifest` adds:
11
+ *
12
+ * - `modules` — the effective module set, the one `architecture.yaml`
13
+ * declares. Skills read it for what the site can render;
14
+ * - `level` — ONLY when the licence-key path derived one
15
+ * (`resolveModuleSet` / `deriveLevel`). Never inferred from `modules`:
16
+ * the level comes only from the key. An alias or `--modules` keep run
17
+ * derives none and records none;
18
+ * - a row for every route present on disk — each module's contributed rows
19
+ * through `planRosterAppend` (add-module's own append), the sample pages
20
+ * a module's bundle installs (`sampleRows`), and a code-route row for any
21
+ * other page route under `apps/web/app/[locale]/` (`codeRouteRows`).
22
+ *
23
+ * Every byte already in the file is kept; the new rows and keys are rendered
24
+ * the way prettier prints the generator's JSON, so the file stays formatted.
25
+ *
26
+ * Pure module: no `import.meta.url`. Package-rooted directories are supplied
27
+ * by the caller, the same split as `site-roster.ts` / `programmatic.ts`.
28
+ */
29
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
30
+ import { join, relative, sep } from "node:path";
31
+ import { isDeepStrictEqual } from "node:util";
32
+ import { AddModuleRefusedError } from "./add-module-core.js";
33
+ import { MODULE_SLICES, normalizeModules } from "./module-architecture.js";
34
+ import { moduleRosterRows, planRosterAppend, } from "./site-roster.js";
35
+ import { TEMPLATE_MANIFESTS, rosterDeclares, templateTreeDir, } from "./template-manifest.js";
36
+ /** Prettier's default print width — the generator formats the manifest with it. */
37
+ const PRINT_WIDTH = 80;
38
+ /** Where the site's App Router pages live. */
39
+ const ROUTE_ROOT = join("apps", "web", "app", "[locale]");
40
+ const PAGE_FILE = /^page\.(tsx|ts|jsx|js|mdx)$/;
41
+ /**
42
+ * The generator's row for a content-less route (`build-template-manifest.mjs`:
43
+ * every row renders under the locale layout's MarketingShell, chrome on).
44
+ */
45
+ const CODE_ROUTE_ROW = {
46
+ kind: "route",
47
+ pageType: null,
48
+ schema: null,
49
+ shell: "MarketingShell",
50
+ chrome: "on",
51
+ sections: [],
52
+ };
53
+ /**
54
+ * The manifest's next text: `modules` (and `level`, when derived) after
55
+ * `requires`, and a row for every route present on disk.
56
+ *
57
+ * @throws AddModuleRefusedError when the file already records `modules` or
58
+ * `level`, or its layout is not the generator's (`rows` last, `requires`
59
+ * at the top level) — guessing would silently mis-record the site.
60
+ */
61
+ export function planSiteManifest(opts) {
62
+ const { roster } = opts;
63
+ for (const key of ["modules", "level"]) {
64
+ if (key in roster.doc) {
65
+ throw new AddModuleRefusedError(`${roster.path} already records "${key}"`);
66
+ }
67
+ }
68
+ const plan = planRosterAppend({
69
+ targetDir: opts.targetDir,
70
+ roster,
71
+ modules: opts.modules,
72
+ references: opts.modules.flatMap((m) => moduleRosterRows(m, opts.siteTemplatesDir)),
73
+ siteTemplatesDir: opts.siteTemplatesDir,
74
+ templatesDir: opts.templatesDir,
75
+ markers: opts.markers,
76
+ });
77
+ const appended = plan.next ?? roster.text;
78
+ const declared = new Set(JSON.parse(appended).rows.map((row) => row["route"]));
79
+ const extra = [
80
+ ...sampleRows(opts, declared),
81
+ ...codeRouteRows(opts.targetDir, declared),
82
+ ];
83
+ const withRows = appendRows(appended, extra, roster);
84
+ return insertRecord(withRows, opts.modules, opts.level, roster.path);
85
+ }
86
+ /**
87
+ * The rows for the sample pages a module's bundle installs through its
88
+ * `installMap` (`signal-collectors`' /projects family) that no row names yet:
89
+ * each is the row a template requiring the module declares for that content,
90
+ * as the generator wrote it into the template's shipped manifest. A
91
+ * `contentDir` row lists the site's own seeds. `declared` gains each route.
92
+ */
93
+ function sampleRows(opts, declared) {
94
+ const out = [];
95
+ for (const moduleName of normalizeModules([...opts.modules])) {
96
+ const installed = Object.values(MODULE_SLICES[moduleName].installMap ?? {})
97
+ .filter((to) => to.startsWith("content/en/") && to.endsWith(".mdx"))
98
+ .filter((to) => existsSync(join(opts.targetDir, to)));
99
+ if (installed.length === 0)
100
+ continue;
101
+ for (const template of Object.values(TEMPLATE_MANIFESTS)) {
102
+ if (!template.requires.includes(moduleName))
103
+ continue;
104
+ const shippedPath = join(opts.siteTemplatesDir, templateTreeDir(template.name), "TEMPLATE-MANIFEST.json");
105
+ if (!existsSync(shippedPath))
106
+ continue;
107
+ const shipped = JSON.parse(readFileSync(shippedPath, "utf8")).rows;
108
+ for (const row of template.roster) {
109
+ if (declared.has(row.route))
110
+ continue;
111
+ if (!installed.some((path) => rosterDeclares([row], path)))
112
+ continue;
113
+ const value = shipped.find((r) => r["route"] === row.route);
114
+ if (value === undefined)
115
+ continue;
116
+ declared.add(row.route);
117
+ out.push(row.contentDir !== undefined && "seeds" in value
118
+ ? {
119
+ ...value,
120
+ seeds: listMdx(join(opts.targetDir, row.contentDir), opts.targetDir),
121
+ }
122
+ : value);
123
+ }
124
+ }
125
+ }
126
+ return out;
127
+ }
128
+ /** A code-route row for every page route on disk that `declared` does not name. */
129
+ function codeRouteRows(targetDir, declared) {
130
+ return routesOnDisk(targetDir)
131
+ .filter((route) => !declared.has(route))
132
+ .map((route) => ({ route, ...CODE_ROUTE_ROW }));
133
+ }
134
+ /**
135
+ * Every route the site's App Router serves from a page file, sorted: route
136
+ * groups `(x)` dropped, a trailing optional catch-all `[[...x]]` folded into
137
+ * its parent (docs-blog's `/blog/[[...slug]]` serves `/blog`). The root
138
+ * content renderers (`/[slug]`, `/[slug]/[detail]`) are not routes of their
139
+ * own — they are how the content rows render — so they are left out.
140
+ */
141
+ function routesOnDisk(targetDir) {
142
+ const root = join(targetDir, ROUTE_ROOT);
143
+ const routes = new Set();
144
+ const stack = existsSync(root) ? [root] : [];
145
+ for (let cur = stack.pop(); cur !== undefined; cur = stack.pop()) {
146
+ const entries = readdirSync(cur, { withFileTypes: true });
147
+ for (const entry of entries) {
148
+ if (entry.isDirectory())
149
+ stack.push(join(cur, entry.name));
150
+ }
151
+ if (!entries.some((entry) => entry.isFile() && PAGE_FILE.test(entry.name))) {
152
+ continue;
153
+ }
154
+ const segments = relative(root, cur)
155
+ .split(sep)
156
+ .filter((segment) => segment !== "" && !/^\(.+\)$/.test(segment));
157
+ const last = segments[segments.length - 1];
158
+ if (last !== undefined && /^\[\[\.\.\..+\]\]$/.test(last))
159
+ segments.pop();
160
+ if (segments[0]?.startsWith("["))
161
+ continue;
162
+ routes.add(`/${segments.join("/")}`);
163
+ }
164
+ return [...routes].sort();
165
+ }
166
+ /** Every `.mdx` under `dir`, as sorted `/`-separated paths relative to `base`. */
167
+ function listMdx(dir, base) {
168
+ const out = [];
169
+ const stack = existsSync(dir) ? [dir] : [];
170
+ for (let cur = stack.pop(); cur !== undefined; cur = stack.pop()) {
171
+ for (const entry of readdirSync(cur, { withFileTypes: true })) {
172
+ const abs = join(cur, entry.name);
173
+ if (entry.isDirectory())
174
+ stack.push(abs);
175
+ else if (entry.isFile() && entry.name.endsWith(".mdx")) {
176
+ out.push(relative(base, abs).split(sep).join("/"));
177
+ }
178
+ }
179
+ }
180
+ return out.sort();
181
+ }
182
+ /**
183
+ * `value` as prettier prints the generator's JSON with its value starting at
184
+ * `column` after a `prefix`-wide key: objects expanded one key per line, an
185
+ * array of scalars inline when the line fits, one element per line otherwise.
186
+ */
187
+ function renderJson(value, column, prefix = 0, trailing = 0) {
188
+ const pad = " ".repeat(column + 2);
189
+ const close = " ".repeat(column);
190
+ if (Array.isArray(value)) {
191
+ if (value.length === 0)
192
+ return "[]";
193
+ if (value.every((item) => item === null || typeof item !== "object")) {
194
+ const inline = `[${value.map((item) => JSON.stringify(item)).join(", ")}]`;
195
+ if (column + prefix + inline.length + trailing <= PRINT_WIDTH)
196
+ return inline;
197
+ }
198
+ const items = value.map((item, i) => pad + renderJson(item, column + 2, 0, i < value.length - 1 ? 1 : 0));
199
+ return `[\n${items.join(",\n")}\n${close}]`;
200
+ }
201
+ if (value !== null && typeof value === "object") {
202
+ const entries = Object.entries(value);
203
+ if (entries.length === 0)
204
+ return "{}";
205
+ const lines = entries.map(([key, item], i) => {
206
+ const name = `${JSON.stringify(key)}: `;
207
+ return (pad +
208
+ name +
209
+ renderJson(item, column + 2, name.length, i < entries.length - 1 ? 1 : 0));
210
+ });
211
+ return `{\n${lines.join(",\n")}\n${close}}`;
212
+ }
213
+ return JSON.stringify(value);
214
+ }
215
+ /** `text` with `rows` appended to its `rows` array — which the generator writes last. */
216
+ function appendRows(text, rows, roster) {
217
+ if (rows.length === 0)
218
+ return text;
219
+ const keys = Object.keys(JSON.parse(text));
220
+ if (keys[keys.length - 1] !== "rows") {
221
+ throw new AddModuleRefusedError(`${roster.path} does not end with its "rows" array, so rows cannot be appended`);
222
+ }
223
+ const close = text.lastIndexOf("]");
224
+ let end = close;
225
+ while (end > 0 && /\s/.test(text[end - 1]))
226
+ end--;
227
+ const empty = text[end - 1] === "[";
228
+ const column = roster.lead.length - roster.lead.lastIndexOf("\n") - 1;
229
+ const rendered = rows
230
+ .map((row) => roster.lead + renderJson(row, column))
231
+ .join(",");
232
+ return empty
233
+ ? text.slice(0, end) + rendered + roster.trail + text.slice(close)
234
+ : text.slice(0, end) + "," + rendered + text.slice(end);
235
+ }
236
+ /** `text` with `modules` (and `level`, when derived) inserted after the top-level `requires`. */
237
+ function insertRecord(text, modules, level, label) {
238
+ const anchor = '\n "requires": ';
239
+ const at = text.indexOf(anchor);
240
+ if (at === -1 || text.indexOf(anchor, at + 1) !== -1) {
241
+ throw new AddModuleRefusedError(`${label} has no single top-level "requires" key to record the module set beside`);
242
+ }
243
+ const end = text.indexOf("]", at) + 1;
244
+ const name = '"modules": ';
245
+ const record = `,\n ${name}${renderJson([...modules], 2, name.length, 1)}` +
246
+ (level !== undefined ? `,\n "level": ${JSON.stringify(level)}` : "");
247
+ const next = text.slice(0, end) + record + text.slice(end);
248
+ const doc = JSON.parse(next);
249
+ if (!isDeepStrictEqual(doc["modules"], [...modules]) ||
250
+ doc["level"] !== level) {
251
+ throw new AddModuleRefusedError(`${label}: the module set could not be recorded beside "requires"`);
252
+ }
253
+ return next;
254
+ }
@@ -1,5 +1,7 @@
1
+ import type { CompiledDesignTheme } from "./design-compile.js";
1
2
  import { type PageRosterRow } from "./module-architecture.js";
2
3
  import type { ModuleName } from "./parse-args.js";
4
+ import type { ResolvedSiteChrome } from "./site-chrome.js";
3
5
  import { type MarkerMap } from "./substitute.js";
4
6
  import { type TemplateManifest } from "./template-manifest.js";
5
7
  /** The site-root file that is the roster of record (#6658 D7). */
@@ -55,6 +57,25 @@ export interface ModuleRosterRow {
55
57
  * not declare is refused: guessing would silently mis-roster the site.
56
58
  */
57
59
  export declare function readSiteRoster(targetDir: string): SiteRoster | undefined;
60
+ /** What `planSiteManifestDesign` writes when it rewrites the manifest. */
61
+ export interface SiteManifestDesignNext {
62
+ design: string;
63
+ layout: ResolvedSiteChrome;
64
+ theme: CompiledDesignTheme;
65
+ }
66
+ /**
67
+ * Plan the site manifest's `design`/`layout`/`theme` keys as a design's
68
+ * applied values (#6813 D6) — `undefined` when the file already carries
69
+ * `next` (no rewrite, so the file keeps its bytes byte-for-byte), else the
70
+ * text with each CHANGED key's value spliced in place, located with the same
71
+ * `findKey`/`endOfValue` scanner `planRosterAppend` uses. `rows`,
72
+ * `generatedHash` and every other byte stay untouched.
73
+ *
74
+ * @throws AddModuleRefusedError when `text` is not a JSON object, or is
75
+ * missing `design`, `layout` or `theme` — a manifest predating #6658 D7
76
+ * never reaches this function (`scaffold()` guards on the file's presence).
77
+ */
78
+ export declare function planSiteManifestDesign(text: string, next: SiteManifestDesignNext, label: string): string | undefined;
58
79
  /**
59
80
  * The generator's row for each of a module's `pageContributions`, in
60
81
  * declaration order — read from the committed manifest of the first
@@ -63,6 +84,26 @@ export declare function readSiteRoster(targetDir: string): SiteRoster | undefine
63
84
  * of its forms. A route no shipped manifest carries is refused.
64
85
  */
65
86
  export declare function moduleRosterRows(moduleName: ModuleName, siteTemplatesDir: string): ModuleRosterRow[];
87
+ /**
88
+ * The Free `/meet` placeholder seeds on `route` (#6942): the keyless Free
89
+ * template's own `content` on a route the `scheduling` module contributes
90
+ * (`FREE_MEET_PLACEHOLDER_ROWS`, `starter-grid.ts`, read through the one
91
+ * registry that composes it). Empty for any other route. Throws when the Free
92
+ * template carries none at all, so a renamed template or a moved row fails
93
+ * loudly instead of re-opening the prune #6942 closed.
94
+ */
95
+ export declare function meetPlaceholderSeeds(route: string): readonly string[];
96
+ /**
97
+ * `roster` with the Free `/meet` placeholder seeds folded onto the
98
+ * `scheduling` module's code-route rows (#6942) — a row on a placeholder
99
+ * route that names no `content` of its own. Unconfigured, the module's
100
+ * `/meet` and `/meet/[slug]` pages hand off to the placeholder routes, so a
101
+ * seed there is still served and still declared: the scaffold's seed prune
102
+ * (`programmatic.ts`) and the site roster check read the roster through
103
+ * this. A row with its own `content` (the Free placeholder row itself) and a
104
+ * row on any other route are returned as is.
105
+ */
106
+ export declare function withMeetPlaceholderSeeds(roster: readonly PageRosterRow[]): PageRosterRow[];
66
107
  /** What becomes of a superseded template row's seed file. */
67
108
  export type SupersededSeed = {
68
109
  path: string;
@@ -30,7 +30,7 @@ import { AddModuleRefusedError, readArchitectureHeader, } from "./add-module-cor
30
30
  import { diffTwoStrings } from "./lib/extract-module-bundle.js";
31
31
  import { MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
32
32
  import { substituteMarkers } from "./substitute.js";
33
- import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, resolveTemplate, rosterDeclares, templateTreeDir, } from "./template-manifest.js";
33
+ import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, resolveTemplate, rosterDeclares, shippedManifestPath, templateTreeDir, } from "./template-manifest.js";
34
34
  /** The site-root file that is the roster of record (#6658 D7). */
35
35
  export const SITE_ROSTER_PATH = ".ailk-site-manifest.json";
36
36
  /**
@@ -39,8 +39,6 @@ export const SITE_ROSTER_PATH = ".ailk-site-manifest.json";
39
39
  * `SITE_ROSTER_PATH` is absent, and written back in place.
40
40
  */
41
41
  export const LEGACY_SITE_ROSTER_PATH = "TEMPLATE-MANIFEST.json";
42
- /** A template tree's own generated manifest — the file the site copy is made from. */
43
- const SHIPPED_MANIFEST_PATH = "TEMPLATE-MANIFEST.json";
44
42
  /** Prettier's default print width — the generator formats the manifest with it. */
45
43
  const PRINT_WIDTH = 80;
46
44
  // ── A string-aware JSON scanner, for the text splice ──────────────────────
@@ -183,13 +181,67 @@ export function readSiteRoster(targetDir) {
183
181
  }
184
182
  return { ...file, template, path };
185
183
  }
184
+ /** `value`, rendered the way prettier prints an object-only JSON value nested one level under the manifest's root object. */
185
+ function renderManifestValue(value) {
186
+ return JSON.stringify(value, null, 2)
187
+ .split("\n")
188
+ .map((line, i) => (i === 0 ? line : ` ${line}`))
189
+ .join("\n");
190
+ }
191
+ /**
192
+ * Plan the site manifest's `design`/`layout`/`theme` keys as a design's
193
+ * applied values (#6813 D6) — `undefined` when the file already carries
194
+ * `next` (no rewrite, so the file keeps its bytes byte-for-byte), else the
195
+ * text with each CHANGED key's value spliced in place, located with the same
196
+ * `findKey`/`endOfValue` scanner `planRosterAppend` uses. `rows`,
197
+ * `generatedHash` and every other byte stay untouched.
198
+ *
199
+ * @throws AddModuleRefusedError when `text` is not a JSON object, or is
200
+ * missing `design`, `layout` or `theme` — a manifest predating #6658 D7
201
+ * never reaches this function (`scaffold()` guards on the file's presence).
202
+ */
203
+ export function planSiteManifestDesign(text, next, label) {
204
+ let doc;
205
+ try {
206
+ doc = JSON.parse(text);
207
+ }
208
+ catch (err) {
209
+ throw new AddModuleRefusedError(`${label} is not JSON (${err.message})`);
210
+ }
211
+ if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
212
+ throw new AddModuleRefusedError(`${label} is not a JSON object`);
213
+ }
214
+ const parsed = doc;
215
+ const keys = ["design", "layout", "theme"];
216
+ for (const key of keys) {
217
+ if (!(key in parsed)) {
218
+ throw new AddModuleRefusedError(`${label} has no "${key}" key`);
219
+ }
220
+ }
221
+ if (keys.every((key) => isDeepStrictEqual(parsed[key], next[key]))) {
222
+ return undefined;
223
+ }
224
+ let result = text;
225
+ for (const key of keys) {
226
+ if (isDeepStrictEqual(parsed[key], next[key]))
227
+ continue;
228
+ const at = findKey(result, result.indexOf("{"), key);
229
+ if (at === undefined) {
230
+ throw new AddModuleRefusedError(`${label} has a "${key}" key the scanner cannot locate`);
231
+ }
232
+ const end = endOfValue(result, at.valueStart);
233
+ result = result.slice(0, at.valueStart) + renderManifestValue(next[key]) + result.slice(end);
234
+ }
235
+ return result;
236
+ }
186
237
  // ── D2: a module's rows, from the shipped manifests ───────────────────────
187
238
  /** A template's shipped manifest, or undefined when its tree ships none. */
188
239
  function readShippedManifest(siteTemplatesDir, templateName) {
189
- const path = join(siteTemplatesDir, templateName, SHIPPED_MANIFEST_PATH);
240
+ const relPath = shippedManifestPath(templateName);
241
+ const path = join(siteTemplatesDir, relPath);
190
242
  if (!existsSync(path))
191
243
  return undefined;
192
- return parseRosterFile(readFileSync(path, "utf8"), `site-templates/${templateName}/${SHIPPED_MANIFEST_PATH}`);
244
+ return parseRosterFile(readFileSync(path, "utf8"), `site-templates/${relPath}`);
193
245
  }
194
246
  /**
195
247
  * The generator's row for each of a module's `pageContributions`, in
@@ -216,14 +268,53 @@ export function moduleRosterRows(moduleName, siteTemplatesDir) {
216
268
  };
217
269
  return contributions.map((contribution) => {
218
270
  const isDir = contribution.contentDir !== undefined;
219
- const row = find(contribution.route, isDir ? "route" : undefined);
220
271
  const seeded = isDir ? find(contribution.route, "page") : undefined;
272
+ let row = find(contribution.route, isDir ? "route" : undefined);
273
+ if (row === undefined && isDir && seeded !== undefined) {
274
+ const contentDir = contribution.contentDir;
275
+ const installMap = MODULE_SLICES[moduleName].installMap ?? {};
276
+ const selfSeeds = Object.values(installMap).some((dest) => dest.startsWith(`${contentDir}/`));
277
+ if (selfSeeds)
278
+ row = deriveRouteRow(contribution.route, seeded);
279
+ }
221
280
  if (row === undefined || (isDir && seeded === undefined)) {
222
281
  throw new AddModuleRefusedError(`module "${moduleName}" contributes ${contribution.route}, but no shipped template manifest carries ${isDir ? "both forms of " : ""}its row, so it cannot be added to ${SITE_ROSTER_PATH}`);
223
282
  }
224
283
  return { module: moduleName, contribution, row, ...(seeded && { seeded }) };
225
284
  });
226
285
  }
286
+ /**
287
+ * Derive a `contentDir` contribution's code-route form from its seeded page
288
+ * form (spec 6892 D9) — for a SELF-SEEDING contribution, one whose
289
+ * contributing module's own `installMap` lands at least one file under the
290
+ * directory, so no template supplies a route form for it (every template
291
+ * taking the module gets the module's seeds, never zero of them). Returns
292
+ * field-for-field the route form `build-template-manifest.mjs` emits for a
293
+ * template that supplies no seeds of its own: `kind: "route"`, no
294
+ * `pageType`/`schema`/`contentDir`/`seeds`, the seeded form's own `shell` and
295
+ * `chrome`, no sections. Rendered the way a generated manifest's row-array
296
+ * element is indented (four spaces beyond `JSON.stringify`'s own two), so a
297
+ * derived row's `text` is byte-identical to the route-form slice a template
298
+ * manifest carries for the same contribution.
299
+ */
300
+ function deriveRouteRow(route, seeded) {
301
+ const value = {
302
+ route,
303
+ kind: "route",
304
+ pageType: null,
305
+ schema: null,
306
+ shell: seeded.value["shell"],
307
+ chrome: seeded.value["chrome"],
308
+ sections: [],
309
+ };
310
+ return {
311
+ value,
312
+ text: JSON.stringify(value, null, 2)
313
+ .split("\n")
314
+ .map((line, i) => (i === 0 ? line : ` ${line}`))
315
+ .join("\n"),
316
+ };
317
+ }
227
318
  /**
228
319
  * Every `.mdx` under `dir`, as `/`-separated paths relative to `base`. Walked
229
320
  * by hand: `Dirent.parentPath` and a reliable recursive `readdirSync` post-date
@@ -281,6 +372,40 @@ function expectedRow(ref, targetDir) {
281
372
  const seeds = listMdx(join(targetDir, dir), targetDir);
282
373
  return seeds.length > 0 ? withSeeds(ref.seeded, seeds) : ref.row;
283
374
  }
375
+ /**
376
+ * The Free `/meet` placeholder seeds on `route` (#6942): the keyless Free
377
+ * template's own `content` on a route the `scheduling` module contributes
378
+ * (`FREE_MEET_PLACEHOLDER_ROWS`, `starter-grid.ts`, read through the one
379
+ * registry that composes it). Empty for any other route. Throws when the Free
380
+ * template carries none at all, so a renamed template or a moved row fails
381
+ * loudly instead of re-opening the prune #6942 closed.
382
+ */
383
+ export function meetPlaceholderSeeds(route) {
384
+ const scheduling = new Set((MODULE_SLICES.scheduling.pageContributions ?? []).map((row) => row.route));
385
+ const placeholders = (TEMPLATE_MANIFESTS["marketing-site-free"]?.roster ?? []).filter((row) => scheduling.has(row.route) && row.content !== undefined);
386
+ if (placeholders.length === 0) {
387
+ throw new Error('meetPlaceholderSeeds: "marketing-site-free" carries no /meet placeholder row on a scheduling route');
388
+ }
389
+ return placeholders.find((row) => row.route === route)?.content ?? [];
390
+ }
391
+ /**
392
+ * `roster` with the Free `/meet` placeholder seeds folded onto the
393
+ * `scheduling` module's code-route rows (#6942) — a row on a placeholder
394
+ * route that names no `content` of its own. Unconfigured, the module's
395
+ * `/meet` and `/meet/[slug]` pages hand off to the placeholder routes, so a
396
+ * seed there is still served and still declared: the scaffold's seed prune
397
+ * (`programmatic.ts`) and the site roster check read the roster through
398
+ * this. A row with its own `content` (the Free placeholder row itself) and a
399
+ * row on any other route are returned as is.
400
+ */
401
+ export function withMeetPlaceholderSeeds(roster) {
402
+ return roster.map((row) => {
403
+ if (row.content !== undefined || row.contentDir !== undefined)
404
+ return row;
405
+ const seeds = meetPlaceholderSeeds(row.route);
406
+ return seeds.length === 0 ? row : { ...row, content: seeds };
407
+ });
408
+ }
284
409
  /** The bytes a scaffold installed at `path` from the template, base or overlay layer. */
285
410
  function installedSeed(opts, path) {
286
411
  const layers = [
@@ -313,7 +438,7 @@ export function planRosterAppend(opts) {
313
438
  const inSet = new Set(opts.modules);
314
439
  const refs = opts.references.filter((ref) => inSet.has(ref.module));
315
440
  const contributed = new Set(refs.map((ref) => ref.contribution.route));
316
- const shipped = readShippedManifest(opts.siteTemplatesDir, templateTreeDir(roster.template.name));
441
+ const shipped = readShippedManifest(opts.siteTemplatesDir, roster.template.name);
317
442
  const templateRows = [];
318
443
  const moduleRows = new Map();
319
444
  const superseded = [];
@@ -350,7 +475,13 @@ export function planRosterAppend(opts) {
350
475
  }
351
476
  const seeds = [];
352
477
  for (const row of superseded) {
478
+ // The module's row on this route still declares a Free `/meet`
479
+ // placeholder seed (#6942): its unconfigured pages serve it, so the seed
480
+ // is kept, never removed or reported.
481
+ const kept = meetPlaceholderSeeds(row.route);
353
482
  for (const path of row.content ?? []) {
483
+ if (kept.includes(path))
484
+ continue;
354
485
  const abs = join(targetDir, path);
355
486
  if (!existsSync(abs))
356
487
  continue;
@@ -487,11 +618,13 @@ export function siteRosterProblems(opts) {
487
618
  }),
488
619
  };
489
620
  });
621
+ // #6942 — the Free /meet placeholder seeds the scheduling module's rows keep.
622
+ const declared = withMeetPlaceholderSeeds(effective);
490
623
  const installed = [...layerInstalledSeeds(roster.template, modules, opts)]
491
624
  .filter((path) => existsSync(join(opts.targetDir, path)))
492
625
  .sort();
493
626
  for (const path of installed) {
494
- if (!rosterDeclares(effective, path)) {
627
+ if (!rosterDeclares(declared, path)) {
495
628
  problems.push(`${path}: installed but not declared by any roster row`);
496
629
  }
497
630
  }
@@ -56,6 +56,15 @@ export interface TemplateAlias {
56
56
  readonly categories: readonly CategorySlug[];
57
57
  readonly layout?: TemplateLayout;
58
58
  readonly differences?: AliasDifferences;
59
+ /**
60
+ * Whether this alias ships its own `site-templates/<name>/` tree (spec 6892
61
+ * D1). Absent means `true` — every alias but `minimalism-site` keeps its
62
+ * own directory. `false` declares that the alias resolves purely from the
63
+ * grid: its seed layer is its shape's tree (`templateTreeDir`), and its
64
+ * generated roster record lives under `site-templates/aliases/` rather
65
+ * than a directory of its own (`shippedManifestPath`).
66
+ */
67
+ readonly ownTree?: boolean;
59
68
  }
60
69
  /**
61
70
  * Each buildable shape's standard roster (#6753 D3) — the rows an alias starts
@@ -64,7 +73,9 @@ export interface TemplateAlias {
64
73
  * `organization-product` is today's base roster, the `/blog` teaser and the
65
74
  * portfolio pair. `organization-service`, `local-business-service` (#6757)
66
75
  * and `person-service` (#6758, roadmap § 6 Phase 2) are each their own rows,
67
- * the `/blog` teaser and the portfolio pair.
76
+ * the `/blog` teaser and the portfolio pair. Every shape's own rows carry
77
+ * `/sign-in` and `/sign-up` (#6797): the locale layout's header links both on
78
+ * every shape, so the roster of record lists them as code routes.
68
79
  */
69
80
  export declare const SHAPE_ROSTERS: Readonly<Record<BuildableShape, readonly TemplateRosterRow[]>>;
70
81
  /**
@@ -116,7 +127,7 @@ export declare const ALIASES: Readonly<{
116
127
  readonly modules: readonly ["marketing", "docs-blog", "scheduling", "signal-collectors"];
117
128
  readonly categories: readonly ["accounting", "legal", "consulting", "hvac", "roofing", "plumbing", "electrical", "landscaping", "auto-repair", "real-estate", "technology", "medical-specialty"];
118
129
  readonly differences: {
119
- readonly drop: readonly ["/blog"];
130
+ readonly drop: readonly ["/blog", "/meet", "/meet/[slug]"];
120
131
  readonly add: readonly import("./module-architecture.js").PageRosterRow[];
121
132
  readonly before: "/portfolio";
122
133
  };
@@ -136,7 +147,7 @@ export declare const ALIASES: Readonly<{
136
147
  readonly modules: readonly ["marketing", "docs-blog", "scheduling", "signal-collectors", "multi-tenant"];
137
148
  readonly categories: readonly ["technology", "medical-specialty"];
138
149
  readonly differences: {
139
- readonly drop: readonly ["/blog"];
150
+ readonly drop: readonly ["/blog", "/meet", "/meet/[slug]"];
140
151
  readonly add: readonly import("./module-architecture.js").PageRosterRow[];
141
152
  readonly before: "/portfolio";
142
153
  };
@@ -157,17 +168,19 @@ export declare const ALIASES: Readonly<{
157
168
  * (`rosterSurfaces`) nor its `_base`/overlay seed (#6646 D1).
158
169
  *
159
170
  * It declares NO `layout` block (#6638): the deck home, mark-only nav and
160
- * centered footer are the `minimalism` design's, supplied by
161
- * `DESIGN_LAYOUTS` from `design:` — the look any shape can wear. The
162
- * roster rows the `organization-service` shape does not derive
163
- * (`/reports`, `/referral`) are recorded with their reasons in this
164
- * template's TEMPLATE-MANIFEST.md § 3a.
171
+ * centered footer are the `minimalism` design's own `layout` (its
172
+ * `DESIGN.md`, resolved by `resolveDesign` off `design:` — #6813 D4) — the
173
+ * look any shape can wear. This alias ships no tree of its own (`ownTree:
174
+ * false`, spec 6892 D1); its GENERATED roster record (no narrative `.md`,
175
+ * D5) lives at `site-templates/aliases/minimalism-site.TEMPLATE-MANIFEST.json`.
165
176
  *
166
- * `/reports` and `/referral` carry no `GATEABLE_SURFACES` entry and need
167
- * none: both are single-segment `LandingPage` slugs served by the generic
168
- * `[slug]` route (#5469 D2), the same mechanism Free's `/how-it-works`
169
- * uses. `/reports/[slug]` (the reader) is the signal-collectors module's
170
- * own contributed row.
177
+ * `/reports` (a `ReportsList` hub over `ReportPage`) and `/referral` (a
178
+ * flow landing page) are signal-collectors module samples (spec 6892 D3);
179
+ * neither carries a `GATEABLE_SURFACES` entry: `/referral` is a
180
+ * single-segment `LandingPage` slug served by the generic `[slug]` route
181
+ * (#5469 D2), the same mechanism Free's `/how-it-works` uses, and `/reports`
182
+ * is its own routes-map row. `/reports/[slug]` (the reader) is the
183
+ * signal-collectors module's own contributed row.
171
184
  *
172
185
  * `/services/[slug]` declares its seed DIRECTORY as `contentDir`
173
186
  * (`content/en/Service`, as `routes-map.ts` records it). The three
@@ -176,8 +189,10 @@ export declare const ALIASES: Readonly<{
176
189
  * Since #6757, `organization-service` carries a real standard roster
177
190
  * (D1), so this alias's `differences` is additions and drops only: it
178
191
  * drops the `/blog` teaser (a Pro alias does not upsell its own level,
179
- * D8) and the portfolio pair (the reports are the proof, not a
180
- * portfolio), adds the signal-collectors samples, and INSERTS its two own
192
+ * D8), the portfolio pair (the reports are the proof, not a
193
+ * portfolio) and the two auth code routes the shape rosters (#6797 —
194
+ * this site has no `/sign-in`, above), adds the signal-collectors
195
+ * samples, and INSERTS its two own
181
196
  * rows — `/reports` before `/about`, `/referral` before `/faq` (D7) — to
182
197
  * reproduce its pre-#6757 row order (§ 1.2) exactly.
183
198
  */
@@ -186,8 +201,9 @@ export declare const ALIASES: Readonly<{
186
201
  readonly design: "minimalism";
187
202
  readonly modules: readonly ["marketing", "scheduling", "signal-collectors"];
188
203
  readonly categories: readonly ["real-estate", "technology", "legal"];
204
+ readonly ownTree: false;
189
205
  readonly differences: {
190
- readonly drop: readonly ["/blog", "/portfolio", "/portfolio/[slug]"];
206
+ readonly drop: readonly ["/sign-in", "/sign-up", "/blog", "/meet", "/meet/[slug]", "/portfolio", "/portfolio/[slug]"];
191
207
  readonly add: readonly import("./module-architecture.js").PageRosterRow[];
192
208
  readonly insert: readonly [{
193
209
  readonly before: "/about";