create-ailk 0.11.0 → 0.11.1

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 (37) hide show
  1. package/dist/lib/scaffold-render-sweep.d.ts +16 -0
  2. package/dist/lib/scaffold-render-sweep.js +52 -3
  3. package/package.json +1 -1
  4. package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +8 -8
  5. package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +8 -8
  6. package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +8 -8
  7. package/site-templates/shapes/organization-service/content/en/Service/first-engagement.mdx +8 -8
  8. package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +8 -8
  9. package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +8 -8
  10. package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +8 -8
  11. package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +8 -8
  12. package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +8 -8
  13. package/templates/.env.example +0 -4
  14. package/templates/apps/api/package.json +1 -1
  15. package/templates/apps/mcp/package.json +1 -1
  16. package/templates/apps/web/CLAUDE.md +1 -1
  17. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +0 -10
  18. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +0 -10
  19. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +1 -14
  20. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +0 -10
  21. package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +0 -10
  22. package/templates/apps/web/app/[locale]/for/[slug]/page.tsx +0 -10
  23. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +1 -14
  24. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +0 -10
  25. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +0 -10
  26. package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +0 -10
  27. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +0 -10
  28. package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +1 -15
  29. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +0 -10
  30. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +0 -10
  31. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +0 -10
  32. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +1 -14
  33. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +4 -16
  34. package/templates/apps/web/app/[locale]/vs/[slug]/__tests__/page.test.tsx +3 -15
  35. package/templates/apps/web/app/[locale]/vs/[slug]/page.tsx +0 -10
  36. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +1 -14
  37. package/versions.json +0 -1
@@ -103,3 +103,19 @@ export declare function assessConfigGatedRoute(input: {
103
103
  * a socket) throws rather than being silently followed or skipped.
104
104
  */
105
105
  export declare function listPublicFiles(dir: string): string[];
106
+ /** The slug the sweep requests to prove a detail route 404s an unknown slug. */
107
+ export declare const UNKNOWN_SLUG = "ailk-unknown-slug-7379";
108
+ /** `/features/[slug]` → `/features`; null unless `[slug]` is the one dynamic segment and the last. */
109
+ export declare function detailRoutePrefix(route: string): string | null;
110
+ /** The sorted basenames (sans `.mdx`) of the `*.mdx` files directly in `dir`; [] when `dir` is absent. */
111
+ export declare function listSeedSlugs(dir: string): string[];
112
+ /** An unknown slug must answer exactly 404, with no redirect. A 500 is the #7379 failure; a 200 means no 404 branch ran. */
113
+ export declare function assessUnknownSlug(input: {
114
+ route: string;
115
+ hops: RedirectHop[];
116
+ status: number | null;
117
+ }): SweepVerdict;
118
+ /** Fails, naming each route, when `.next/prerender-manifest.json`'s `dynamicRoutes` has a key under `/[locale]/`. */
119
+ export declare function assessPrerenderManifest(manifest: {
120
+ dynamicRoutes?: Record<string, unknown>;
121
+ }): SweepVerdict;
@@ -13,10 +13,17 @@
13
13
  * not cost a templates build, an install and a `next build`. Each is a pure
14
14
  * function of what the driver observed, so
15
15
  * `__tests__/verify-scaffold-render-sweep.test.ts` drives them directly. The
16
- * driver loads this module from `dist/`. Only `listPublicFiles` touches the
17
- * filesystem, and none of it touches the network.
16
+ * driver loads this module from `dist/`. Only `listPublicFiles` and
17
+ * `listSeedSlugs` touch the filesystem, and none of it touches the network.
18
+ *
19
+ * #7379 added two sweeps. A starter roster's detail row that names a seed
20
+ * directory (`/features/[slug]` → `content/en/Feature`) is probed: each seed
21
+ * must render, and `UNKNOWN_SLUG` must answer 404. And the build's
22
+ * `prerender-manifest.json` must register no `/[locale]/…` route as an
23
+ * on-demand prerender — under the cookie-reading locale layout, such a route
24
+ * answers 500 for every slug, seeded or not.
18
25
  */
19
- import { readdirSync } from "node:fs";
26
+ import { existsSync, readdirSync } from "node:fs";
20
27
  import { join } from "node:path";
21
28
  /** More hops than this is a redirect chain, not a render. */
22
29
  export const MAX_REDIRECTS = 5;
@@ -234,3 +241,45 @@ function listFilesUnder(dir, relPrefix) {
234
241
  }
235
242
  return out;
236
243
  }
