create-ailk 0.13.0 → 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.
- package/README.md +12 -4
- package/component-catalog.json +247 -33
- package/component-catalog.md +10 -6
- package/dist/apply-design.d.ts +21 -2
- package/dist/apply-design.js +120 -19
- package/dist/cli.js +1 -0
- package/dist/design-compile.d.ts +70 -2
- package/dist/design-compile.js +97 -1
- package/dist/design-home.d.ts +74 -0
- package/dist/design-home.js +256 -0
- package/dist/design-package.d.ts +39 -2
- package/dist/design-package.js +223 -9
- package/dist/design-presets.d.ts +17 -1
- package/dist/design-presets.js +17 -5
- package/dist/design-scaffold.d.ts +6 -25
- package/dist/design-scaffold.js +26 -74
- package/dist/index.d.ts +1 -0
- package/dist/lib/design-checks.d.ts +12 -1
- package/dist/lib/design-checks.js +38 -2
- package/dist/lib/module-license-gate.d.ts +9 -0
- package/dist/lib/module-license-gate.js +18 -0
- package/dist/programmatic.js +70 -26
- package/dist/site-chrome.d.ts +38 -0
- package/dist/site-chrome.js +109 -2
- package/dist/site-fonts.d.ts +21 -0
- package/dist/site-fonts.js +54 -0
- package/dist/site-roster.d.ts +16 -0
- package/dist/site-roster.js +81 -0
- package/dist/starter-grid.js +15 -0
- package/dist/surfaces.js +12 -0
- package/dist/team-profiles.d.ts +29 -0
- package/dist/team-profiles.js +43 -0
- package/package.json +3 -3
- package/release-scope.json +1 -1
- package/site-templates/_base/content/en/TeamList/team.mdx +68 -0
- package/site-templates/_base/content/en/TeamMember/first-member.mdx +22 -0
- package/site-templates/_base/content/en/TeamMember/second-member.mdx +22 -0
- package/site-templates/_base/content/en/TeamMember/third-member.mdx +22 -0
- package/site-templates/designs/minimalism/DESIGN.md +164 -34
- package/site-templates/designs/minimalism/home/local-business-service.mdx +119 -0
- package/site-templates/designs/minimalism/home/organization-product.mdx +132 -0
- package/site-templates/designs/minimalism/home/organization-service.mdx +131 -0
- package/site-templates/designs/minimalism/home/person-service.mdx +101 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +20 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +22 -1
- package/site-templates/marketing-site-free/ailk-template.json +1 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -7
- package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -7
- package/site-templates/shapes/organization-service/ailk-template.json +1 -1
- package/templates/README.md +2 -0
- package/templates/apps/api/CLAUDE.md +4 -1
- package/templates/apps/mcp/package.json +1 -1
- package/templates/apps/web/CLAUDE.md +11 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +10 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/__tests__/theme-modes.test.ts +42 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/theme-modes.ts +8 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +14 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +69 -0
- package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +64 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +15 -3
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +17 -6
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +14 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +4 -6
- package/templates/apps/web/app/[locale]/layout.tsx +156 -53
- package/templates/apps/web/app/[locale]/page.tsx +37 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +4 -6
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +15 -2
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +43 -6
- package/templates/apps/web/app/[locale]/services/page.tsx +14 -2
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/team/[slug]/page.tsx +45 -0
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +155 -0
- package/templates/apps/web/app/[locale]/team/page.tsx +49 -0
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +14 -1
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/not-found.tsx +47 -0
- package/templates/apps/web/lib/__tests__/profile-back-link.test.ts +95 -0
- package/templates/apps/web/lib/__tests__/site-theme-head.test.ts +70 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +64 -1
- package/templates/apps/web/lib/profile-back-link.ts +51 -0
- package/templates/apps/web/lib/site-chrome.ts +12 -0
- package/templates/apps/web/lib/site-fonts.ts +15 -0
- package/templates/apps/web/lib/site-logo.tsx +39 -1
- package/templates/apps/web/lib/site-theme-head.ts +33 -0
- package/templates/apps/web/lib/site-theme.ts +13 -1
- package/templates/apps/web/messages/en.json +7 -1
- package/templates/database/CHANGELOG.md +7 -0
- package/templates/database/package.json +1 -1
- package/versions.json +34 -34
- package/site-templates/designs/minimalism/sample/README.md +0 -48
- package/site-templates/designs/minimalism/sample/content/en/HomePage/home.mdx +0 -244
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-1.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-2.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-3.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-4.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-5.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-6.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/hero-backdrop.svg +0 -17
- package/site-templates/designs/minimalism/sample/public/logo-placeholder-1.svg +0 -6
- package/site-templates/designs/minimalism/sample/public/logo-placeholder-2.svg +0 -6
- package/site-templates/designs/minimalism/sample/public/logo-placeholder-3.svg +0 -6
- package/site-templates/designs/minimalism/sample/public/wordmark-placeholder.svg +0 -28
|
@@ -87,13 +87,51 @@ describe("ServicesPage — snapshot regression", () => {
|
|
|
87
87
|
});
|
|
88
88
|
const { container } = render(element);
|
|
89
89
|
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
90
|
-
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Services","description":"All services.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Service"}]}}</script><main id="main"><h1>Services</h1><p data-testid="services-description">All services.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-remote" data-source="
|
|
91
|
-
|
|
92
|
-
Body."></div></article></div></div></main>"
|
|
90
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Services","description":"All services.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Service"}]}}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">Services</h1><p data-testid="services-description" class="mt-block-tight text-lead">All services.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle mt-block-loose"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-fg) text-body-sm leading-relaxed [&>*:first-child]:mt-0 [&_h2]:mt-block-loose [&_h2]:scroll-mt-24 [&_h2]:text-heading-3 [&_h2]:font-display [&_h2]:text-(--color-fg) [&_h3]:mt-block-loose [&_h3]:scroll-mt-24 [&_h3]:text-heading-4 [&_h3]:font-display [&_h3]:text-(--color-fg) [&_h4]:mt-block-tight [&_h4]:scroll-mt-24 [&_h4]:text-heading-4 [&_h4]:font-display [&_h4]:text-(--color-fg) [&_h5]:mt-block-tight [&_h5]:scroll-mt-24 [&_h5]:text-heading-4 [&_h5]:font-display [&_h5]:text-(--color-fg) [&_h6]:mt-block-tight [&_h6]:scroll-mt-24 [&_h6]:text-heading-4 [&_h6]:font-display [&_h6]:text-(--color-fg) [&_p]:mt-paragraph [&_strong]:font-semibold [&_strong]:text-(--color-fg) [&_em]:italic [&_ul]:mt-paragraph [&_ul]:list-disc [&_ul]:ps-6 [&_ol]:mt-paragraph [&_ol]:list-decimal [&_ol]:ps-6 [&_li]:mt-1 [&_li]:ps-1 [&_li]:marker:text-(--color-text-muted) [&_li>ul]:mt-1 [&_li>ol]:mt-1 [&_blockquote]:mt-paragraph [&_blockquote]:border-s-4 [&_blockquote]:border-(--color-border-strong) [&_blockquote]:ps-4 [&_blockquote]:italic [&_blockquote]:text-(--color-text-muted) [&_hr]:my-block-loose [&_hr]:border-(--color-border) [&_a]:text-(--color-accent) [&_a]:underline [&_a]:underline-offset-4 [&_:not(pre)>code]:rounded [&_:not(pre)>code]:bg-(--color-surface-secondary) [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:text-code [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-(--color-fg) [&_pre]:mt-paragraph [&_pre]:overflow-auto [&_pre]:rounded-md [&_pre]:border-hairline [&_pre]:border-(--color-border) [&_pre]:bg-(--color-bg) [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-body-sm [&_img]:mt-paragraph [&_img]:h-auto [&_img]:max-w-full [&_img]:rounded-lg [&_table]:mt-paragraph [&_table]:w-full [&_table]:border-collapse [&_table]:text-body-sm [&_th]:border-b [&_th]:border-(--color-border) [&_th]:bg-(--color-surface-secondary) [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:font-semibold [&_th]:text-(--color-fg) [&_td]:border-b [&_td]:border-(--color-border) [&_td]:px-3 [&_td]:py-2 [&_kbd]:rounded [&_kbd]:border-hairline [&_kbd]:border-(--color-border) [&_kbd]:bg-(--color-surface-secondary) [&_kbd]:px-1.5 [&_kbd]:py-0.5 [&_kbd]:text-caption [&_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-remote" data-source="
|
|
91
|
+
Body."></div></div></article></div></div></div></main>"
|
|
93
92
|
`);
|
|
94
93
|
});
|
|
95
94
|
});
|
|
96
95
|
|
|
96
|
+
// ── #7745 D2: a ServicesList page that authors sections renders them ──────────
|
|
97
|
+
|
|
98
|
+
describe("ServicesPage — authored sections (#7745)", () => {
|
|
99
|
+
it("renders a sectioned page in sections mode, with no mdx-body", async () => {
|
|
100
|
+
const renderPageSpy = jest.spyOn(pageRenderer, "renderPage");
|
|
101
|
+
const source = {
|
|
102
|
+
...makeServicesListSource(),
|
|
103
|
+
frontmatter: {
|
|
104
|
+
...makeServicesListSource().frontmatter,
|
|
105
|
+
sections: [
|
|
106
|
+
{
|
|
107
|
+
id: "hero-1",
|
|
108
|
+
type: "hero",
|
|
109
|
+
data: { headline: "Get on AILK fast" },
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
const element = await ServicesPage({
|
|
115
|
+
params: Promise.resolve({ locale: "en" }),
|
|
116
|
+
getMdxSource: jest.fn().mockResolvedValue(source),
|
|
117
|
+
});
|
|
118
|
+
const { container } = render(element);
|
|
119
|
+
expect(renderPageSpy).toHaveBeenCalledTimes(1);
|
|
120
|
+
expect(renderPageSpy.mock.calls[0]![0].bodyMode).toBeUndefined();
|
|
121
|
+
renderPageSpy.mockRestore();
|
|
122
|
+
expect(container.querySelector('[data-testid="mdx-body"]')).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("keeps the mdx-body render for a prose-only page", async () => {
|
|
126
|
+
const element = await ServicesPage({
|
|
127
|
+
params: Promise.resolve({ locale: "en" }),
|
|
128
|
+
getMdxSource: jest.fn().mockResolvedValue(makeServicesListSource()),
|
|
129
|
+
});
|
|
130
|
+
const { container } = render(element);
|
|
131
|
+
expect(container.querySelector('[data-testid="mdx-body"]')).not.toBeNull();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
97
135
|
// ── services/[slug]/page.tsx — detail ─────────────────────────────────────────
|
|
98
136
|
|
|
99
137
|
describe("ServiceSlugPage — snapshot regression", () => {
|
|
@@ -111,9 +149,8 @@ describe("ServiceSlugPage — snapshot regression", () => {
|
|
|
111
149
|
});
|
|
112
150
|
const { container } = render(element);
|
|
113
151
|
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
114
|
-
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"Implementation consulting","description":"Help adapting AI Launch Kit.","mainEntity":{"@context":"https://schema.org","@type":"Service","name":"Implementation consulting","description":"Help adapting AI Launch Kit."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Services","item":"http://localhost:3000/en/services"},{"@type":"ListItem","position":3,"name":"Implementation consulting","item":"http://localhost:3000/en/services/consulting"}]}]}</script><main id="main"><h1>Implementation consulting</h1><p data-testid="service-description">Help adapting AI Launch Kit.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-remote" data-source="
|
|
115
|
-
|
|
116
|
-
Body."></div></article></div></div></main>"
|
|
152
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"Implementation consulting","description":"Help adapting AI Launch Kit.","mainEntity":{"@context":"https://schema.org","@type":"Service","name":"Implementation consulting","description":"Help adapting AI Launch Kit."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Services","item":"http://localhost:3000/en/services"},{"@type":"ListItem","position":3,"name":"Implementation consulting","item":"http://localhost:3000/en/services/consulting"}]}]}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">Implementation consulting</h1><p data-testid="service-description" class="mt-block-tight text-lead">Help adapting AI Launch Kit.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle mt-block-loose"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-fg) text-body-sm leading-relaxed [&>*:first-child]:mt-0 [&_h2]:mt-block-loose [&_h2]:scroll-mt-24 [&_h2]:text-heading-3 [&_h2]:font-display [&_h2]:text-(--color-fg) [&_h3]:mt-block-loose [&_h3]:scroll-mt-24 [&_h3]:text-heading-4 [&_h3]:font-display [&_h3]:text-(--color-fg) [&_h4]:mt-block-tight [&_h4]:scroll-mt-24 [&_h4]:text-heading-4 [&_h4]:font-display [&_h4]:text-(--color-fg) [&_h5]:mt-block-tight [&_h5]:scroll-mt-24 [&_h5]:text-heading-4 [&_h5]:font-display [&_h5]:text-(--color-fg) [&_h6]:mt-block-tight [&_h6]:scroll-mt-24 [&_h6]:text-heading-4 [&_h6]:font-display [&_h6]:text-(--color-fg) [&_p]:mt-paragraph [&_strong]:font-semibold [&_strong]:text-(--color-fg) [&_em]:italic [&_ul]:mt-paragraph [&_ul]:list-disc [&_ul]:ps-6 [&_ol]:mt-paragraph [&_ol]:list-decimal [&_ol]:ps-6 [&_li]:mt-1 [&_li]:ps-1 [&_li]:marker:text-(--color-text-muted) [&_li>ul]:mt-1 [&_li>ol]:mt-1 [&_blockquote]:mt-paragraph [&_blockquote]:border-s-4 [&_blockquote]:border-(--color-border-strong) [&_blockquote]:ps-4 [&_blockquote]:italic [&_blockquote]:text-(--color-text-muted) [&_hr]:my-block-loose [&_hr]:border-(--color-border) [&_a]:text-(--color-accent) [&_a]:underline [&_a]:underline-offset-4 [&_:not(pre)>code]:rounded [&_:not(pre)>code]:bg-(--color-surface-secondary) [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:text-code [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-(--color-fg) [&_pre]:mt-paragraph [&_pre]:overflow-auto [&_pre]:rounded-md [&_pre]:border-hairline [&_pre]:border-(--color-border) [&_pre]:bg-(--color-bg) [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-body-sm [&_img]:mt-paragraph [&_img]:h-auto [&_img]:max-w-full [&_img]:rounded-lg [&_table]:mt-paragraph [&_table]:w-full [&_table]:border-collapse [&_table]:text-body-sm [&_th]:border-b [&_th]:border-(--color-border) [&_th]:bg-(--color-surface-secondary) [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:font-semibold [&_th]:text-(--color-fg) [&_td]:border-b [&_td]:border-(--color-border) [&_td]:px-3 [&_td]:py-2 [&_kbd]:rounded [&_kbd]:border-hairline [&_kbd]:border-(--color-border) [&_kbd]:bg-(--color-surface-secondary) [&_kbd]:px-1.5 [&_kbd]:py-0.5 [&_kbd]:text-caption [&_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-remote" data-source="
|
|
153
|
+
Body."></div></div></article></div></div></div></main>"
|
|
117
154
|
`);
|
|
118
155
|
});
|
|
119
156
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// Services list page. Reads ServicesList MDX via the content adapter and emits
|
|
2
2
|
// a JSON-LD CollectionPage via the dispatcher.
|
|
3
3
|
import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getMdxSource as loadMdxSource,
|
|
6
|
+
renderPage,
|
|
7
|
+
renderPageMetadata,
|
|
8
|
+
} from "@working-theory/page-renderer";
|
|
5
9
|
import type { Metadata } from "next";
|
|
6
10
|
import type { ReactElement } from "react";
|
|
7
11
|
|
|
@@ -22,12 +26,20 @@ export default async function Page({
|
|
|
22
26
|
getMdxSource,
|
|
23
27
|
}: Props): Promise<ReactElement> {
|
|
24
28
|
const { locale } = await params;
|
|
29
|
+
// #7745 D2 — the /for switch (#6878): a ServicesList page that composes
|
|
30
|
+
// `sections[]` renders them; a prose-only page keeps the MDX-body render.
|
|
31
|
+
// The CollectionPage JSON-LD is built from the sections either way.
|
|
32
|
+
const source = await (getMdxSource ?? loadMdxSource)("ServicesList", "services", locale);
|
|
33
|
+
const composed =
|
|
34
|
+
source !== null &&
|
|
35
|
+
Array.isArray((source.frontmatter as { sections?: unknown[] }).sections) &&
|
|
36
|
+
((source.frontmatter as { sections?: unknown[] }).sections?.length ?? 0) > 0;
|
|
25
37
|
return renderPage({
|
|
26
38
|
type: "ServicesList",
|
|
27
39
|
slug: "services",
|
|
28
40
|
locale,
|
|
29
41
|
translationsNamespace: "services",
|
|
30
|
-
bodyMode: "mdx-body",
|
|
42
|
+
...(composed ? {} : { bodyMode: "mdx-body" as const }),
|
|
31
43
|
...(getMdxSource !== undefined && { getMdxSource }),
|
|
32
44
|
});
|
|
33
45
|
}
|
|
@@ -61,9 +61,9 @@ describe("software/[slug] — snapshot regression", () => {
|
|
|
61
61
|
});
|
|
62
62
|
const { container } = render(element);
|
|
63
63
|
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
64
|
-
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"@working-theory/page-renderer","description":"Single rendering primitive for MDX-wired routes.","mainEntity":{"@context":"https://schema.org","@type":"SoftwareApplication","name":"@working-theory/page-renderer","description":"Single rendering primitive for MDX-wired routes."}}</script><main id="main"><h1>@working-theory/page-renderer</h1><p data-testid="software-description">Single rendering primitive for MDX-wired routes.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-rendered">## Overview
|
|
64
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"@working-theory/page-renderer","description":"Single rendering primitive for MDX-wired routes.","mainEntity":{"@context":"https://schema.org","@type":"SoftwareApplication","name":"@working-theory/page-renderer","description":"Single rendering primitive for MDX-wired routes."}}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">@working-theory/page-renderer</h1><p data-testid="software-description" class="mt-block-tight text-lead">Single rendering primitive for MDX-wired routes.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle mt-block-loose"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-fg) text-body-sm leading-relaxed [&>*:first-child]:mt-0 [&_h2]:mt-block-loose [&_h2]:scroll-mt-24 [&_h2]:text-heading-3 [&_h2]:font-display [&_h2]:text-(--color-fg) [&_h3]:mt-block-loose [&_h3]:scroll-mt-24 [&_h3]:text-heading-4 [&_h3]:font-display [&_h3]:text-(--color-fg) [&_h4]:mt-block-tight [&_h4]:scroll-mt-24 [&_h4]:text-heading-4 [&_h4]:font-display [&_h4]:text-(--color-fg) [&_h5]:mt-block-tight [&_h5]:scroll-mt-24 [&_h5]:text-heading-4 [&_h5]:font-display [&_h5]:text-(--color-fg) [&_h6]:mt-block-tight [&_h6]:scroll-mt-24 [&_h6]:text-heading-4 [&_h6]:font-display [&_h6]:text-(--color-fg) [&_p]:mt-paragraph [&_strong]:font-semibold [&_strong]:text-(--color-fg) [&_em]:italic [&_ul]:mt-paragraph [&_ul]:list-disc [&_ul]:ps-6 [&_ol]:mt-paragraph [&_ol]:list-decimal [&_ol]:ps-6 [&_li]:mt-1 [&_li]:ps-1 [&_li]:marker:text-(--color-text-muted) [&_li>ul]:mt-1 [&_li>ol]:mt-1 [&_blockquote]:mt-paragraph [&_blockquote]:border-s-4 [&_blockquote]:border-(--color-border-strong) [&_blockquote]:ps-4 [&_blockquote]:italic [&_blockquote]:text-(--color-text-muted) [&_hr]:my-block-loose [&_hr]:border-(--color-border) [&_a]:text-(--color-accent) [&_a]:underline [&_a]:underline-offset-4 [&_:not(pre)>code]:rounded [&_:not(pre)>code]:bg-(--color-surface-secondary) [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:text-code [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-(--color-fg) [&_pre]:mt-paragraph [&_pre]:overflow-auto [&_pre]:rounded-md [&_pre]:border-hairline [&_pre]:border-(--color-border) [&_pre]:bg-(--color-bg) [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-body-sm [&_img]:mt-paragraph [&_img]:h-auto [&_img]:max-w-full [&_img]:rounded-lg [&_table]:mt-paragraph [&_table]:w-full [&_table]:border-collapse [&_table]:text-body-sm [&_th]:border-b [&_th]:border-(--color-border) [&_th]:bg-(--color-surface-secondary) [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:font-semibold [&_th]:text-(--color-fg) [&_td]:border-b [&_td]:border-(--color-border) [&_td]:px-3 [&_td]:py-2 [&_kbd]:rounded [&_kbd]:border-hairline [&_kbd]:border-(--color-border) [&_kbd]:bg-(--color-surface-secondary) [&_kbd]:px-1.5 [&_kbd]:py-0.5 [&_kbd]:text-caption [&_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-rendered">## Overview
|
|
65
65
|
|
|
66
|
-
Body content.</div></article></div></div></main>"
|
|
66
|
+
Body content.</div></div></article></div></div></div></main>"
|
|
67
67
|
`);
|
|
68
68
|
});
|
|
69
69
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Team member profile page (#7744 D2). Reads the TeamMember MDX for the given slug and emits a
|
|
2
|
+
// JSON-LD <script>. Unknown slugs 404 through renderPage. Renders `sections[]` when the
|
|
3
|
+
// frontmatter carries them (the /for composed pattern), MDX prose otherwise.
|
|
4
|
+
import {
|
|
5
|
+
getMdxSource as defaultGetMdxSource,
|
|
6
|
+
renderPage,
|
|
7
|
+
renderPageMetadata,
|
|
8
|
+
} from "@working-theory/page-renderer";
|
|
9
|
+
import type { Metadata } from "next";
|
|
10
|
+
import type { ReactElement } from "react";
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
params: Promise<{ locale: string; slug: string }>;
|
|
14
|
+
getMdxSource?: typeof defaultGetMdxSource;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function generateMetadata({
|
|
18
|
+
params,
|
|
19
|
+
}: Pick<Props, "params">): Promise<Metadata> {
|
|
20
|
+
const { locale, slug } = await params;
|
|
21
|
+
return renderPageMetadata({ type: "TeamMember", slug, locale });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default async function Page({
|
|
25
|
+
params,
|
|
26
|
+
getMdxSource = defaultGetMdxSource,
|
|
27
|
+
}: Props): Promise<ReactElement> {
|
|
28
|
+
const { locale, slug } = await params;
|
|
29
|
+
// Read once to pick the body mode. A null source still goes to renderPage,
|
|
30
|
+
// which owns the unknown-slug branch (#7537).
|
|
31
|
+
const source = await getMdxSource("TeamMember", slug, locale);
|
|
32
|
+
|
|
33
|
+
const composed =
|
|
34
|
+
source !== null &&
|
|
35
|
+
Array.isArray((source.frontmatter as { sections?: unknown[] }).sections) &&
|
|
36
|
+
((source.frontmatter as { sections?: unknown[] }).sections?.length ?? 0) > 0;
|
|
37
|
+
|
|
38
|
+
return renderPage({
|
|
39
|
+
type: "TeamMember",
|
|
40
|
+
slug,
|
|
41
|
+
locale,
|
|
42
|
+
...(composed ? {} : { bodyMode: "mdx-body" as const }),
|
|
43
|
+
getMdxSource: async () => source,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The team routes (#7744 D2, AC-1): `/team/<slug>` renders a profile whose
|
|
3
|
+
* JSON-LD is a `ProfilePage` with a `Person` main entity carrying the
|
|
4
|
+
* page-scoped `@id`, valid under the schema validator; an unknown slug 404s
|
|
5
|
+
* through `renderPage`; `/team` emits a valid `TeamList` block.
|
|
6
|
+
*
|
|
7
|
+
* MDX sources are inline stubs (no fs reads), so the suite runs unchanged
|
|
8
|
+
* inside a scaffold.
|
|
9
|
+
*/
|
|
10
|
+
import React from "react";
|
|
11
|
+
import { render } from "@testing-library/react";
|
|
12
|
+
import {
|
|
13
|
+
validateBlock,
|
|
14
|
+
validatePageGraph,
|
|
15
|
+
} from "@working-theory/schema-validator";
|
|
16
|
+
|
|
17
|
+
jest.mock("next/navigation", () => ({
|
|
18
|
+
__esModule: true,
|
|
19
|
+
notFound: jest.fn(() => {
|
|
20
|
+
throw new Error("NEXT_NOT_FOUND");
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
jest.mock("next-mdx-remote/rsc", () => ({
|
|
25
|
+
__esModule: true,
|
|
26
|
+
MDXRemote: ({ source }: { source: string }) =>
|
|
27
|
+
React.createElement("div", { "data-testid": "mdx-rendered" }, source),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
import TeamPage from "../page";
|
|
31
|
+
import TeamMemberPage from "../[slug]/page";
|
|
32
|
+
import * as pageRenderer from "@working-theory/page-renderer";
|
|
33
|
+
|
|
34
|
+
type JsonLd = Record<string, unknown>;
|
|
35
|
+
|
|
36
|
+
function jsonLdScripts(container: HTMLElement): JsonLd[] {
|
|
37
|
+
return [
|
|
38
|
+
...container.querySelectorAll("script[type='application/ld+json']"),
|
|
39
|
+
].map((s) => JSON.parse(s.textContent ?? "") as JsonLd);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const FIRST_MEMBER = {
|
|
43
|
+
frontmatter: {
|
|
44
|
+
type: "TeamMember",
|
|
45
|
+
slug: "first-member",
|
|
46
|
+
title: "{First team member's name}",
|
|
47
|
+
description: "{One sentence on who they are and what they do here}",
|
|
48
|
+
schema: { jobTitle: "{Their title}" },
|
|
49
|
+
},
|
|
50
|
+
body: "{Two or three sentences on what they do here.}",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe("team/[slug] — the profile page", () => {
|
|
54
|
+
it("emits one ProfilePage graph whose Person carries @id #person, valid under the schema validator", async () => {
|
|
55
|
+
const element = await TeamMemberPage({
|
|
56
|
+
params: Promise.resolve({ locale: "en", slug: "first-member" }),
|
|
57
|
+
getMdxSource: jest.fn().mockResolvedValue(FIRST_MEMBER),
|
|
58
|
+
});
|
|
59
|
+
const { container } = render(element);
|
|
60
|
+
const scripts = jsonLdScripts(container);
|
|
61
|
+
expect(scripts).toHaveLength(1);
|
|
62
|
+
const graph = scripts[0]!["@graph"] as JsonLd[];
|
|
63
|
+
expect(validatePageGraph(graph as never, "TeamMember")).toEqual(
|
|
64
|
+
expect.objectContaining({ valid: true }),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const profile = graph.find((b) => b["@type"] === "ProfilePage")!;
|
|
68
|
+
const person = profile["mainEntity"] as JsonLd;
|
|
69
|
+
expect(person["@type"]).toBe("Person");
|
|
70
|
+
expect(person["@id"]).toBe("#person");
|
|
71
|
+
expect(person["jobTitle"]).toBe(FIRST_MEMBER.frontmatter.schema.jobTitle);
|
|
72
|
+
|
|
73
|
+
const breadcrumb = graph.find((b) => b["@type"] === "BreadcrumbList")!;
|
|
74
|
+
const items = breadcrumb["itemListElement"] as Array<{ item: string }>;
|
|
75
|
+
expect(items[1]!.item).toMatch(/\/en\/team$/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("renders a sections-carrying profile (the _base seed's shape) through its content-block, with the same valid graph", async () => {
|
|
79
|
+
const element = await TeamMemberPage({
|
|
80
|
+
params: Promise.resolve({ locale: "en", slug: "first-member" }),
|
|
81
|
+
getMdxSource: jest.fn().mockResolvedValue({
|
|
82
|
+
...FIRST_MEMBER,
|
|
83
|
+
frontmatter: {
|
|
84
|
+
...FIRST_MEMBER.frontmatter,
|
|
85
|
+
sections: [
|
|
86
|
+
{
|
|
87
|
+
id: "profile",
|
|
88
|
+
type: "content-block",
|
|
89
|
+
data: {
|
|
90
|
+
headline: FIRST_MEMBER.frontmatter.title,
|
|
91
|
+
body: "{Two or three sentences on what they do here.}",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
const { container } = render(element);
|
|
99
|
+
expect(container.querySelector("[data-testid='mdx-body']")).toBeNull();
|
|
100
|
+
expect(container.textContent).toContain(
|
|
101
|
+
"{Two or three sentences on what they do here.}",
|
|
102
|
+
);
|
|
103
|
+
const graph = jsonLdScripts(container)[0]!["@graph"] as JsonLd[];
|
|
104
|
+
expect(validatePageGraph(graph as never, "TeamMember")).toEqual(
|
|
105
|
+
expect.objectContaining({ valid: true }),
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("404s an unknown slug through renderPage, with no translations namespace", async () => {
|
|
110
|
+
const renderPageSpy = jest.spyOn(pageRenderer, "renderPage");
|
|
111
|
+
await expect(
|
|
112
|
+
TeamMemberPage({
|
|
113
|
+
params: Promise.resolve({ locale: "en", slug: "unknown" }),
|
|
114
|
+
getMdxSource: jest.fn().mockResolvedValue(null),
|
|
115
|
+
}),
|
|
116
|
+
).rejects.toThrow("NEXT_NOT_FOUND");
|
|
117
|
+
expect(renderPageSpy).toHaveBeenCalledTimes(1);
|
|
118
|
+
expect(renderPageSpy.mock.calls[0]![0].translationsNamespace).toBeUndefined();
|
|
119
|
+
renderPageSpy.mockRestore();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("team — the index page", () => {
|
|
124
|
+
it("emits a block the schema validator accepts as TeamList", async () => {
|
|
125
|
+
const element = await TeamPage({
|
|
126
|
+
params: Promise.resolve({ locale: "en" }),
|
|
127
|
+
getMdxSource: jest.fn().mockResolvedValue({
|
|
128
|
+
frontmatter: {
|
|
129
|
+
type: "TeamList",
|
|
130
|
+
slug: "team",
|
|
131
|
+
title: "Team",
|
|
132
|
+
description: "The people behind the work.",
|
|
133
|
+
},
|
|
134
|
+
body: "The team.",
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
const { container } = render(element);
|
|
138
|
+
const scripts = jsonLdScripts(container);
|
|
139
|
+
expect(scripts).toHaveLength(1);
|
|
140
|
+
expect(validateBlock(scripts[0] as never, "TeamList")).toEqual(
|
|
141
|
+
expect.objectContaining({ valid: true }),
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("renders the empty state from the team namespace when the page is missing", async () => {
|
|
146
|
+
const element = await TeamPage({
|
|
147
|
+
params: Promise.resolve({ locale: "en" }),
|
|
148
|
+
getMdxSource: jest.fn().mockResolvedValue(null),
|
|
149
|
+
});
|
|
150
|
+
const { getByTestId } = render(element);
|
|
151
|
+
expect(getByTestId("section-empty-state").textContent).toBe(
|
|
152
|
+
"Team listing coming soon.",
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Team list page (#7744 D2). Reads TeamList MDX via the content adapter and emits
|
|
2
|
+
// a JSON-LD CollectionPage via the dispatcher.
|
|
3
|
+
// A copy of the portfolio hub (#6687) with the type and slug changed — the /for
|
|
4
|
+
// composed pattern: sections when the frontmatter carries `sections[]`, MDX
|
|
5
|
+
// prose otherwise.
|
|
6
|
+
import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
|
|
7
|
+
import {
|
|
8
|
+
getMdxSource as loadMdxSource,
|
|
9
|
+
renderPage,
|
|
10
|
+
renderPageMetadata,
|
|
11
|
+
} from "@working-theory/page-renderer";
|
|
12
|
+
import type { Metadata } from "next";
|
|
13
|
+
import type { ReactElement } from "react";
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
params: Promise<{ locale: string }>;
|
|
17
|
+
getMdxSource?: typeof defaultGetMdxSource;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function generateMetadata({
|
|
21
|
+
params,
|
|
22
|
+
}: Pick<Props, "params">): Promise<Metadata> {
|
|
23
|
+
const { locale } = await params;
|
|
24
|
+
return renderPageMetadata({
|
|
25
|
+
type: "TeamList",
|
|
26
|
+
slug: "team",
|
|
27
|
+
locale,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default async function Page({
|
|
32
|
+
params,
|
|
33
|
+
getMdxSource,
|
|
34
|
+
}: Props): Promise<ReactElement> {
|
|
35
|
+
const { locale } = await params;
|
|
36
|
+
const source = await (getMdxSource ?? loadMdxSource)("TeamList", "team", locale);
|
|
37
|
+
const composed =
|
|
38
|
+
source !== null &&
|
|
39
|
+
Array.isArray((source.frontmatter as { sections?: unknown[] }).sections) &&
|
|
40
|
+
((source.frontmatter as { sections?: unknown[] }).sections?.length ?? 0) > 0;
|
|
41
|
+
return renderPage({
|
|
42
|
+
type: "TeamList",
|
|
43
|
+
slug: "team",
|
|
44
|
+
locale,
|
|
45
|
+
translationsNamespace: "team",
|
|
46
|
+
...(composed ? {} : { bodyMode: "mdx-body" as const }),
|
|
47
|
+
...(getMdxSource !== undefined && { getMdxSource }),
|
|
48
|
+
});
|
|
49
|
+
}
|
package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx
CHANGED
|
@@ -62,9 +62,9 @@ describe("tech-articles/[slug] — snapshot regression", () => {
|
|
|
62
62
|
});
|
|
63
63
|
const { container } = render(element);
|
|
64
64
|
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
65
|
-
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"AEO Layer 1 Scoring","description":"Deep dive into AEO Layer 1 scoring and AILK audit internals.","mainEntity":{"@context":"https://schema.org","@type":"TechArticle","headline":"AEO Layer 1 Scoring","description":"Deep dive into AEO Layer 1 scoring and AILK audit internals."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Tech Articles","item":"http://localhost:3000/en/tech-articles"},{"@type":"ListItem","position":3,"name":"AEO Layer 1 Scoring","item":"http://localhost:3000/en/tech-articles/aeo-layer1-scoring"}]}]}</script><main id="main"><h1>AEO Layer 1 Scoring</h1><p data-testid="tech-article-description">Deep dive into AEO Layer 1 scoring and AILK audit internals.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-rendered">## AEO Layer 1
|
|
65
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"AEO Layer 1 Scoring","description":"Deep dive into AEO Layer 1 scoring and AILK audit internals.","mainEntity":{"@context":"https://schema.org","@type":"TechArticle","headline":"AEO Layer 1 Scoring","description":"Deep dive into AEO Layer 1 scoring and AILK audit internals."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Tech Articles","item":"http://localhost:3000/en/tech-articles"},{"@type":"ListItem","position":3,"name":"AEO Layer 1 Scoring","item":"http://localhost:3000/en/tech-articles/aeo-layer1-scoring"}]}]}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">AEO Layer 1 Scoring</h1><p data-testid="tech-article-description" class="mt-block-tight text-lead">Deep dive into AEO Layer 1 scoring and AILK audit internals.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle mt-block-loose"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-fg) text-body-sm leading-relaxed [&>*:first-child]:mt-0 [&_h2]:mt-block-loose [&_h2]:scroll-mt-24 [&_h2]:text-heading-3 [&_h2]:font-display [&_h2]:text-(--color-fg) [&_h3]:mt-block-loose [&_h3]:scroll-mt-24 [&_h3]:text-heading-4 [&_h3]:font-display [&_h3]:text-(--color-fg) [&_h4]:mt-block-tight [&_h4]:scroll-mt-24 [&_h4]:text-heading-4 [&_h4]:font-display [&_h4]:text-(--color-fg) [&_h5]:mt-block-tight [&_h5]:scroll-mt-24 [&_h5]:text-heading-4 [&_h5]:font-display [&_h5]:text-(--color-fg) [&_h6]:mt-block-tight [&_h6]:scroll-mt-24 [&_h6]:text-heading-4 [&_h6]:font-display [&_h6]:text-(--color-fg) [&_p]:mt-paragraph [&_strong]:font-semibold [&_strong]:text-(--color-fg) [&_em]:italic [&_ul]:mt-paragraph [&_ul]:list-disc [&_ul]:ps-6 [&_ol]:mt-paragraph [&_ol]:list-decimal [&_ol]:ps-6 [&_li]:mt-1 [&_li]:ps-1 [&_li]:marker:text-(--color-text-muted) [&_li>ul]:mt-1 [&_li>ol]:mt-1 [&_blockquote]:mt-paragraph [&_blockquote]:border-s-4 [&_blockquote]:border-(--color-border-strong) [&_blockquote]:ps-4 [&_blockquote]:italic [&_blockquote]:text-(--color-text-muted) [&_hr]:my-block-loose [&_hr]:border-(--color-border) [&_a]:text-(--color-accent) [&_a]:underline [&_a]:underline-offset-4 [&_:not(pre)>code]:rounded [&_:not(pre)>code]:bg-(--color-surface-secondary) [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:text-code [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-(--color-fg) [&_pre]:mt-paragraph [&_pre]:overflow-auto [&_pre]:rounded-md [&_pre]:border-hairline [&_pre]:border-(--color-border) [&_pre]:bg-(--color-bg) [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-body-sm [&_img]:mt-paragraph [&_img]:h-auto [&_img]:max-w-full [&_img]:rounded-lg [&_table]:mt-paragraph [&_table]:w-full [&_table]:border-collapse [&_table]:text-body-sm [&_th]:border-b [&_th]:border-(--color-border) [&_th]:bg-(--color-surface-secondary) [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:font-semibold [&_th]:text-(--color-fg) [&_td]:border-b [&_td]:border-(--color-border) [&_td]:px-3 [&_td]:py-2 [&_kbd]:rounded [&_kbd]:border-hairline [&_kbd]:border-(--color-border) [&_kbd]:bg-(--color-surface-secondary) [&_kbd]:px-1.5 [&_kbd]:py-0.5 [&_kbd]:text-caption [&_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-rendered">## AEO Layer 1
|
|
66
66
|
|
|
67
|
-
Body content.</div></article></div></div></main>"
|
|
67
|
+
Body content.</div></div></article></div></div></div></main>"
|
|
68
68
|
`);
|
|
69
69
|
});
|
|
70
70
|
|
package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx
CHANGED
|
@@ -16,6 +16,19 @@ import { render } from "@testing-library/react";
|
|
|
16
16
|
|
|
17
17
|
// ── @working-theory/ui mock ─────────────────────────────────────────────────────────────
|
|
18
18
|
jest.mock("@working-theory/ui", () => ({
|
|
19
|
+
// #7745: renderPage's mdx-body branch frames its head and body in the
|
|
20
|
+
// section Container and renders the body through MDXContent + the shared map.
|
|
21
|
+
Container: ({
|
|
22
|
+
children,
|
|
23
|
+
}: {
|
|
24
|
+
children?: React.ReactNode;
|
|
25
|
+
}): React.ReactElement => React.createElement("div", null, children),
|
|
26
|
+
MDXContent: ({
|
|
27
|
+
children,
|
|
28
|
+
}: {
|
|
29
|
+
children?: React.ReactNode;
|
|
30
|
+
}): React.ReactElement => React.createElement(React.Fragment, null, children),
|
|
31
|
+
getMDXComponents: (overrides?: Record<string, unknown>) => ({ ...overrides }),
|
|
19
32
|
// #4446/#4447: renderPage's own fallback/empty-state title now composes
|
|
20
33
|
// Heading (mobile type-budget classes, #4417) instead of a bare <h1>.
|
|
21
34
|
Heading: ({
|
|
@@ -66,7 +79,7 @@ describe("TestimonialsPage — snapshot regression", () => {
|
|
|
66
79
|
});
|
|
67
80
|
const { container } = render(element);
|
|
68
81
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
69
|
-
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Customer Testimonials","description":"What our customers say.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Review"}]},"aggregateRating":{"@context":"https://schema.org","@type":"AggregateRating","ratingValue":4.9,"reviewCount":0,"bestRating":5,"worstRating":1,"itemReviewed":{"@type":"SoftwareApplication"}}}</script><main id="main"><h1>Customer Testimonials</h1><p data-testid="testimonials-description">What our customers say.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-remote" data-source=""></div></article></div></div></main>"`,
|
|
82
|
+
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Customer Testimonials","description":"What our customers say.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Review"}]},"aggregateRating":{"@context":"https://schema.org","@type":"AggregateRating","ratingValue":4.9,"reviewCount":0,"bestRating":5,"worstRating":1,"itemReviewed":{"@type":"SoftwareApplication"}}}</script><main id="main"><div><h1>Customer Testimonials</h1><p data-testid="testimonials-description" class="mt-block-tight text-lead">What our customers say.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle mt-block-loose"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-remote" data-source=""></div></article></div></div></div></main>"`,
|
|
70
83
|
);
|
|
71
84
|
});
|
|
72
85
|
});
|
package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx
CHANGED
|
@@ -62,9 +62,9 @@ describe("whitepapers/[slug] — snapshot regression", () => {
|
|
|
62
62
|
});
|
|
63
63
|
const { container } = render(element);
|
|
64
64
|
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
65
|
-
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"AILK AEO Strategy 2026","description":"In-depth research on answer engine optimization for AILK products.","mainEntity":{"@context":"https://schema.org","@type":"Article","headline":"AILK AEO Strategy 2026","description":"In-depth research on answer engine optimization for AILK products."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Whitepapers","item":"http://localhost:3000/en/whitepapers"},{"@type":"ListItem","position":3,"name":"AILK AEO Strategy 2026","item":"http://localhost:3000/en/whitepapers/ailk-aeo-strategy-2026"}]}]}</script><main id="main"><h1>AILK AEO Strategy 2026</h1><p data-testid="whitepaper-description">In-depth research on answer engine optimization for AILK products.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-rendered">## Executive Summary
|
|
65
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"AILK AEO Strategy 2026","description":"In-depth research on answer engine optimization for AILK products.","mainEntity":{"@context":"https://schema.org","@type":"Article","headline":"AILK AEO Strategy 2026","description":"In-depth research on answer engine optimization for AILK products."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Whitepapers","item":"http://localhost:3000/en/whitepapers"},{"@type":"ListItem","position":3,"name":"AILK AEO Strategy 2026","item":"http://localhost:3000/en/whitepapers/ailk-aeo-strategy-2026"}]}]}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">AILK AEO Strategy 2026</h1><p data-testid="whitepaper-description" class="mt-block-tight text-lead">In-depth research on answer engine optimization for AILK products.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle mt-block-loose"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-fg) text-body-sm leading-relaxed [&>*:first-child]:mt-0 [&_h2]:mt-block-loose [&_h2]:scroll-mt-24 [&_h2]:text-heading-3 [&_h2]:font-display [&_h2]:text-(--color-fg) [&_h3]:mt-block-loose [&_h3]:scroll-mt-24 [&_h3]:text-heading-4 [&_h3]:font-display [&_h3]:text-(--color-fg) [&_h4]:mt-block-tight [&_h4]:scroll-mt-24 [&_h4]:text-heading-4 [&_h4]:font-display [&_h4]:text-(--color-fg) [&_h5]:mt-block-tight [&_h5]:scroll-mt-24 [&_h5]:text-heading-4 [&_h5]:font-display [&_h5]:text-(--color-fg) [&_h6]:mt-block-tight [&_h6]:scroll-mt-24 [&_h6]:text-heading-4 [&_h6]:font-display [&_h6]:text-(--color-fg) [&_p]:mt-paragraph [&_strong]:font-semibold [&_strong]:text-(--color-fg) [&_em]:italic [&_ul]:mt-paragraph [&_ul]:list-disc [&_ul]:ps-6 [&_ol]:mt-paragraph [&_ol]:list-decimal [&_ol]:ps-6 [&_li]:mt-1 [&_li]:ps-1 [&_li]:marker:text-(--color-text-muted) [&_li>ul]:mt-1 [&_li>ol]:mt-1 [&_blockquote]:mt-paragraph [&_blockquote]:border-s-4 [&_blockquote]:border-(--color-border-strong) [&_blockquote]:ps-4 [&_blockquote]:italic [&_blockquote]:text-(--color-text-muted) [&_hr]:my-block-loose [&_hr]:border-(--color-border) [&_a]:text-(--color-accent) [&_a]:underline [&_a]:underline-offset-4 [&_:not(pre)>code]:rounded [&_:not(pre)>code]:bg-(--color-surface-secondary) [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:text-code [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-(--color-fg) [&_pre]:mt-paragraph [&_pre]:overflow-auto [&_pre]:rounded-md [&_pre]:border-hairline [&_pre]:border-(--color-border) [&_pre]:bg-(--color-bg) [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-body-sm [&_img]:mt-paragraph [&_img]:h-auto [&_img]:max-w-full [&_img]:rounded-lg [&_table]:mt-paragraph [&_table]:w-full [&_table]:border-collapse [&_table]:text-body-sm [&_th]:border-b [&_th]:border-(--color-border) [&_th]:bg-(--color-surface-secondary) [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:font-semibold [&_th]:text-(--color-fg) [&_td]:border-b [&_td]:border-(--color-border) [&_td]:px-3 [&_td]:py-2 [&_kbd]:rounded [&_kbd]:border-hairline [&_kbd]:border-(--color-border) [&_kbd]:bg-(--color-surface-secondary) [&_kbd]:px-1.5 [&_kbd]:py-0.5 [&_kbd]:text-caption [&_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-rendered">## Executive Summary
|
|
66
66
|
|
|
67
|
-
Body content.</div></article></div></div></main>"
|
|
67
|
+
Body content.</div></div></article></div></div></div></main>"
|
|
68
68
|
`);
|
|
69
69
|
});
|
|
70
70
|
|
|
@@ -6,12 +6,59 @@
|
|
|
6
6
|
// app/[locale]/layout.tsx — the only other importer of globals.css — so it
|
|
7
7
|
// imports the stylesheet itself; without it the primitives' token classes
|
|
8
8
|
// would render unstyled here (#6514).
|
|
9
|
+
//
|
|
10
|
+
// Under `chromeStyle: minimal` (#7742 D6) every 404 renders in the site's
|
|
11
|
+
// theme instead: its own <html> and <body> (the documented pattern for a root
|
|
12
|
+
// not-found under a pass-through root layout), the theme variables and font
|
|
13
|
+
// class the locale layout emits (`siteThemeHead`), the site's mark, one
|
|
14
|
+
// statement and a back link. `data-theme` is fixed at light — the one mode a
|
|
15
|
+
// `colorMode: light` site has. Without the knob it renders today's markup.
|
|
9
16
|
import "./globals.css";
|
|
10
17
|
|
|
18
|
+
import { BrandLogo } from "@working-theory/ui/blocks";
|
|
11
19
|
import { Heading, Link, Text } from "@working-theory/ui/primitives";
|
|
12
20
|
import type { ReactElement } from "react";
|
|
13
21
|
|
|
22
|
+
import { SITE_NAME } from "../lib/site-brand";
|
|
23
|
+
import { siteChrome, type SiteChrome } from "../lib/site-chrome";
|
|
24
|
+
import { siteLogo } from "../lib/site-logo";
|
|
25
|
+
import { siteThemeHead } from "../lib/site-theme-head";
|
|
26
|
+
|
|
27
|
+
/** Widened as the locale layout reads it: an older generated file has no `chromeStyle`. */
|
|
28
|
+
const chrome: SiteChrome & { chromeStyle?: "minimal" } = siteChrome;
|
|
29
|
+
|
|
30
|
+
function ThemedNotFound(): ReactElement {
|
|
31
|
+
const head = siteThemeHead();
|
|
32
|
+
return (
|
|
33
|
+
<html lang="en" data-theme="light" className={head.fontClassName}>
|
|
34
|
+
<head>
|
|
35
|
+
<style data-ailk="theme-vars" dangerouslySetInnerHTML={{ __html: head.css }} />
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<main
|
|
39
|
+
data-testid="not-found"
|
|
40
|
+
className="flex min-h-screen flex-col items-center justify-center gap-block-loose bg-(--color-bg) p-card text-(--color-fg)"
|
|
41
|
+
>
|
|
42
|
+
<BrandLogo
|
|
43
|
+
locale="en"
|
|
44
|
+
siteName={SITE_NAME}
|
|
45
|
+
name={SITE_NAME}
|
|
46
|
+
href="/"
|
|
47
|
+
composition="logomark"
|
|
48
|
+
mark={siteLogo()}
|
|
49
|
+
/>
|
|
50
|
+
<Heading level={1}>Page not found</Heading>
|
|
51
|
+
<Link href="/" data-testid="not-found-back">
|
|
52
|
+
Back to home
|
|
53
|
+
</Link>
|
|
54
|
+
</main>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
14
60
|
export default function NotFound(): ReactElement {
|
|
61
|
+
if (chrome.chromeStyle === "minimal") return <ThemedNotFound />;
|
|
15
62
|
return (
|
|
16
63
|
<main className="flex min-h-screen flex-col items-center justify-center gap-4 bg-(--color-bg) p-card text-(--color-fg)">
|
|
17
64
|
<Heading level={1} visual={3}>
|