create-ailk 0.26.0 → 0.28.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 +86 -9
- package/dist/add-module-core.js +2 -1
- package/dist/add-module.d.ts +2 -1
- package/dist/add-module.js +9 -4
- package/dist/add-route.js +46 -19
- package/dist/body-heading.d.ts +56 -0
- package/dist/body-heading.js +394 -0
- package/dist/cli.js +28 -1
- package/dist/content-payloads.js +2 -2
- package/dist/derive-architecture.js +8 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/lib/auth-secret-file.d.ts +6 -2
- package/dist/lib/auth-secret-file.js +46 -20
- package/dist/lib/env-catalog.js +20 -0
- package/dist/lib/site-modules.d.ts +8 -0
- package/dist/lib/site-modules.js +34 -0
- package/dist/module-architecture.js +25 -14
- package/dist/paid-sections.d.ts +17 -0
- package/dist/paid-sections.js +39 -0
- package/dist/programmatic.d.ts +16 -0
- package/dist/programmatic.js +73 -4
- package/dist/remove-module.d.ts +98 -0
- package/dist/remove-module.js +620 -0
- package/dist/seed-flows.d.ts +2 -2
- package/dist/seed-flows.js +16 -9
- package/dist/site-roster.js +4 -1
- package/dist/starter-grid.d.ts +26 -0
- package/dist/starter-grid.js +34 -0
- package/dist/surfaces.d.ts +26 -0
- package/dist/surfaces.js +112 -11
- package/dist/sync-routes.d.ts +7 -0
- package/dist/sync-routes.js +53 -6
- package/dist/template-pages.d.ts +36 -0
- package/dist/template-pages.js +129 -0
- package/dist/topology.d.ts +2 -1
- package/dist/topology.js +4 -2
- package/dist/update.d.ts +3 -1
- package/dist/update.js +90 -6
- package/package.json +3 -3
- package/release-scope.json +2 -1
- package/site-templates/aliases/hub.TEMPLATE-MANIFEST.json +115 -0
- package/templates/.env.example +12 -2
- package/templates/CLAUDE.md +20 -20
- package/templates/README.md +19 -19
- package/templates/apps/api/.env.example +16 -5
- package/templates/apps/api/CLAUDE.md +32 -11
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +37 -0
- package/templates/apps/api/src/vercel-handler.ts +8 -1
- package/templates/apps/api/vercel.json +7 -1
- package/templates/apps/mcp/CLAUDE.md +18 -10
- package/templates/apps/web/.env.example +27 -0
- package/templates/apps/web/CLAUDE.md +56 -8
- package/templates/apps/web/__tests__/setup.ts +7 -0
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +7 -7
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/next-path.test.ts +24 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +51 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-in/next-path.ts +13 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +10 -4
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/page.test.tsx +197 -4
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +23 -7
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/versions/[version]/file/route.ts +61 -2
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/file-route.test.ts +81 -0
- package/templates/apps/web/app/[locale]/(authed)/account/connect/__tests__/page.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/(authed)/account/connect/page.tsx +152 -0
- package/templates/apps/web/app/[locale]/about/__tests__/about-routes.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +76 -21
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +46 -16
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +180 -2
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +122 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/hub-children.test.tsx +65 -0
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/case-studies/page.tsx +23 -11
- package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/consent.actions.ts +44 -6
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +269 -42
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +88 -30
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/courses/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/courses/page.tsx +22 -6
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +15 -2
- package/templates/apps/web/app/[locale]/events/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +8 -2
- package/templates/apps/web/app/[locale]/events/page.tsx +23 -7
- package/templates/apps/web/app/[locale]/features/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/features/page.tsx +23 -25
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +95 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/save.actions.test.ts +193 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/email-moment.ts +92 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +10 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +513 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +122 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.no-commerce.test.tsx +195 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/landing.test.tsx +293 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +397 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/checkout-seam.ts +74 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +264 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/save.actions.ts +75 -0
- package/templates/apps/web/app/[locale]/for/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +5 -1
- package/templates/apps/web/app/[locale]/for/page.tsx +23 -25
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +25 -4
- package/templates/apps/web/app/[locale]/integrations/__tests__/hub-children.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +8 -2
- package/templates/apps/web/app/[locale]/integrations/page.tsx +23 -11
- package/templates/apps/web/app/[locale]/layout.tsx +161 -128
- package/templates/apps/web/app/[locale]/page.tsx +51 -3
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +16 -3
- package/templates/apps/web/app/[locale]/partners/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/partners/page.tsx +22 -6
- package/templates/apps/web/app/[locale]/portfolio/__tests__/hub-children.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/portfolio/page.tsx +25 -24
- package/templates/apps/web/app/[locale]/pricing/[slug]/__tests__/page.test.tsx +321 -0
- package/templates/apps/web/app/[locale]/pricing/[slug]/page.tsx +87 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/pricing-pages.test.tsx +215 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +56 -341
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +21 -4
- package/templates/apps/web/app/[locale]/products/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +8 -2
- package/templates/apps/web/app/[locale]/products/page.tsx +22 -6
- package/templates/apps/web/app/[locale]/projects/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -0
- package/templates/apps/web/app/[locale]/projects/page.tsx +18 -11
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +25 -4
- package/templates/apps/web/app/[locale]/resources/__tests__/hub-children.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/resources/page.tsx +20 -10
- package/templates/apps/web/app/[locale]/services/[slug]/[child]/page.tsx +68 -0
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +64 -9
- package/templates/apps/web/app/[locale]/services/__tests__/hub-children.test.tsx +158 -0
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/services/__tests__/service-detail-sections.test.tsx +223 -0
- package/templates/apps/web/app/[locale]/services/__tests__/service-families.test.tsx +368 -0
- package/templates/apps/web/app/[locale]/services/page.tsx +45 -19
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/team/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/team/page.tsx +25 -24
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +24 -3
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/templates/[slug]/__tests__/page.test.tsx +203 -0
- package/templates/apps/web/app/[locale]/templates/[slug]/page.tsx +81 -0
- package/templates/apps/web/app/[locale]/templates/__tests__/page.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/templates/page.tsx +108 -0
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +24 -3
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +322 -2
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +161 -29
- package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +51 -0
- package/templates/apps/web/app/api/hub-session/sign-in/route.ts +13 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +233 -7
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +92 -41
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +77 -39
- package/templates/apps/web/app/llms.txt/route.ts +41 -3
- package/templates/apps/web/app/robots.txt/route.ts +5 -1
- package/templates/apps/web/app/sitemap.xml/route.ts +45 -3
- package/templates/apps/web/auth.ts +102 -50
- package/templates/apps/web/components/signed-in-nav.tsx +8 -1
- package/templates/apps/web/lib/__tests__/bot-check.test.ts +254 -0
- package/templates/apps/web/lib/__tests__/consent-visitor.test.ts +212 -13
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +13 -1
- package/templates/apps/web/lib/__tests__/docs-index.test.ts +93 -0
- package/templates/apps/web/lib/__tests__/docs-path.test.ts +168 -0
- package/templates/apps/web/lib/__tests__/hub-children.fixtures.ts +278 -0
- package/templates/apps/web/lib/__tests__/nav-label-keys.test.ts +138 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +178 -2
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +183 -0
- package/templates/apps/web/lib/__tests__/page-chrome.test.ts +412 -0
- package/templates/apps/web/lib/__tests__/pricing-page.test.ts +142 -0
- package/templates/apps/web/lib/__tests__/relay.test.ts +484 -4
- package/templates/apps/web/lib/__tests__/route-family.test.ts +57 -0
- package/templates/apps/web/lib/__tests__/service-listing.test.ts +111 -0
- package/templates/apps/web/lib/__tests__/services-path.test.ts +96 -0
- package/templates/apps/web/lib/__tests__/site-header.test.ts +151 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +148 -6
- package/templates/apps/web/lib/__tests__/telemetry-scrub.test.ts +84 -0
- package/templates/apps/web/lib/__tests__/template-listing.test.ts +158 -0
- package/templates/apps/web/lib/auth-secret.ts +57 -0
- package/templates/apps/web/lib/bot-check.ts +138 -0
- package/templates/apps/web/lib/consent-visitor.ts +81 -7
- package/templates/apps/web/lib/deck-page.ts +6 -3
- package/templates/apps/web/lib/docs-index.ts +100 -0
- package/templates/apps/web/lib/docs-path.ts +94 -0
- package/templates/apps/web/lib/hub-children.tsx +318 -0
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +22 -2
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +11 -8
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/nav-label-check.ts +61 -0
- package/templates/apps/web/lib/nav-label-keys.ts +78 -0
- package/templates/apps/web/lib/newsletter.actions.ts +31 -6
- package/templates/apps/web/lib/offerings-pricing.ts +135 -6
- package/templates/apps/web/lib/page-chrome.ts +367 -0
- package/templates/apps/web/lib/posting-title.ts +26 -4
- package/templates/apps/web/lib/pricing-page.ts +430 -0
- package/templates/apps/web/lib/relay.ts +44 -4
- package/templates/apps/web/lib/route-family.ts +69 -0
- package/templates/apps/web/lib/service-listing.ts +89 -0
- package/templates/apps/web/lib/services-path.ts +97 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +17 -6
- package/templates/apps/web/lib/site-header.ts +94 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +68 -3
- package/templates/apps/web/lib/template-listing.ts +169 -0
- package/templates/apps/web/messages/en.json +144 -3
- package/templates/apps/web/middleware.ts +179 -12
- package/templates/apps/web/next.config.mjs +220 -3
- package/templates/apps/web/package.json +3 -2
- package/templates/apps/web/scripts/assert-content-bundled.ts +28 -6
- package/templates/apps/web/scripts/check-nav-labels.ts +39 -0
- package/templates/apps/web/sentry.server.config.ts +30 -2
- package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -9
- package/templates/apps/web/src/lib/rate-limit.ts +68 -27
- package/templates/apps/web/src/lib/routes.ts +15 -1
- package/versions.json +48 -48
package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx
CHANGED
|
@@ -70,6 +70,8 @@ describe("IntegrationsPage — snapshot regression", () => {
|
|
|
70
70
|
const getMdxSourceEmpty = jest.fn().mockResolvedValue(null);
|
|
71
71
|
const element = await IntegrationsPage({
|
|
72
72
|
params: Promise.resolve({ locale: "en" }),
|
|
73
|
+
// No child pages: this snapshot pins the hub's own render (#10121).
|
|
74
|
+
getMdxSlugs: async () => [],
|
|
73
75
|
getMdxSource: getMdxSourceEmpty,
|
|
74
76
|
});
|
|
75
77
|
const { container } = render(element);
|
|
@@ -83,11 +85,14 @@ describe("IntegrationsPage — snapshot regression", () => {
|
|
|
83
85
|
const getMdxSourcePopulated = jest.fn().mockResolvedValue(source);
|
|
84
86
|
const element = await IntegrationsPage({
|
|
85
87
|
params: Promise.resolve({ locale: "en" }),
|
|
88
|
+
// No child pages: this snapshot pins the hub's own render (#10121).
|
|
89
|
+
getMdxSlugs: async () => [],
|
|
86
90
|
getMdxSource: getMdxSourcePopulated,
|
|
87
91
|
});
|
|
88
92
|
const { container } = render(element);
|
|
89
93
|
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
90
|
-
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Integrations","description":"First-party integrations for AI Launch Kit.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"SoftwareApplication"}]}}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><
|
|
94
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Integrations","description":"First-party integrations for AI Launch Kit.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"SoftwareApplication"}]}}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><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 motion-safe:transition motion-safe:duration-(--button-hover-duration) 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) not-disabled:hover:bg-(--button-hover-quiet-bg) not-disabled:hover:text-(--button-hover-quiet-fg) not-disabled:hover:opacity-(--button-hover-opacity) not-disabled:hover:transform-(--button-hover-transform) not-disabled:hover:shadow-(--button-hover-shadow) not-disabled:active:bg-gray-200 not-disabled:active:text-(--color-fg) 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 motion-safe:transition motion-safe:duration-(--button-hover-duration) 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) not-disabled:hover:bg-(--button-hover-quiet-bg) not-disabled:hover:text-(--button-hover-quiet-fg) not-disabled:hover:opacity-(--button-hover-opacity) not-disabled:hover:transform-(--button-hover-transform) not-disabled:hover:shadow-(--button-hover-shadow) not-disabled:active:bg-gray-200 not-disabled:active:text-(--color-fg) 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-text-muted) 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="# Integrations
|
|
95
|
+
|
|
91
96
|
Body."></div></div></article></div></div></div></main>"
|
|
92
97
|
`);
|
|
93
98
|
});
|
|
@@ -110,7 +115,8 @@ describe("IntegrationSlugPage — snapshot regression", () => {
|
|
|
110
115
|
});
|
|
111
116
|
const { container } = render(element);
|
|
112
117
|
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
113
|
-
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"OpenAI API","description":"Connect AILK to OpenAI.","mainEntity":{"@context":"https://schema.org","@type":"SoftwareApplication","name":"OpenAI API","description":"Connect AILK to OpenAI."}},{"@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":"Integrations","item":"http://localhost:3000/en/integrations"},{"@type":"ListItem","position":3,"name":"OpenAI API","item":"http://localhost:3000/en/integrations/openai-api"}]}]}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><
|
|
118
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"OpenAI API","description":"Connect AILK to OpenAI.","mainEntity":{"@context":"https://schema.org","@type":"SoftwareApplication","name":"OpenAI API","description":"Connect AILK to OpenAI."}},{"@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":"Integrations","item":"http://localhost:3000/en/integrations"},{"@type":"ListItem","position":3,"name":"OpenAI API","item":"http://localhost:3000/en/integrations/openai-api"}]}]}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><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 motion-safe:transition motion-safe:duration-(--button-hover-duration) 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) not-disabled:hover:bg-(--button-hover-quiet-bg) not-disabled:hover:text-(--button-hover-quiet-fg) not-disabled:hover:opacity-(--button-hover-opacity) not-disabled:hover:transform-(--button-hover-transform) not-disabled:hover:shadow-(--button-hover-shadow) not-disabled:active:bg-gray-200 not-disabled:active:text-(--color-fg) 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 motion-safe:transition motion-safe:duration-(--button-hover-duration) 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) not-disabled:hover:bg-(--button-hover-quiet-bg) not-disabled:hover:text-(--button-hover-quiet-fg) not-disabled:hover:opacity-(--button-hover-opacity) not-disabled:hover:transform-(--button-hover-transform) not-disabled:hover:shadow-(--button-hover-shadow) not-disabled:active:bg-gray-200 not-disabled:active:text-(--color-fg) 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-text-muted) 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="# OpenAI API
|
|
119
|
+
|
|
114
120
|
Body."></div></div></article></div></div></div></main>"
|
|
115
121
|
`);
|
|
116
122
|
});
|
|
@@ -1,37 +1,49 @@
|
|
|
1
1
|
// Integrations list page. Reads IntegrationsList MDX via the content adapter and
|
|
2
2
|
// emits a JSON-LD CollectionPage via the dispatcher.
|
|
3
|
-
|
|
3
|
+
// It lists every Integration page through one generated card section (#10121,
|
|
4
|
+
// apps/web/lib/hub-children.tsx — the /alternatives and /templates shape).
|
|
5
|
+
import type {
|
|
6
|
+
getMdxSlugs as defaultGetMdxSlugs,
|
|
7
|
+
getMdxSource as defaultGetMdxSource,
|
|
8
|
+
getPageSections as defaultGetPageSections,
|
|
9
|
+
} from "@working-theory/page-renderer";
|
|
4
10
|
import { renderPage, renderPageMetadata } from "@working-theory/page-renderer";
|
|
5
11
|
import type { Metadata } from "next";
|
|
6
12
|
import type { ReactElement } from "react";
|
|
7
13
|
|
|
14
|
+
import { type HubConfig, hubPageInput } from "../../../lib/hub-children";
|
|
15
|
+
|
|
8
16
|
interface Props {
|
|
9
17
|
params: Promise<{ locale: string }>;
|
|
10
18
|
getMdxSource?: typeof defaultGetMdxSource;
|
|
19
|
+
getPageSections?: typeof defaultGetPageSections;
|
|
20
|
+
getMdxSlugs?: typeof defaultGetMdxSlugs;
|
|
11
21
|
}
|
|
12
22
|
|
|
23
|
+
const HUB: HubConfig = {
|
|
24
|
+
listType: "IntegrationsList",
|
|
25
|
+
slug: "integrations",
|
|
26
|
+
childType: "Integration",
|
|
27
|
+
linkLabel: "View the integration",
|
|
28
|
+
sectionId: "integration-cards",
|
|
29
|
+
body: "mdx-body",
|
|
30
|
+
};
|
|
31
|
+
|
|
13
32
|
export async function generateMetadata({
|
|
14
33
|
params,
|
|
15
34
|
}: Pick<Props, "params">): Promise<Metadata> {
|
|
16
35
|
const { locale } = await params;
|
|
17
|
-
return renderPageMetadata({
|
|
18
|
-
type: "IntegrationsList",
|
|
19
|
-
slug: "integrations",
|
|
20
|
-
locale,
|
|
21
|
-
});
|
|
36
|
+
return renderPageMetadata({ type: "IntegrationsList", slug: "integrations", locale });
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
export default async function Page({
|
|
25
40
|
params,
|
|
26
|
-
|
|
41
|
+
...loaders
|
|
27
42
|
}: Props): Promise<ReactElement> {
|
|
28
43
|
const { locale } = await params;
|
|
29
44
|
return renderPage({
|
|
30
45
|
type: "IntegrationsList",
|
|
31
46
|
slug: "integrations",
|
|
32
|
-
locale,
|
|
33
|
-
translationsNamespace: "integrations",
|
|
34
|
-
bodyMode: "mdx-body",
|
|
35
|
-
...(getMdxSource !== undefined && { getMdxSource }),
|
|
47
|
+
...(await hubPageInput(HUB, locale, loaders)),
|
|
36
48
|
});
|
|
37
49
|
}
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
getStealthRobots,
|
|
49
49
|
readLocales,
|
|
50
50
|
} from "@working-theory/web-i18n";
|
|
51
|
+
import { BotIdClient } from "botid/client";
|
|
51
52
|
import type { Metadata, Viewport } from "next";
|
|
52
53
|
import { cookies, headers } from "next/headers";
|
|
53
54
|
import { NextIntlClientProvider } from "next-intl";
|
|
@@ -60,8 +61,13 @@ import {
|
|
|
60
61
|
type SignedInNavProps,
|
|
61
62
|
} from "../../components/signed-in-nav";
|
|
62
63
|
import { isAccountPath, isAuthPath, isBookingPath } from "../../lib/auth-routes";
|
|
64
|
+
import { BOT_CLIENT_PROTECT, botCheckOn } from "../../lib/bot-check";
|
|
63
65
|
import { isClosingFooterPath } from "../../lib/closing-footer-path";
|
|
64
66
|
import { pageOptsIntoDeck } from "../../lib/deck-page";
|
|
67
|
+
import {
|
|
68
|
+
withPublicDocsColumns,
|
|
69
|
+
withPublicDocsPaths,
|
|
70
|
+
} from "../../lib/docs-path";
|
|
65
71
|
import {
|
|
66
72
|
inPageView,
|
|
67
73
|
newsletterSignup,
|
|
@@ -75,14 +81,30 @@ import { assertEnabledLocale } from "../../lib/locale-guard";
|
|
|
75
81
|
import { isMagnetWalkPath } from "../../lib/magnet-routes";
|
|
76
82
|
import { SCHEDULING_PAGES } from "../../lib/modules.generated";
|
|
77
83
|
import { footerColumns, navItems } from "../../lib/nav-config";
|
|
84
|
+
import { navMessageKeys, usedNavLabelKeys } from "../../lib/nav-label-keys";
|
|
78
85
|
import { getOgImageUrl } from "../../lib/og";
|
|
86
|
+
import {
|
|
87
|
+
resolveChromePage,
|
|
88
|
+
resolveHomeChrome,
|
|
89
|
+
resolvePageChrome,
|
|
90
|
+
routeFamilyOf,
|
|
91
|
+
} from "../../lib/page-chrome";
|
|
79
92
|
import { backLinkHref } from "../../lib/profile-back-link";
|
|
93
|
+
import {
|
|
94
|
+
withPublicServicesColumns,
|
|
95
|
+
withPublicServicesPaths,
|
|
96
|
+
} from "../../lib/services-path";
|
|
80
97
|
import {
|
|
81
98
|
COPYRIGHT_HOLDER,
|
|
82
99
|
SITE_DESCRIPTION,
|
|
83
100
|
SITE_NAME,
|
|
84
101
|
} from "../../lib/site-brand";
|
|
85
102
|
import { siteChrome, type SiteChrome } from "../../lib/site-chrome";
|
|
103
|
+
import {
|
|
104
|
+
HEADER_CTA_LABEL_KEY,
|
|
105
|
+
readSiteHeader,
|
|
106
|
+
signedOutHeaderActions,
|
|
107
|
+
} from "../../lib/site-header";
|
|
86
108
|
import { siteLogo } from "../../lib/site-logo";
|
|
87
109
|
// Registers the `_site.mdx` identity with page-renderer at module load, so
|
|
88
110
|
// every page rendered under this layout carries the sitewide JSON-LD (#6597).
|
|
@@ -326,76 +348,36 @@ export default async function LocaleLayout({
|
|
|
326
348
|
// Resolve nav labels server-side so Nav + Footer components stay
|
|
327
349
|
// i18n-hook-free and are testable without a NextIntlClientProvider wrapper.
|
|
328
350
|
const tNav = await getTranslations({ locale, namespace: "nav" });
|
|
351
|
+
// #10045 D1 — every key resolves from the `nav` namespace by name: the keys
|
|
352
|
+
// the merged catalog carries (template + site overlay), every key
|
|
353
|
+
// `nav-config.ts` uses and the ones this layout names. There is no allow-list
|
|
354
|
+
// to extend; a key used but missing fails the build
|
|
355
|
+
// (`scripts/check-nav-labels.ts`). Entries for surfaces this site did not
|
|
356
|
+
// scaffold are never reached — `nav-config.ts` only lists surfaces the
|
|
357
|
+
// declared architecture carries.
|
|
358
|
+
const navLabelKeys = new Set([...navMessageKeys(messages), ...usedNavLabelKeys()]);
|
|
359
|
+
// The typed catalog knows only the template's keys; a site's own keys (in
|
|
360
|
+
// nav-config.ts or messages/site/<locale>.json) are plain strings, so look
|
|
361
|
+
// them up untyped — the build check is what guarantees each one exists.
|
|
362
|
+
const navText = tNav as unknown as (key: string) => string;
|
|
363
|
+
const navItemLabels: Record<string, string> = Object.fromEntries(
|
|
364
|
+
[...navLabelKeys].map((key) => [key, navText(key)]),
|
|
365
|
+
);
|
|
366
|
+
// Session-aware action labels (#4984). `account` carries the signed-in
|
|
367
|
+
// user's display name (name, else email) so the Nav shows WHO is signed
|
|
368
|
+
// in; it keeps the generic label when there is no session.
|
|
369
|
+
if (session?.user) {
|
|
370
|
+
navItemLabels["account"] = session.user.name ?? session.user.email;
|
|
371
|
+
}
|
|
372
|
+
// The site's own signed-out header button (#10046) — `_site.mdx`'s
|
|
373
|
+
// `header.cta`. Its label is site content, written straight into the map
|
|
374
|
+
// as `account` is; the composition is `signedOutHeaderActions` below.
|
|
375
|
+
const siteHeader = readSiteHeader();
|
|
376
|
+
if (siteHeader?.cta) {
|
|
377
|
+
navItemLabels[HEADER_CTA_LABEL_KEY] = siteHeader.cta.label;
|
|
378
|
+
}
|
|
329
379
|
const navLabels = {
|
|
330
|
-
items:
|
|
331
|
-
home: tNav("home"),
|
|
332
|
-
// The Free marketing-site chrome (#5469 D5): the Product flyout's three
|
|
333
|
-
// entries + descriptions, the top-level items, and the Get started CTA.
|
|
334
|
-
product: tNav("product"),
|
|
335
|
-
services: tNav("services"),
|
|
336
|
-
useCases: tNav("useCases"),
|
|
337
|
-
useCasesDesc: tNav("useCasesDesc"),
|
|
338
|
-
footerSitePagesHeading: tNav("footerSitePagesHeading"),
|
|
339
|
-
footerFeaturesHeading: tNav("footerFeaturesHeading"),
|
|
340
|
-
footerResourcesHeading: tNav("footerResourcesHeading"),
|
|
341
|
-
feature1: tNav("feature1"),
|
|
342
|
-
feature2: tNav("feature2"),
|
|
343
|
-
feature3: tNav("feature3"),
|
|
344
|
-
feature4: tNav("feature4"),
|
|
345
|
-
feature5: tNav("feature5"),
|
|
346
|
-
allFeatures: tNav("allFeatures"),
|
|
347
|
-
// #6878 D6 — one flyout rule for every shape: the offering column
|
|
348
|
-
// (Features or Services) beside the Customers column (the /for
|
|
349
|
-
// persona pages), plus the Customers footer column.
|
|
350
|
-
flyoutFeaturesHeading: tNav("flyoutFeaturesHeading"),
|
|
351
|
-
flyoutServicesHeading: tNav("flyoutServicesHeading"),
|
|
352
|
-
flyoutCustomersHeading: tNav("flyoutCustomersHeading"),
|
|
353
|
-
footerCustomersHeading: tNav("footerCustomersHeading"),
|
|
354
|
-
allCustomers: tNav("allCustomers"),
|
|
355
|
-
customer1: tNav("customer1"),
|
|
356
|
-
customer2: tNav("customer2"),
|
|
357
|
-
customer3: tNav("customer3"),
|
|
358
|
-
service1: tNav("service1"),
|
|
359
|
-
service2: tNav("service2"),
|
|
360
|
-
service3: tNav("service3"),
|
|
361
|
-
howItWorks: tNav("howItWorks"),
|
|
362
|
-
howItWorksDesc: tNav("howItWorksDesc"),
|
|
363
|
-
features: tNav("features"),
|
|
364
|
-
featuresDesc: tNav("featuresDesc"),
|
|
365
|
-
faq: tNav("faq"),
|
|
366
|
-
faqDesc: tNav("faqDesc"),
|
|
367
|
-
pricing: tNav("pricing"),
|
|
368
|
-
portfolio: tNav("portfolio"),
|
|
369
|
-
about: tNav("about"),
|
|
370
|
-
// The footer's column groups (#6468): their two headings + Contact.
|
|
371
|
-
contact: tNav("contact"),
|
|
372
|
-
// The three pages every starter's roster names (#6658 D8), linked from
|
|
373
|
-
// the footer when their surfaces are on.
|
|
374
|
-
bookACall: tNav("bookACall"),
|
|
375
|
-
glossary: tNav("glossary"),
|
|
376
|
-
newsletter: tNav("newsletter"),
|
|
377
|
-
footerProductHeading: tNav("footerProductHeading"),
|
|
378
|
-
footerCompanyHeading: tNav("footerCompanyHeading"),
|
|
379
|
-
getStarted: tNav("getStarted"),
|
|
380
|
-
signUp: tNav("signUp"),
|
|
381
|
-
docs: tNav("docs"),
|
|
382
|
-
blog: tNav("blog"),
|
|
383
|
-
// The scheduling module's booking page, /meet (#4633; renamed from the
|
|
384
|
-
// schedule/ route by #5563 — the label key itself keeps its name, D1
|
|
385
|
-
// R2: it names copy, not a route). Present in the map unconditionally,
|
|
386
|
-
// like `docs` and `blog`: the map is a label lookup, and an entry for
|
|
387
|
-
// a surface this site did not scaffold is never reached — `nav-config.ts`
|
|
388
|
-
// only lists surfaces the declared architecture carries.
|
|
389
|
-
schedule: tNav("schedule"),
|
|
390
|
-
// Session-aware action labels (#4984). `account` carries the signed-in
|
|
391
|
-
// user's display name (name, else email) so the Nav shows WHO is signed
|
|
392
|
-
// in; it falls back to the generic label when there is no session.
|
|
393
|
-
signIn: tNav("signIn"),
|
|
394
|
-
signOut: tNav("signOut"),
|
|
395
|
-
account: session?.user
|
|
396
|
-
? (session.user.name ?? session.user.email)
|
|
397
|
-
: tNav("account"),
|
|
398
|
-
},
|
|
380
|
+
items: navItemLabels,
|
|
399
381
|
openMenu: tNav("openMenu"),
|
|
400
382
|
closeMenu: tNav("closeMenu"),
|
|
401
383
|
};
|
|
@@ -453,7 +435,7 @@ export default async function LocaleLayout({
|
|
|
453
435
|
variant: "outline-strong",
|
|
454
436
|
},
|
|
455
437
|
]
|
|
456
|
-
:
|
|
438
|
+
: signedOutHeaderActions(siteHeader, authActions);
|
|
457
439
|
|
|
458
440
|
// The announcement bar is HOME-ONLY (#5469 D5): its route gate matches the
|
|
459
441
|
// pathname literally, and under next-intl's `as-needed` prefixing the home
|
|
@@ -466,25 +448,49 @@ export default async function LocaleLayout({
|
|
|
466
448
|
// same header `generateMetadata` above reads (forwarded by middleware.ts);
|
|
467
449
|
// Next.js's `headers()` is request-scoped and cheap to call again here.
|
|
468
450
|
const pathname = (await headers()).get("x-pathname") ?? homePath;
|
|
469
|
-
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
451
|
+
// #10049 D5 — the home's layout and each page's header and footer resolve
|
|
452
|
+
// page frontmatter → route family → `_site.mdx` → the design's knobs
|
|
453
|
+
// (`lib/page-chrome.ts`). A site that sets none of them resolves to the
|
|
454
|
+
// design's chrome exactly as before: the deck home and `innerPages: "bare"`
|
|
455
|
+
// (#7721 D4 — bare header + one-bar footer on every page but the home) are
|
|
456
|
+
// that resolver's design level. Fail-safe: a missing `x-pathname`
|
|
457
|
+
// resolves to the home path.
|
|
458
|
+
const isHome = pathname === homePath;
|
|
459
|
+
const home = await resolveHomeChrome(locale, chrome);
|
|
460
|
+
const homeLayout = home.layout;
|
|
461
|
+
const isDeckHome = homeLayout === "deck" && isHome;
|
|
462
|
+
const pageChrome = isHome
|
|
463
|
+
? home.chrome
|
|
464
|
+
: resolvePageChrome({
|
|
465
|
+
isHome: false,
|
|
466
|
+
homeIsDeck: homeLayout === "deck",
|
|
467
|
+
family: routeFamilyOf(pathname, readLocales()),
|
|
468
|
+
page: await resolveChromePage(pathname, locale),
|
|
469
|
+
site: home.site,
|
|
470
|
+
chrome,
|
|
471
|
+
// #7797 — an inner page the design lists in `closingFooterPaths`
|
|
472
|
+
// ends on the closing footer unless a site setting says otherwise.
|
|
473
|
+
closingListed: isClosingFooterPath({
|
|
474
|
+
pathname,
|
|
475
|
+
paths: chrome.closingFooterPaths,
|
|
476
|
+
locales: readLocales(),
|
|
477
|
+
}),
|
|
478
|
+
});
|
|
479
|
+
const headerIsFull = pageChrome.header === "full";
|
|
480
|
+
|
|
481
|
+
// Only a full header carries actions (#7718 D1, #10049). Under a bare or no
|
|
482
|
+
// header the auth links move to the footer — the auth pair signed out, an
|
|
483
|
+
// Account link signed in (the route where Sign out lives; its label is the
|
|
484
|
+
// user's name, `navLabels.items.account`).
|
|
485
|
+
const headerActions: NavAction[] = headerIsFull ? navActions : [];
|
|
481
486
|
const deckFooterActions = session
|
|
482
487
|
? [{ labelKey: "account", path: "/account", variant: "outline-strong" as const }]
|
|
483
488
|
: [...authActions];
|
|
484
489
|
// A bare inner page's auth (#7721 D3/D4) rides the one-bar footer's link
|
|
485
490
|
// group: Sign in / Sign up signed out, Account signed in (where Sign out
|
|
486
|
-
// lives). Labels from the existing `nav` namespace.
|
|
487
|
-
|
|
491
|
+
// lives). Labels from the existing `nav` namespace. Under a full header
|
|
492
|
+
// they are in the header instead (#10049).
|
|
493
|
+
const accountLinks = !(pageChrome.footer === "line" && !headerIsFull)
|
|
488
494
|
? []
|
|
489
495
|
: session
|
|
490
496
|
? [{ label: tNav("account"), href: localeHref(locale, "/account") }]
|
|
@@ -528,18 +534,6 @@ export default async function LocaleLayout({
|
|
|
528
534
|
// middleware and hreflang alternates read (NEXT_PUBLIC_ENABLED_LOCALES).
|
|
529
535
|
const enabledLocales = readLocales();
|
|
530
536
|
|
|
531
|
-
// #7797 — an inner page the site lists in `closingFooterPaths` ends on the
|
|
532
|
-
// closing footer: the wordmark, the contact lines and the legal line. The
|
|
533
|
-
// home is never an inner page, and an absent or empty list is false for
|
|
534
|
-
// every page, so a site that lists none renders exactly as before.
|
|
535
|
-
const isClosingInner =
|
|
536
|
-
pathname !== homePath &&
|
|
537
|
-
isClosingFooterPath({
|
|
538
|
-
pathname,
|
|
539
|
-
paths: chrome.closingFooterPaths,
|
|
540
|
-
locales: enabledLocales,
|
|
541
|
-
});
|
|
542
|
-
|
|
543
537
|
// The (auth) route group's chrome branch (#5963).
|
|
544
538
|
//
|
|
545
539
|
// A route group's layout nests INSIDE its parent segment's layout, so
|
|
@@ -611,8 +605,14 @@ export default async function LocaleLayout({
|
|
|
611
605
|
// #7721 D4 (gap F) — `mark-only` means the mark alone: no wordmark beside
|
|
612
606
|
// it on any page. On the deck home `deckMorph` still governs the brand:
|
|
613
607
|
// Nav spreads its `full-logo` composition after `logoDisplay`'s.
|
|
608
|
+
// #10049 D5 — the hybrid deck home (a full header) keeps the full bar,
|
|
609
|
+
// pinned, with no `deckMorph`; a bare header on a non-deck home is the
|
|
610
|
+
// bare bar, unpinned.
|
|
614
611
|
...(siteChrome.nav === "mark-only" ? { logoDisplay: "mark" as const } : {}),
|
|
615
|
-
...(
|
|
612
|
+
...(isHome && pageChrome.header === "bare"
|
|
613
|
+
? { ...(isDeckHome ? { sticky: true, deckMorph: true } : {}), bar: "bare" as const }
|
|
614
|
+
: {}),
|
|
615
|
+
...(isDeckHome && headerIsFull ? { sticky: true } : {}),
|
|
616
616
|
// #8687 D5 — a deck page's header is pinned, as the deck home's is: under
|
|
617
617
|
// snap an unpinned bar scrolls away with slide 1 (#5697). It keeps its
|
|
618
618
|
// own actions, mark and bar style — only `sticky` is added.
|
|
@@ -646,9 +646,12 @@ export default async function LocaleLayout({
|
|
|
646
646
|
] as const;
|
|
647
647
|
// Footer column groups (#6468), generated beside `navItems` as label keys;
|
|
648
648
|
// `Footer` takes translated strings, so resolve them through the same map.
|
|
649
|
-
const labelFor = (key: string): string =>
|
|
650
|
-
|
|
651
|
-
|
|
649
|
+
const labelFor = (key: string): string => navItemLabels[key] ?? navText(key);
|
|
650
|
+
// #10041 D10 — every nav and footer path at `/docs` is rendered at the
|
|
651
|
+
// site's docs path; `nav-config.ts` itself is never edited.
|
|
652
|
+
// #10043 D9 — likewise every path at `/services`, at the services base.
|
|
653
|
+
const publicNavItems = withPublicServicesPaths(withPublicDocsPaths(navItems));
|
|
654
|
+
const footerColumnDefs = withPublicServicesColumns(withPublicDocsColumns(footerColumns)).map((col) => ({
|
|
652
655
|
heading: labelFor(col.headingKey),
|
|
653
656
|
headingKey: col.headingKey,
|
|
654
657
|
items: col.items.map((item) => ({ ...item, label: labelFor(item.labelKey) })),
|
|
@@ -672,7 +675,7 @@ export default async function LocaleLayout({
|
|
|
672
675
|
// reopens the consent banner (BannerConsentIsland).
|
|
673
676
|
{ label: tConsent("settingsLabel"), href: "/privacy#cookie-settings" },
|
|
674
677
|
...(siteChrome.footer === "centered"
|
|
675
|
-
? [{ label:
|
|
678
|
+
? [{ label: tNav("faq"), href: "/faq" }]
|
|
676
679
|
: []),
|
|
677
680
|
];
|
|
678
681
|
|
|
@@ -704,6 +707,13 @@ export default async function LocaleLayout({
|
|
|
704
707
|
dangerouslySetInnerHTML={{ __html: FLASH_PREVENTION_SCRIPT }}
|
|
705
708
|
/>
|
|
706
709
|
)}
|
|
710
|
+
{/* #9889 D5, #9255 D3 — BotID's browser half: one inline script,
|
|
711
|
+
nothing visible, protecting every same-origin POST (a Server
|
|
712
|
+
Action posts to the page it was submitted from). Mounted only
|
|
713
|
+
where the server check runs, so a site with it off is
|
|
714
|
+
byte-identical and one off Vercel never loads a challenge it
|
|
715
|
+
cannot complete. */}
|
|
716
|
+
{botCheckOn("write") ? <BotIdClient protect={BOT_CLIENT_PROTECT} /> : null}
|
|
707
717
|
{/* Inline CSS-variable declarations for the active theme mode */}
|
|
708
718
|
<style
|
|
709
719
|
data-ailk="theme-vars"
|
|
@@ -834,9 +844,17 @@ export default async function LocaleLayout({
|
|
|
834
844
|
// #8687 D5/C3 — the home's brand landing stays home-only;
|
|
835
845
|
// an opted-in page gets the deck's mechanics, never the
|
|
836
846
|
// wordmark-in-the-headline treatment.
|
|
837
|
-
|
|
838
|
-
//
|
|
839
|
-
...(
|
|
847
|
+
// #10049 D5 — a full header keeps the home's own bar, so
|
|
848
|
+
// the bare-bar landing (the brand in the headline) is off.
|
|
849
|
+
...(isDeckHome && !headerIsFull ? { deckHero: true } : {}),
|
|
850
|
+
// #10049 (operator, 2026-10-27) — the pinned full bar
|
|
851
|
+
// never covers a slide's own top spacing.
|
|
852
|
+
...(isDeckHome && headerIsFull ? { navScrollOffset: true } : {}),
|
|
853
|
+
// #7740 D8 — the design's deck presentation dial. A full
|
|
854
|
+
// header keeps one colour across slides (#10049 D5/D7).
|
|
855
|
+
...(chrome.deckPresentation === true
|
|
856
|
+
? { presentation: true, ...(headerIsFull ? { navFollowsGround: false } : {}) }
|
|
857
|
+
: {}),
|
|
840
858
|
// The deck's slide-advance controls (#7732) — named from
|
|
841
859
|
// the `nav` catalog, never the shell's English default.
|
|
842
860
|
slideAdvanceLabels: {
|
|
@@ -851,7 +869,10 @@ export default async function LocaleLayout({
|
|
|
851
869
|
// server Nav (nothing callable crosses the RSC boundary).
|
|
852
870
|
// #7744 D7 — on a deck home a team profile's arrow returns
|
|
853
871
|
// to the people slide (`/#slide-people`) instead.
|
|
854
|
-
|
|
872
|
+
// #10049 D1 — `none` renders no header; a site-set `full`
|
|
873
|
+
// header shows the menus even on a `mark-only` design.
|
|
874
|
+
pageChrome.header === "none" ? undefined : !isHome &&
|
|
875
|
+
pageChrome.header === "bare" ? (
|
|
855
876
|
<Nav
|
|
856
877
|
{...navProps}
|
|
857
878
|
navItems={[]}
|
|
@@ -860,7 +881,7 @@ export default async function LocaleLayout({
|
|
|
860
881
|
href: backLinkHref({
|
|
861
882
|
pathname,
|
|
862
883
|
homePath,
|
|
863
|
-
home:
|
|
884
|
+
home: homeLayout,
|
|
864
885
|
locales: enabledLocales,
|
|
865
886
|
}),
|
|
866
887
|
label: tNav("home"),
|
|
@@ -871,15 +892,21 @@ export default async function LocaleLayout({
|
|
|
871
892
|
) : (
|
|
872
893
|
<Nav
|
|
873
894
|
{...navProps}
|
|
874
|
-
navItems={
|
|
895
|
+
navItems={
|
|
896
|
+
siteChrome.nav === "mark-only" &&
|
|
897
|
+
(pageChrome.header === "bare" || pageChrome.headerFromTemplate)
|
|
898
|
+
? []
|
|
899
|
+
: publicNavItems
|
|
900
|
+
}
|
|
875
901
|
/>
|
|
876
902
|
)
|
|
877
903
|
}
|
|
878
904
|
footer={
|
|
905
|
+
pageChrome.footer === "none" ? undefined : (
|
|
879
906
|
<Footer
|
|
880
907
|
locale={locale}
|
|
881
908
|
itemLabels={navLabels.items}
|
|
882
|
-
navItems={
|
|
909
|
+
navItems={publicNavItems}
|
|
883
910
|
columns={footerColumnDefs}
|
|
884
911
|
siteName={SITE_NAME}
|
|
885
912
|
logo={brandLogo}
|
|
@@ -892,27 +919,32 @@ export default async function LocaleLayout({
|
|
|
892
919
|
// #7721 D4 — a bare inner page's footer is the one bar, its
|
|
893
920
|
// link group carrying the account links the header drops.
|
|
894
921
|
// #7797 — a listed inner page takes the same closing footer.
|
|
895
|
-
// Under
|
|
896
|
-
//
|
|
897
|
-
//
|
|
898
|
-
//
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
922
|
+
// Under a bare header the closing footer carries the auth
|
|
923
|
+
// actions in its place; under a full header the header still
|
|
924
|
+
// holds them.
|
|
925
|
+
// #10049 D5 — the style is `pageChrome.footer`, and every
|
|
926
|
+
// footer with an actions row takes the auth links when the
|
|
927
|
+
// header is not full.
|
|
928
|
+
{...(pageChrome.footer === "closing"
|
|
929
|
+
? {
|
|
930
|
+
layout: "closing" as const,
|
|
931
|
+
contactLines: readSiteContactLines(),
|
|
932
|
+
...(!headerIsFull && !(isHome && isMinimalChrome)
|
|
933
|
+
? { actions: deckFooterActions }
|
|
934
|
+
: {}),
|
|
935
|
+
}
|
|
936
|
+
: pageChrome.footer === "line"
|
|
937
|
+
? { layout: "line" as const, accountLinks }
|
|
938
|
+
: {
|
|
939
|
+
...(pageChrome.footer === "centered"
|
|
940
|
+
? { layout: "centered" as const }
|
|
941
|
+
: {}),
|
|
942
|
+
...(!headerIsFull ? { actions: deckFooterActions } : {}),
|
|
943
|
+
})}
|
|
912
944
|
// #7718 D4 — the deck home's closing slide carries the
|
|
913
|
-
// centred brand
|
|
914
|
-
{...(isDeckHome && !
|
|
915
|
-
? { centeredBrand: true
|
|
945
|
+
// centred brand beside the auth links its bare header drops.
|
|
946
|
+
{...(isDeckHome && !headerIsFull && pageChrome.footer !== "closing"
|
|
947
|
+
? { centeredBrand: true }
|
|
916
948
|
: {})}
|
|
917
949
|
// #7823 — the minimal closing slide's actions are a button,
|
|
918
950
|
// then a text link; Footer reads the style from this.
|
|
@@ -932,6 +964,7 @@ export default async function LocaleLayout({
|
|
|
932
964
|
/>
|
|
933
965
|
}
|
|
934
966
|
/>
|
|
967
|
+
)
|
|
935
968
|
}
|
|
936
969
|
>
|
|
937
970
|
{children}
|