244
+ /** The slug the sweep requests to prove a detail route 404s an unknown slug. */
245
+ export const UNKNOWN_SLUG = "ailk-unknown-slug-7379";
246
+ /** `/features/[slug]` → `/features`; null unless `[slug]` is the one dynamic segment and the last. */
247
+ export function detailRoutePrefix(route) {
248
+ const match = /^((?:\/[^/[\]]+)+)\/\[slug\]$/.exec(route);
249
+ return match?.[1] ?? null;
250
+ }
251
+ /** The sorted basenames (sans `.mdx`) of the `*.mdx` files directly in `dir`; [] when `dir` is absent. */
252
+ export function listSeedSlugs(dir) {
253
+ if (!existsSync(dir))
254
+ return [];
255
+ return readdirSync(dir, { withFileTypes: true })
256
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".mdx"))
257
+ .map((entry) => entry.name.slice(0, -".mdx".length))
258
+ .sort();
259
+ }
260
+ /** An unknown slug must answer exactly 404, with no redirect. A 500 is the #7379 failure; a 200 means no 404 branch ran. */
261
+ export function assessUnknownSlug(input) {
262
+ const { route, hops, status } = input;
263
+ const first = hops[0];
264
+ if (first !== undefined) {
265
+ return { ok: false, reason: `${route} -> ${first.status} redirect (an unknown slug must answer 404, not redirect)` };
266
+ }
267
+ if (status === 200)
268
+ return { ok: false, reason: `${route} -> 200 (no 404 branch ran for an unknown slug)` };
269
+ if (status !== 404)
270
+ return { ok: false, reason: `${route} -> status ${status} (expected 404 for an unknown slug)` };
271
+ return { ok: true, reason: "404" };
272
+ }
273
+ /** Fails, naming each route, when `.next/prerender-manifest.json`'s `dynamicRoutes` has a key under `/[locale]/`. */
274
+ export function assessPrerenderManifest(manifest) {
275
+ const locale = Object.keys(manifest.dynamicRoutes ?? {})
276
+ .filter((route) => route.startsWith("/[locale]/"))
277
+ .sort();
278
+ if (locale.length > 0) {
279
+ return {
280
+ ok: false,
281
+ reason: `on-demand prerender under the cookie-reading locale layout (500s every slug, #7379): ${locale.join(", ")}`,
282
+ };
283
+ }
284
+ return { ok: true, reason: "no /[locale] on-demand prerender route" };
285
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ailk",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Scaffolder for AI Launch Kit — `pnpm create ailk@latest <dir>` produces a starter repo consuming @working-theory/* from npm (per ADR 0007).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,29 +11,29 @@ schema:
11
11
  name: "Your Company"
12
12
  ---
13
13
 
14
- # {First job}
14
+ # \{First job}
15
15
 
16
- {Open with the situation a reader is in when they come looking for this. Two or
16
+ \{Open with the situation a reader is in when they come looking for this. Two or
17
17
  three sentences, no more — the detail belongs under the headings below.}
18
18
 
19
19
  ## What it covers
20
20
 
21
- - {The first thing you actually do}
22
- - {The second thing you actually do}
23
- - {The third thing you actually do}
21
+ - \{The first thing you actually do}
22
+ - \{The second thing you actually do}
23
+ - \{The third thing you actually do}
24
24
 
25
25
  ## How it runs
26
26
 
27
- {One paragraph on the shape of the visit: how it is scheduled, how long it
27
+ \{One paragraph on the shape of the visit: how it is scheduled, how long it
28
28
  takes, who turns up, and what you need from the customer beforehand.}
29
29
 
30
30
  ## What you end up with
31
31
 
32
- {One paragraph on the result. Be concrete — describe the property when the
32
+ \{One paragraph on the result. Be concrete — describe the property when the
33
33
  crew drives away.}
34
34
 
35
35
  ## What it costs
36
36
 
37
- {One line on the pricing shape — flat rate, hourly, or by the job — and what it
37
+ \{One line on the pricing shape — flat rate, hourly, or by the job — and what it
38
38
  excludes. Replace with your real numbers, or delete this section and let a
39
39
  call do it.}
@@ -11,29 +11,29 @@ schema:
11
11
  name: "Your Company"
12
12
  ---
13
13
 
14
- # {Second job}
14
+ # \{Second job}
15
15
 
16
- {Open with the situation a reader is in when they come looking for this. Two or
16
+ \{Open with the situation a reader is in when they come looking for this. Two or
17
17
  three sentences, no more — the detail belongs under the headings below.}
18
18
 
19
19
  ## What it covers
20
20
 
21
- - {The first thing you actually do}
22
- - {The second thing you actually do}
23
- - {The third thing you actually do}
21
+ - \{The first thing you actually do}
22
+ - \{The second thing you actually do}
23
+ - \{The third thing you actually do}
24
24
 
25
25
  ## How it runs
26
26
 
27
- {One paragraph on the shape of the visit: how it is scheduled, how long it
27
+ \{One paragraph on the shape of the visit: how it is scheduled, how long it
28
28
  takes, who turns up, and what you need from the customer beforehand.}
29
29
 
30
30
  ## What you end up with
31
31
 
32
- {One paragraph on the result. Be concrete — describe the property when the
32
+ \{One paragraph on the result. Be concrete — describe the property when the
33
33
  crew drives away.}
34
34
 
35
35
  ## What it costs
36
36
 
37
- {One line on the pricing shape — flat rate, hourly, or by the job — and what it
37
+ \{One line on the pricing shape — flat rate, hourly, or by the job — and what it
38
38
  excludes. Replace with your real numbers, or delete this section and let a
39
39
  call do it.}
@@ -11,29 +11,29 @@ schema:
11
11
  name: "Your Company"
12
12
  ---
13
13
 
14
- # {Third job}
14
+ # \{Third job}
15
15
 
16
- {Open with the situation a reader is in when they come looking for this. Two or
16
+ \{Open with the situation a reader is in when they come looking for this. Two or
17
17
  three sentences, no more — the detail belongs under the headings below.}
18
18
 
19
19
  ## What it covers
20
20
 
21
- - {The first thing you actually do}
22
- - {The second thing you actually do}
23
- - {The third thing you actually do}
21
+ - \{The first thing you actually do}
22
+ - \{The second thing you actually do}
23
+ - \{The third thing you actually do}
24
24
 
25
25
  ## How it runs
26
26
 
27
- {One paragraph on the shape of the visit: how it is scheduled, how long it
27
+ \{One paragraph on the shape of the visit: how it is scheduled, how long it
28
28
  takes, who turns up, and what you need from the customer beforehand.}
29
29
 
30
30
  ## What you end up with
31
31
 
32
- {One paragraph on the result. Be concrete — describe the property when the
32
+ \{One paragraph on the result. Be concrete — describe the property when the
33
33
  crew drives away.}
34
34
 
35
35
  ## What it costs
36
36
 
37
- {One line on the pricing shape — flat rate, hourly, or by the job — and what it
37
+ \{One line on the pricing shape — flat rate, hourly, or by the job — and what it
38
38
  excludes. Replace with your real numbers, or delete this section and let a
39
39
  call do it.}
@@ -10,29 +10,29 @@ schema:
10
10
  name: "Your Company"
11
11
  ---
12
12
 
13
- # {First engagement}
13
+ # \{First engagement}
14
14
 
15
- {Open with the situation a reader is in when they come looking for this. Two or
15
+ \{Open with the situation a reader is in when they come looking for this. Two or
16
16
  three sentences, no more — the detail belongs under the headings below.}
17
17
 
18
18
  ## What it covers
19
19
 
20
- - {The first thing you actually do}
21
- - {The second thing you actually do}
22
- - {The third thing you actually do}
20
+ - \{The first thing you actually do}
21
+ - \{The second thing you actually do}
22
+ - \{The third thing you actually do}
23
23
 
24
24
  ## How it runs
25
25
 
26
- {One paragraph on the shape of the engagement: how it starts, how long it runs,
26
+ \{One paragraph on the shape of the engagement: how it starts, how long it runs,
27
27
  who is involved on both sides, and what a week looks like.}
28
28
 
29
29
  ## What you end up with
30
30
 
31
- {One paragraph on the deliverable. Be concrete — name the artefact, not the
31
+ \{One paragraph on the deliverable. Be concrete — name the artefact, not the
32
32
  feeling.}
33
33
 
34
34
  ## What it costs
35
35
 
36
- {One line on the pricing shape — fixed fee, retainer, day rate — and what it
36
+ \{One line on the pricing shape — fixed fee, retainer, day rate — and what it
37
37
  excludes. Replace with your real numbers, or delete this section and let the
38
38
  call do it.}
@@ -10,29 +10,29 @@ schema:
10
10
  name: "Your Company"
11
11
  ---
12
12
 
13
- # {Second engagement}
13
+ # \{Second engagement}
14
14
 
15
- {Open with the situation a reader is in when they come looking for this. Two or
15
+ \{Open with the situation a reader is in when they come looking for this. Two or
16
16
  three sentences, no more — the detail belongs under the headings below.}
17
17
 
18
18
  ## What it covers
19
19
 
20
- - {The first thing you actually do}
21
- - {The second thing you actually do}
22
- - {The third thing you actually do}
20
+ - \{The first thing you actually do}
21
+ - \{The second thing you actually do}
22
+ - \{The third thing you actually do}
23
23
 
24
24
  ## How it runs
25
25
 
26
- {One paragraph on the shape of the engagement: how it starts, how long it runs,
26
+ \{One paragraph on the shape of the engagement: how it starts, how long it runs,
27
27
  who is involved on both sides, and what a week looks like.}
28
28
 
29
29
  ## What you end up with
30
30
 
31
- {One paragraph on the deliverable. Be concrete — name the artefact, not the
31
+ \{One paragraph on the deliverable. Be concrete — name the artefact, not the
32
32
  feeling.}
33
33
 
34
34
  ## What it costs
35
35
 
36
- {One line on the pricing shape — fixed fee, retainer, day rate — and what it
36
+ \{One line on the pricing shape — fixed fee, retainer, day rate — and what it
37
37
  excludes. Replace with your real numbers, or delete this section and let the
38
38
  call do it.}
@@ -10,29 +10,29 @@ schema:
10
10
  name: "Your Company"
11
11
  ---
12
12
 
13
- # {Third engagement}
13
+ # \{Third engagement}
14
14
 
15
- {Open with the situation a reader is in when they come looking for this. Two or
15
+ \{Open with the situation a reader is in when they come looking for this. Two or
16
16
  three sentences, no more — the detail belongs under the headings below.}
17
17
 
18
18
  ## What it covers
19
19
 
20
- - {The first thing you actually do}
21
- - {The second thing you actually do}
22
- - {The third thing you actually do}
20
+ - \{The first thing you actually do}
21
+ - \{The second thing you actually do}
22
+ - \{The third thing you actually do}
23
23
 
24
24
  ## How it runs
25
25
 
26
- {One paragraph on the shape of the engagement: how it starts, how long it runs,
26
+ \{One paragraph on the shape of the engagement: how it starts, how long it runs,
27
27
  who is involved on both sides, and what a week looks like.}
28
28
 
29
29
  ## What you end up with
30
30
 
31
- {One paragraph on the deliverable. Be concrete — name the artefact, not the
31
+ \{One paragraph on the deliverable. Be concrete — name the artefact, not the
32
32
  feeling.}
33
33
 
34
34
  ## What it costs
35
35
 
36
- {One line on the pricing shape — fixed fee, retainer, day rate — and what it
36
+ \{One line on the pricing shape — fixed fee, retainer, day rate — and what it
37
37
  excludes. Replace with your real numbers, or delete this section and let the
38
38
  call do it.}
@@ -10,29 +10,29 @@ schema:
10
10
  name: "Your Name"
11
11
  ---
12
12
 
13
- # {First engagement}
13
+ # \{First engagement}
14
14
 
15
- {Open with the situation a reader is in when they come looking for this. Two or
15
+ \{Open with the situation a reader is in when they come looking for this. Two or
16
16
  three sentences, no more — the detail belongs under the headings below.}
17
17
 
18
18
  ## What it covers
19
19
 
20
- - {The first thing you actually do}
21
- - {The second thing you actually do}
22
- - {The third thing you actually do}
20
+ - \{The first thing you actually do}
21
+ - \{The second thing you actually do}
22
+ - \{The third thing you actually do}
23
23
 
24
24
  ## How it runs
25
25
 
26
- {One paragraph on the shape of the engagement: how it starts, how long it runs,
26
+ \{One paragraph on the shape of the engagement: how it starts, how long it runs,
27
27
  who is involved, and what a week looks like.}
28
28
 
29
29
  ## What you end up with
30
30
 
31
- {One paragraph on the deliverable. Be concrete — name the artefact, not the
31
+ \{One paragraph on the deliverable. Be concrete — name the artefact, not the
32
32
  feeling.}
33
33
 
34
34
  ## What it costs
35
35
 
36
- {One line on the pricing shape — fixed fee, retainer, day rate — and what it
36
+ \{One line on the pricing shape — fixed fee, retainer, day rate — and what it
37
37
  excludes. Replace with your real numbers, or delete this section and let the
38
38
  call do it.}
@@ -10,29 +10,29 @@ schema:
10
10
  name: "Your Name"
11
11
  ---
12
12
 
13
- # {Second engagement}
13
+ # \{Second engagement}
14
14
 
15
- {Open with the situation a reader is in when they come looking for this. Two or
15
+ \{Open with the situation a reader is in when they come looking for this. Two or
16
16
  three sentences, no more — the detail belongs under the headings below.}
17
17
 
18
18
  ## What it covers
19
19
 
20
- - {The first thing you actually do}
21
- - {The second thing you actually do}
22
- - {The third thing you actually do}
20
+ - \{The first thing you actually do}
21
+ - \{The second thing you actually do}
22
+ - \{The third thing you actually do}
23
23
 
24
24
  ## How it runs
25
25
 
26
- {One paragraph on the shape of the engagement: how it starts, how long it runs,
26
+ \{One paragraph on the shape of the engagement: how it starts, how long it runs,
27
27
  who is involved, and what a week looks like.}
28
28
 
29
29
  ## What you end up with
30
30
 
31
- {One paragraph on the deliverable. Be concrete — name the artefact, not the
31
+ \{One paragraph on the deliverable. Be concrete — name the artefact, not the
32
32
  feeling.}
33
33
 
34
34
  ## What it costs
35
35
 
36
- {One line on the pricing shape — fixed fee, retainer, day rate — and what it
36
+ \{One line on the pricing shape — fixed fee, retainer, day rate — and what it
37
37
  excludes. Replace with your real numbers, or delete this section and let the
38
38
  call do it.}
@@ -10,29 +10,29 @@ schema:
10
10
  name: "Your Name"
11
11
  ---
12
12
 
13
- # {Third engagement}
13
+ # \{Third engagement}
14
14
 
15
- {Open with the situation a reader is in when they come looking for this. Two or
15
+ \{Open with the situation a reader is in when they come looking for this. Two or
16
16
  three sentences, no more — the detail belongs under the headings below.}
17
17
 
18
18
  ## What it covers
19
19
 
20
- - {The first thing you actually do}
21
- - {The second thing you actually do}
22
- - {The third thing you actually do}
20
+ - \{The first thing you actually do}
21
+ - \{The second thing you actually do}
22
+ - \{The third thing you actually do}
23
23
 
24
24
  ## How it runs
25
25
 
26
- {One paragraph on the shape of the engagement: how it starts, how long it runs,
26
+ \{One paragraph on the shape of the engagement: how it starts, how long it runs,
27
27
  who is involved, and what a week looks like.}
28
28
 
29
29
  ## What you end up with
30
30
 
31
- {One paragraph on the deliverable. Be concrete — name the artefact, not the
31
+ \{One paragraph on the deliverable. Be concrete — name the artefact, not the
32
32
  feeling.}
33
33
 
34
34
  ## What it costs
35
35
 
36
- {One line on the pricing shape — fixed fee, retainer, day rate — and what it
36
+ \{One line on the pricing shape — fixed fee, retainer, day rate — and what it
37
37
  excludes. Replace with your real numbers, or delete this section and let the
38
38
  call do it.}
@@ -50,10 +50,6 @@ CONTENT_ADAPTER=mdx
50
50
  # Developers → Webhooks → <your endpoint> → Signing secret.
51
51
  # Format: whsec_*. Server-only — must NOT be prefixed with NEXT_PUBLIC_.
52
52
  # STRIPE_WEBHOOK_SECRET=
53
- # Internal URL of the billing service — used by marketing-api's webhook
54
- # proxy (POST /v1/webhooks/stripe) to forward verified deliveries. Format:
55
- # https://<vercel-deployment>.vercel.app (no trailing slash, no path).
56
- # BILLING_SERVICE_URL=
57
53
 
58
54
  # ── Auth (Better Auth — apps/web only) ───────────────────────────────────────
59
55
  # BETTER_AUTH_URL=http://localhost:3000
@@ -49,7 +49,7 @@
49
49
  "@working-theory/stripe-client": "workspace:*",
50
50
  "@working-theory/validation": "workspace:*",
51
51
  "dotenv": "^17.4.2",
52
- "fastify": "^5.12.1",
52
+ "fastify": "^5.12.5",
53
53
  "pdf-lib": "^1.17.1",
54
54
  "pg": "^8.22.0",
55
55
  "yaml": "^2.9.0",
@@ -28,7 +28,7 @@
28
28
  "@working-theory/metadata": "workspace:*",
29
29
  "@working-theory/schema": "workspace:*",
30
30
  "@working-theory/validation": "workspace:*",
31
- "fastify": "^5.12.1",
31
+ "fastify": "^5.12.5",
32
32
  "yaml": "^2.9.0",
33
33
  "zod": "^4.2.1"
34
34
  },
@@ -14,7 +14,7 @@ Inherits root `CLAUDE.md`. This file adds web-specific rules.
14
14
 
15
15
  ## Always do first
16
16
 
17
- - For any UI work: invoke the `frontend-design` skill before writing code.
17
+ - For any UI work: read § Theme & styling and § Iterate visually below before writing code. To add a whole page, run the `/ailk:add-page` skill.
18
18
  - For any page-rendering or schema-emission work: read `packages/schema/README.md` (catalog) and `packages/schema/CLAUDE.md` (operational layer + component map) if you haven't this session.
19
19
  - For content authoring questions: read `content/CLAUDE.md` first.
20
20
  - For a full page build, follow the per-page loop (Content intent → Layout → Author content → Visual iterate). The visual-iterate step uses `screenshots/` as scratch (see § Iterate visually).
@@ -3,7 +3,6 @@
3
3
  // Careers list at /careers links here; each job posting lives at /careers/<slug>.
4
4
  import {
5
5
  getMdxSource as defaultGetMdxSource,
6
- getMdxSlugs,
7
6
  renderPage,
8
7
  renderPageMetadata,
9
8
  } from "@working-theory/page-renderer";
@@ -23,15 +22,6 @@ export async function generateMetadata({
23
22
  return renderPageMetadata({ type: "JobPosting", slug, locale });
24
23
  }
25
24
 
26
- export async function generateStaticParams({
27
- params,
28
- }: {
29
- params: { locale: string };
30
- }): Promise<{ slug: string }[]> {
31
- const slugs = await getMdxSlugs("JobPosting", params.locale);
32
- return slugs.map((slug: string) => ({ slug }));
33
- }
34
-
35
25
  export default async function Page({
36
26
  params,
37
27
  getMdxSource = defaultGetMdxSource,
@@ -2,7 +2,6 @@
2
2
  // next-mdx-remote/rsc, and emits a JSON-LD <script>. Unknown slugs trigger 404.
3
3
  import {
4
4
  getMdxSource as defaultGetMdxSource,
5
- getMdxSlugs,
6
5
  renderPage,
7
6
  renderPageMetadata,
8
7
  } from "@working-theory/page-renderer";
@@ -22,15 +21,6 @@ export async function generateMetadata({
22
21
  return renderPageMetadata({ type: "CaseStudy", slug, locale });
23
22
  }
24
23
 
25
- export async function generateStaticParams({
26
- params,
27
- }: {
28
- params: { locale: string };
29
- }): Promise<{ slug: string }[]> {
30
- const slugs = await getMdxSlugs("CaseStudy", params.locale);
31
- return slugs.map((slug: string) => ({ slug }));
32
- }
33
-
34
24
  export default async function Page({
35
25
  params,
36
26
  getMdxSource = defaultGetMdxSource,
@@ -1,11 +1,7 @@
1
1
  // Course detail page. Reads the Course MDX for the given slug, renders via
2
2
  // @working-theory/page-renderer, and emits a JSON-LD <script>. Unknown slugs trigger 404.
3
3
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
4
- import {
5
- getMdxSlugs,
6
- renderPage,
7
- renderPageMetadata,
8
- } from "@working-theory/page-renderer";
4
+ import { renderPage, renderPageMetadata } from "@working-theory/page-renderer";
9
5
  import type { Metadata } from "next";
10
6
  import type { ReactElement } from "react";
11
7
 
@@ -21,15 +17,6 @@ export async function generateMetadata({
21
17
  return renderPageMetadata({ type: "Course", slug, locale });
22
18
  }
23
19
 
24
- export async function generateStaticParams({
25
- params,
26
- }: {
27
- params: { locale: string };
28
- }): Promise<{ slug: string }[]> {
29
- const slugs = await getMdxSlugs("Course", params.locale);
30
- return slugs.map((slug) => ({ slug }));
31
- }
32
-
33
20
  export default async function Page({
34
21
  params,
35
22
  getMdxSource,
@@ -4,7 +4,6 @@ import {
4
4
  renderPage,
5
5
  renderPageMetadata,
6
6
  getMdxSource as defaultGetMdxSource,
7
- getMdxSlugs,
8
7
  } from "@working-theory/page-renderer";
9
8
  import type { Metadata } from "next";
10
9
  import { notFound } from "next/navigation";
@@ -22,15 +21,6 @@ export async function generateMetadata({
22
21
  return renderPageMetadata({ type: "Event", slug, locale });
23
22
  }
24
23
 
25
- export async function generateStaticParams({
26
- params,
27
- }: {
28
- params: { locale: string };
29
- }): Promise<{ slug: string }[]> {
30
- const slugs = await getMdxSlugs("Event", params.locale);
31
- return slugs.map((slug) => ({ slug }));
32
- }
33
-
34
24
  export default async function Page({
35
25
  params,
36
26
  getMdxSource = defaultGetMdxSource,
@@ -7,7 +7,6 @@
7
7
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
8
8
  import {
9
9
  getMdxSource as loadMdxSource,
10
- getMdxSlugs,
11
10
  renderPage,
12
11
  renderPageMetadata,
13
12
  } from "@working-theory/page-renderer";
@@ -26,15 +25,6 @@ export async function generateMetadata({
26
25
  return renderPageMetadata({ type: "Feature", slug, locale });
27
26
  }
28
27
 
29
- export async function generateStaticParams({
30
- params,
31
- }: {
32
- params: { locale: string };
33
- }): Promise<{ slug: string }[]> {
34
- const slugs = await getMdxSlugs("Feature", params.locale);
35
- return slugs.map((slug) => ({ slug }));
36
- }
37
-
38
28
  export default async function Page({
39
29
  params,
40
30
  getMdxSource,
@@ -7,7 +7,6 @@
7
7
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
8
8
  import {
9
9
  getMdxSource as loadMdxSource,
10
- getMdxSlugs,
11
10
  renderPage,
12
11
  renderPageMetadata,
13
12
  } from "@working-theory/page-renderer";
@@ -26,15 +25,6 @@ export async function generateMetadata({
26
25
  return renderPageMetadata({ type: "UseCase", slug, locale });
27
26
  }
28
27
 
29
- export async function generateStaticParams({
30
- params,
31
- }: {
32
- params: { locale: string };
33
- }): Promise<{ slug: string }[]> {
34
- const slugs = await getMdxSlugs("UseCase", params.locale);
35
- return slugs.map((slug) => ({ slug }));
36
- }
37
-
38
28
  export default async function Page({
39
29
  params,
40
30
  getMdxSource,
@@ -1,11 +1,7 @@
1
1
  // HowTo detail page. Reads the HowTo MDX for the given slug, renders via
2
2
  // @working-theory/page-renderer, and emits a JSON-LD <script>. Unknown slugs trigger 404.
3
3
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
4
- import {
5
- getMdxSlugs,
6
- renderPage,
7
- renderPageMetadata,
8
- } from "@working-theory/page-renderer";
4
+ import { renderPage, renderPageMetadata } from "@working-theory/page-renderer";
9
5
  import type { Metadata } from "next";
10
6
  import type { ReactElement } from "react";
11
7
 
@@ -21,15 +17,6 @@ export async function generateMetadata({
21
17
  return renderPageMetadata({ type: "HowTo", slug, locale });
22
18
  }
23
19
 
24
- export async function generateStaticParams({
25
- params,
26
- }: {
27
- params: { locale: string };
28
- }): Promise<{ slug: string }[]> {
29
- const slugs = await getMdxSlugs("HowTo", params.locale);
30
- return slugs.map((slug) => ({ slug }));
31
- }
32
-
33
20
  export default async function Page({
34
21
  params,
35
22
  getMdxSource,
@@ -4,7 +4,6 @@ import {
4
4
  renderPage,
5
5
  renderPageMetadata,
6
6
  getMdxSource as defaultGetMdxSource,
7
- getMdxSlugs,
8
7
  } from "@working-theory/page-renderer";
9
8
  import type { Metadata } from "next";
10
9
  import { notFound } from "next/navigation";
@@ -22,15 +21,6 @@ export async function generateMetadata({
22
21
  return renderPageMetadata({ type: "Integration", slug, locale });
23
22
  }
24
23
 
25
- export async function generateStaticParams({
26
- params,
27
- }: {
28
- params: { locale: string };
29
- }): Promise<{ slug: string }[]> {
30
- const slugs = await getMdxSlugs("Integration", params.locale);
31
- return slugs.map((slug) => ({ slug }));
32
- }
33
-
34
24
  export default async function Page({
35
25
  params,
36
26
  getMdxSource = defaultGetMdxSource,
@@ -2,7 +2,6 @@
2
2
  // next-mdx-remote/rsc, and emits a JSON-LD <script>. Unknown slugs trigger 404.
3
3
  import {
4
4
  getMdxSource as defaultGetMdxSource,
5
- getMdxSlugs,
6
5
  renderPage,
7
6
  renderPageMetadata,
8
7
  } from "@working-theory/page-renderer";
@@ -22,15 +21,6 @@ export async function generateMetadata({
22
21
  return renderPageMetadata({ type: "Partner", slug, locale });
23
22
  }
24
23
 
25
- export async function generateStaticParams({
26
- params,
27
- }: {
28
- params: { locale: string };
29
- }): Promise<{ slug: string }[]> {
30
- const slugs = await getMdxSlugs("Partner", params.locale);
31
- return slugs.map((slug: string) => ({ slug }));
32
- }
33
-
34
24
  export default async function Page({
35
25
  params,
36
26
  getMdxSource = defaultGetMdxSource,
@@ -4,7 +4,6 @@
4
4
  // NO_HAND_IMPLEMENT_OK: /build-starter operator-directed live pass (#6687).
5
5
  import {
6
6
  getMdxSource as defaultGetMdxSource,
7
- getMdxSlugs,
8
7
  renderPage,
9
8
  renderPageMetadata,
10
9
  } from "@working-theory/page-renderer";
@@ -24,15 +23,6 @@ export async function generateMetadata({
24
23
  return renderPageMetadata({ type: "PortfolioItem", slug, locale });
25
24
  }
26
25
 
27
- export async function generateStaticParams({
28
- params,
29
- }: {
30
- params: { locale: string };
31
- }): Promise<{ slug: string }[]> {
32
- const slugs = await getMdxSlugs("PortfolioItem", params.locale);
33
- return slugs.map((slug: string) => ({ slug }));
34
- }
35
-
36
26
  export default async function Page({
37
27
  params,
38
28
  getMdxSource = defaultGetMdxSource,
@@ -4,7 +4,6 @@ import {
4
4
  renderPage,
5
5
  renderPageMetadata,
6
6
  getMdxSource as defaultGetMdxSource,
7
- getMdxSlugs,
8
7
  } from "@working-theory/page-renderer";
9
8
  import type { Metadata } from "next";
10
9
  import { notFound } from "next/navigation";
@@ -22,15 +21,6 @@ export async function generateMetadata({
22
21
  return renderPageMetadata({ type: "Product", slug, locale });
23
22
  }
24
23
 
25
- export async function generateStaticParams({
26
- params,
27
- }: {
28
- params: { locale: string };
29
- }): Promise<{ slug: string }[]> {
30
- const slugs = await getMdxSlugs("Product", params.locale);
31
- return slugs.map((slug) => ({ slug }));
32
- }
33
-
34
24
  export default async function Page({
35
25
  params,
36
26
  getMdxSource = defaultGetMdxSource,
@@ -4,11 +4,7 @@ import type {
4
4
  getMdxSource as defaultGetMdxSource,
5
5
  getPageSections as defaultGetPageSections,
6
6
  } from "@working-theory/page-renderer";
7
- import {
8
- getContentAdapter,
9
- renderPage,
10
- renderPageMetadata,
11
- } from "@working-theory/page-renderer";
7
+ import { renderPage, renderPageMetadata } from "@working-theory/page-renderer";
12
8
  import type { Metadata } from "next";
13
9
  import type { ReactElement } from "react";
14
10
 
@@ -25,16 +21,6 @@ export async function generateMetadata({
25
21
  return renderPageMetadata({ type: "Project", slug, locale });
26
22
  }
27
23
 
28
- export async function generateStaticParams({
29
- params,
30
- }: {
31
- params: { locale: string };
32
- }): Promise<{ slug: string }[]> {
33
- const adapter = getContentAdapter();
34
- const pages = await adapter.listDetail("Project", params.locale);
35
- return pages.map((page) => ({ slug: page.slug }));
36
- }
37
-
38
24
  export default async function Page({
39
25
  params,
40
26
  getMdxSource,
@@ -4,7 +4,6 @@ import type {
4
4
  getMdxSource as defaultGetMdxSource,
5
5
  } from "@working-theory/page-renderer";
6
6
  import {
7
- getMdxSlugs,
8
7
  getMdxSource as defaultGetMdxSourceImpl,
9
8
  renderPage,
10
9
  renderPageMetadata,
@@ -25,15 +24,6 @@ export async function generateMetadata({
25
24
  return renderPageMetadata({ type: "ResourceDetail", slug, locale });
26
25
  }
27
26
 
28
- export async function generateStaticParams({
29
- params,
30
- }: {
31
- params: { locale: string };
32
- }): Promise<{ slug: string }[]> {
33
- const slugs = await getMdxSlugs("ResourceDetail", params.locale);
34
- return slugs.map((slug) => ({ slug }));
35
- }
36
-
37
27
  export default async function Page({
38
28
  params,
39
29
  getMdxSource = defaultGetMdxSourceImpl,
@@ -4,7 +4,6 @@ import {
4
4
  renderPage,
5
5
  renderPageMetadata,
6
6
  getMdxSource as defaultGetMdxSource,
7
- getMdxSlugs,
8
7
  } from "@working-theory/page-renderer";
9
8
  import type { Metadata } from "next";
10
9
  import { notFound } from "next/navigation";
@@ -22,15 +21,6 @@ export async function generateMetadata({
22
21
  return renderPageMetadata({ type: "Service", slug, locale });
23
22
  }
24
23
 
25
- export async function generateStaticParams({
26
- params,
27
- }: {
28
- params: { locale: string };
29
- }): Promise<{ slug: string }[]> {
30
- const slugs = await getMdxSlugs("Service", params.locale);
31
- return slugs.map((slug) => ({ slug }));
32
- }
33
-
34
24
  export default async function Page({
35
25
  params,
36
26
  getMdxSource = defaultGetMdxSource,
@@ -4,7 +4,6 @@
4
4
  // renders them; a prose-only page keeps the MDX-body render.
5
5
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
6
6
  import {
7
- getMdxSlugs,
8
7
  getMdxSource as loadMdxSource,
9
8
  renderPage,
10
9
  renderPageMetadata,
@@ -24,15 +23,6 @@ export async function generateMetadata({
24
23
  return renderPageMetadata({ type: "SoftwareProduct", slug, locale });
25
24
  }
26
25
 
27
- export async function generateStaticParams({
28
- params,
29
- }: {
30
- params: { locale: string };
31
- }): Promise<{ slug: string }[]> {
32
- const slugs = await getMdxSlugs("SoftwareProduct", params.locale);
33
- return slugs.map((slug) => ({ slug }));
34
- }
35
-
36
26
  export default async function Page({
37
27
  params,
38
28
  getMdxSource,
@@ -1,11 +1,7 @@
1
1
  // TechArticle detail page. Reads the TechArticle MDX for the given slug, renders via
2
2
  // @working-theory/page-renderer, and emits a JSON-LD <script>. Unknown slugs trigger 404.
3
3
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
4
- import {
5
- getMdxSlugs,
6
- renderPage,
7
- renderPageMetadata,
8
- } from "@working-theory/page-renderer";
4
+ import { renderPage, renderPageMetadata } from "@working-theory/page-renderer";
9
5
  import type { Metadata } from "next";
10
6
  import type { ReactElement } from "react";
11
7
 
@@ -21,15 +17,6 @@ export async function generateMetadata({
21
17
  return renderPageMetadata({ type: "TechArticle", slug, locale });
22
18
  }
23
19
 
24
- export async function generateStaticParams({
25
- params,
26
- }: {
27
- params: { locale: string };
28
- }): Promise<{ slug: string }[]> {
29
- const slugs = await getMdxSlugs("TechArticle", params.locale);
30
- return slugs.map((slug) => ({ slug }));
31
- }
32
-
33
20
  export default async function Page({
34
21
  params,
35
22
  getMdxSource,
@@ -1,14 +1,11 @@
1
1
  // ThankYouPage detail page (#3960 D6) — the segment-keyed flow-completion
2
- // redirect target. Sections-mode renderPage (the ContactPage pattern) with
3
- // Whitepaper's dynamic-slug generateStaticParams pattern. Unknown slugs 404
2
+ // redirect target. Sections-mode renderPage (the ContactPage pattern),
3
+ // rendered on request under the cookie-reading locale layout; [slug]/page.tsx's
4
+ // header says why there is no static-params export (#7379). Unknown slugs 404
4
5
  // via renderPage's own detail-route branch (no translationsNamespace passed
5
6
  // — renderPage calls notFound() itself when getMdxSource resolves null).
6
7
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
7
- import {
8
- getMdxSlugs,
9
- renderPage,
10
- renderPageMetadata,
11
- } from "@working-theory/page-renderer";
8
+ import { renderPage, renderPageMetadata } from "@working-theory/page-renderer";
12
9
  import type { Metadata } from "next";
13
10
  import type { ReactElement } from "react";
14
11
 
@@ -24,15 +21,6 @@ export async function generateMetadata({
24
21
  return renderPageMetadata({ type: "ThankYouPage", slug, locale });
25
22
  }
26
23
 
27
- export async function generateStaticParams({
28
- params,
29
- }: {
30
- params: { locale: string };
31
- }): Promise<{ slug: string }[]> {
32
- const slugs = await getMdxSlugs("ThankYouPage", params.locale);
33
- return slugs.map((slug) => ({ slug }));
34
- }
35
-
36
24
  export default async function Page({
37
25
  params,
38
26
  getMdxSource,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Tests for /vs/[slug] (#6878 D9) — the ComparisonPage detail route.
3
- * `renderPage`/`renderPageMetadata`/`getMdxSlugs` are mocked at the
3
+ * `renderPage`/`renderPageMetadata` are mocked at the
4
4
  * `@working-theory/page-renderer` boundary; `getMdxSource` is supplied
5
5
  * per-call through the route's DI prop.
6
6
  */
@@ -10,24 +10,19 @@
10
10
  // production, unaffected by this mock's loose type here.
11
11
  const mockRenderPage = jest.fn(async (..._args: unknown[]) => "<rendered/>");
12
12
  const mockRenderPageMetadata = jest.fn((..._args: unknown[]) => undefined);
13
- const mockGetMdxSlugs = jest.fn((..._args: unknown[]) => Promise.resolve<string[]>([]));
14
13
 
15
14
  jest.mock("@working-theory/page-renderer", () => ({
16
15
  __esModule: true,
17
- getMdxSlugs: (...args: unknown[]) => mockGetMdxSlugs(...args),
18
16
  renderPage: (...args: unknown[]) => mockRenderPage(...args),
19
17
  renderPageMetadata: (...args: unknown[]) => mockRenderPageMetadata(...args),
20
18
  getMdxSource: jest.fn(),
21
19
  }));
22
20
 
23
- import ComparisonPage, {
24
- generateMetadata,
25
- generateStaticParams,
26
- } from "../page";
21
+ import ComparisonPage, { generateMetadata } from "../page";
27
22
 
28
23
  beforeEach(() => jest.clearAllMocks());
29
24
 
30
- describe("ComparisonPage detail — metadata + static params", () => {
25
+ describe("ComparisonPage detail — metadata", () => {
31
26
  it("resolves metadata for the given slug", async () => {
32
27
  await generateMetadata({
33
28
  params: Promise.resolve({ locale: "en", slug: "acme" }),
@@ -38,13 +33,6 @@ describe("ComparisonPage detail — metadata + static params", () => {
38
33
  locale: "en",
39
34
  });
40
35
  });
41
-
42
- it("enumerates every ComparisonPage slug for static generation", async () => {
43
- mockGetMdxSlugs.mockResolvedValue(["acme", "globex"]);
44
- const params = await generateStaticParams({ params: { locale: "en" } });
45
- expect(mockGetMdxSlugs).toHaveBeenCalledWith("ComparisonPage", "en");
46
- expect(params).toEqual([{ slug: "acme" }, { slug: "globex" }]);
47
- });
48
36
  });
49
37
 
50
38
  describe("ComparisonPage detail — sections vs mdx-body", () => {
@@ -12,7 +12,6 @@
12
12
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
13
13
  import {
14
14
  getMdxSource as loadMdxSource,
15
- getMdxSlugs,
16
15
  renderPage,
17
16
  renderPageMetadata,
18
17
  } from "@working-theory/page-renderer";
@@ -31,15 +30,6 @@ export async function generateMetadata({
31
30
  return renderPageMetadata({ type: "ComparisonPage", slug, locale });
32
31
  }
33
32
 
34
- export async function generateStaticParams({
35
- params,
36
- }: {
37
- params: { locale: string };
38
- }): Promise<{ slug: string }[]> {
39
- const slugs = await getMdxSlugs("ComparisonPage", params.locale);
40
- return slugs.map((slug) => ({ slug }));
41
- }
42
-
43
33
  export default async function Page({
44
34
  params,
45
35
  getMdxSource,
@@ -1,11 +1,7 @@
1
1
  // Whitepaper detail page. Reads the Whitepaper MDX for the given slug, renders via
2
2
  // @working-theory/page-renderer, and emits a JSON-LD <script>. Unknown slugs trigger 404.
3
3
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
4
- import {
5
- getMdxSlugs,
6
- renderPage,
7
- renderPageMetadata,
8
- } from "@working-theory/page-renderer";
4
+ import { renderPage, renderPageMetadata } from "@working-theory/page-renderer";
9
5
  import type { Metadata } from "next";
10
6
  import type { ReactElement } from "react";
11
7
 
@@ -21,15 +17,6 @@ export async function generateMetadata({
21
17
  return renderPageMetadata({ type: "Whitepaper", slug, locale });
22
18
  }
23
19
 
24
- export async function generateStaticParams({
25
- params,
26
- }: {
27
- params: { locale: string };
28
- }): Promise<{ slug: string }[]> {
29
- const slugs = await getMdxSlugs("Whitepaper", params.locale);
30
- return slugs.map((slug) => ({ slug }));
31
- }
32
-
33
20
  export default async function Page({
34
21
  params,
35
22
  getMdxSource,
package/versions.json CHANGED
@@ -39,7 +39,6 @@
39
39
  "@working-theory/branding": "0.0.0",
40
40
  "@working-theory/license-client": "0.0.0",
41
41
  "@working-theory/license-manifest": "0.0.0",
42
- "@working-theory/marketing-api-client": "0.0.0",
43
42
  "@working-theory/partner-session": "0.0.0",
44
43
  "@working-theory/preview-protocol": "0.0.0",
45
44
  "@working-theory/ui-commerce": "0.23.7",