create-ailk 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -10
- package/component-catalog.json +798 -49
- package/component-catalog.md +9 -6
- package/dist/add-module.d.ts +20 -0
- package/dist/add-module.js +43 -7
- package/dist/add-route.d.ts +56 -0
- package/dist/add-route.js +286 -0
- package/dist/apply-design-md.d.ts +7 -0
- package/dist/apply-design-md.js +50 -0
- package/dist/apply-design.d.ts +64 -0
- package/dist/apply-design.js +273 -0
- package/dist/claude-md.d.ts +111 -0
- package/dist/claude-md.js +334 -0
- package/dist/cli.js +114 -28
- package/dist/derive-architecture.js +41 -36
- package/dist/design-compile.d.ts +71 -0
- package/dist/design-compile.js +98 -0
- package/dist/design-package.d.ts +83 -0
- package/dist/design-package.js +468 -0
- package/dist/design-presets.d.ts +86 -0
- package/dist/design-presets.js +642 -0
- package/dist/design-scaffold.d.ts +76 -0
- package/dist/design-scaffold.js +220 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/legacy-nav-renderers.d.ts +45 -0
- package/dist/legacy-nav-renderers.js +545 -0
- package/dist/lib/auth-secret-file.d.ts +19 -0
- package/dist/lib/auth-secret-file.js +112 -0
- package/dist/lib/design-checks.d.ts +78 -0
- package/dist/lib/design-checks.js +174 -0
- package/dist/lib/extract-module-bundle.js +5 -1
- package/dist/lib/license-key-files.d.ts +9 -0
- package/dist/lib/license-key-files.js +2 -2
- package/dist/lib/module-license-gate.d.ts +89 -0
- package/dist/lib/module-license-gate.js +171 -12
- package/dist/lib/module-tier.d.ts +45 -16
- package/dist/lib/module-tier.js +76 -0
- package/dist/lib/pro-callout.js +10 -2
- package/dist/lib/render-verdict.d.ts +52 -0
- package/dist/lib/render-verdict.js +77 -0
- package/dist/lib/scaffold-render-sweep.d.ts +105 -0
- package/dist/lib/scaffold-render-sweep.js +236 -0
- package/dist/module-architecture.d.ts +19 -16
- package/dist/module-architecture.js +64 -21
- package/dist/parse-args.d.ts +88 -10
- package/dist/parse-args.js +272 -21
- package/dist/programmatic.d.ts +66 -12
- package/dist/programmatic.js +361 -34
- package/dist/site-chrome.d.ts +19 -9
- package/dist/site-chrome.js +23 -13
- package/dist/site-manifest.d.ts +25 -0
- package/dist/site-manifest.js +254 -0
- package/dist/site-roster.d.ts +41 -0
- package/dist/site-roster.js +149 -11
- package/dist/starter-grid.d.ts +232 -0
- package/dist/starter-grid.js +545 -0
- package/dist/surfaces.d.ts +41 -9
- package/dist/surfaces.js +268 -92
- package/dist/template-manifest.d.ts +65 -29
- package/dist/template-manifest.js +161 -382
- package/dist/template-names.d.ts +14 -12
- package/dist/template-names.js +13 -17
- package/package.json +5 -4
- package/site-templates/_base/content/en/Feature/feature-1.mdx +69 -0
- package/site-templates/_base/content/en/Feature/feature-2.mdx +69 -0
- package/site-templates/_base/content/en/Feature/feature-3.mdx +69 -0
- package/site-templates/_base/content/en/FeaturesList/features.mdx +91 -0
- package/site-templates/_base/content/en/GlossaryList/glossary.mdx +0 -1
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +0 -1
- package/site-templates/_base/content/en/PortfolioItem/sample-four.mdx +105 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-one.mdx +34 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-three.mdx +94 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-two.mdx +45 -0
- package/site-templates/_base/content/en/PortfolioList/portfolio.mdx +169 -0
- package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +15 -0
- package/site-templates/_base/content/en/UseCase/persona-1.mdx +70 -0
- package/site-templates/_base/content/en/UseCase/persona-2.mdx +70 -0
- package/site-templates/_base/content/en/UseCase/persona-3.mdx +70 -0
- package/site-templates/{minimalism-site/TEMPLATE-MANIFEST.json → aliases/minimalism-site.TEMPLATE-MANIFEST.json} +72 -40
- package/site-templates/designs/minimalism/DESIGN.md +98 -6
- package/site-templates/designs/minimalism/sample/README.md +48 -0
- package/site-templates/designs/standard/DESIGN.md +103 -6
- package/site-templates/marketing-site/DESIGN.md +103 -6
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +144 -47
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +82 -41
- package/site-templates/marketing-site-free/DESIGN.md +103 -6
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +150 -48
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +114 -67
- package/site-templates/marketing-site-free/ailk-template.json +1 -1
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +199 -0
- package/site-templates/saas-product/DESIGN.md +103 -6
- package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
- package/site-templates/{simple-service → shapes/local-business-service}/TEMPLATE-MANIFEST.json +210 -81
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +356 -0
- package/site-templates/shapes/local-business-service/ailk-template.json +10 -0
- package/site-templates/{simple-service → shapes/local-business-service}/content/en/AboutPage/about.mdx +4 -4
- package/site-templates/shapes/local-business-service/content/en/HomePage/home.mdx +70 -0
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/local-business-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/{minimalism-site → shapes/local-business-service}/content/en/PurchasePolicyPage/purchase-policy.mdx +7 -8
- package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/ServicesList/services.mdx +61 -0
- package/site-templates/shapes/local-business-service/provenance.json +4 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +471 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +354 -0
- package/site-templates/shapes/organization-service/ailk-template.json +10 -0
- package/site-templates/shapes/organization-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/organization-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/shapes/organization-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/{minimalism-site → shapes/organization-service}/content/en/ServicesList/services.mdx +3 -4
- package/site-templates/shapes/organization-service/provenance.json +4 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +478 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +353 -0
- package/site-templates/shapes/person-service/ailk-template.json +10 -0
- package/site-templates/shapes/person-service/content/_site.mdx +85 -0
- package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +39 -0
- package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +97 -0
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/{simple-service → shapes/person-service}/content/en/PurchasePolicyPage/purchase-policy.mdx +10 -11
- package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/{simple-service → shapes/person-service}/content/en/ServicesList/services.mdx +16 -16
- package/site-templates/shapes/person-service/provenance.json +4 -0
- package/templates/.claude/rules/architecture.md +7 -0
- package/templates/.claude/skills/README.md +5 -3
- package/templates/.claude/skills/add-auth/references/env-vars.md +22 -15
- package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +3 -2
- package/templates/.claude/skills/add-page/SKILL.md +241 -238
- package/templates/.claude/skills/add-page/references/page-types.md +85 -72
- package/templates/.claude/skills/add-page/scripts/site-pages.mjs +87 -0
- package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +99 -0
- package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +8 -18
- package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +43 -0
- package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +52 -0
- package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +38 -0
- package/templates/.claude/skills/check-site/SKILL.md +23 -0
- package/templates/.claude/skills/fill-page/SKILL.md +26 -7
- package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +1 -1
- package/templates/.claude/skills/setup-database/SKILL.md +156 -0
- package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
- package/templates/.claude/skills/setup-deploy/SKILL.md +64 -32
- package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +44 -36
- package/templates/.claude/skills/setup-deploy/references/runbook-template.md +58 -102
- package/templates/.claude/skills/update-page/SKILL.md +117 -0
- package/templates/.claude/skills/update-page/scripts/update-page.ts +640 -0
- package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +31 -0
- package/templates/.claude/skills/upgrade-plan/SKILL.md +12 -8
- package/templates/.github/workflows/ci.yml +4 -0
- package/templates/.prettierignore +1 -0
- package/templates/CLAUDE.md +146 -103
- package/templates/DESIGN.md +103 -6
- package/templates/README.md +63 -43
- package/templates/apps/api/.env.example +2 -1
- package/templates/apps/api/CLAUDE.md +3 -2
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +2 -0
- package/templates/apps/api/src/__tests__/server.test.ts +116 -0
- package/templates/apps/api/src/config/__tests__/index.test.ts +115 -14
- package/templates/apps/api/src/config/index.ts +35 -16
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +30 -0
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +96 -0
- package/templates/apps/api/src/lib/lead-capture-store.ts +12 -0
- package/templates/apps/api/src/lib/lead-delivery.ts +52 -0
- package/templates/apps/api/src/lib/stripe.ts +9 -0
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +140 -0
- package/templates/apps/api/src/middleware/auth.ts +54 -0
- package/templates/apps/api/src/openapi/spec.ts +63 -12
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +5 -2
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +628 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +222 -24
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +88 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +100 -0
- package/templates/apps/api/src/routes/leads/index.ts +46 -1
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -1
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +5 -1
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +521 -20
- package/templates/apps/api/src/routes/webhooks/stripe.ts +124 -32
- package/templates/apps/api/src/server.ts +35 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +194 -0
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +124 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +90 -15
- package/templates/apps/api/src/services/checkout-buyer.ts +72 -0
- package/templates/apps/api/src/services/lead-routing.ts +140 -30
- package/templates/apps/api/src/services/leads-inbox.ts +48 -11
- package/templates/apps/api/src/services/revalidation.ts +60 -15
- package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +30 -24
- package/templates/apps/mcp/src/tools/create_project.ts +21 -6
- package/templates/apps/mcp/src/tools/purchase.ts +4 -2
- package/templates/apps/web/.env.example +13 -0
- package/templates/apps/web/CLAUDE.md +22 -28
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +1 -1
- package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +24 -0
- package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +17 -6
- package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +110 -0
- package/templates/apps/web/app/[locale]/[slug]/[detail]/page.tsx +60 -0
- package/templates/apps/web/app/[locale]/[slug]/page.tsx +1 -1
- package/templates/apps/web/app/[locale]/alternatives/__tests__/page.test.tsx +161 -0
- package/templates/apps/web/app/[locale]/alternatives/page.tsx +104 -0
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +14 -6
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +10 -9
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +32 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +129 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +227 -84
- package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +50 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +73 -20
- package/templates/apps/web/app/[locale]/contact/page.tsx +105 -15
- package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
- package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +64 -0
- package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
- package/templates/apps/web/app/[locale]/{use-cases → for}/[slug]/page.tsx +21 -5
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +73 -0
- package/templates/apps/web/app/[locale]/{use-cases → for}/page.tsx +8 -7
- package/templates/apps/web/app/[locale]/glossary/page.tsx +1 -1
- package/templates/apps/web/app/[locale]/layout.tsx +65 -17
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +75 -0
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +163 -0
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +66 -0
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +106 -14
- package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
- package/templates/apps/web/app/[locale]/{book-a-call → portfolio}/page.tsx +12 -13
- package/templates/apps/web/app/[locale]/vs/[slug]/__tests__/page.test.tsx +112 -0
- package/templates/apps/web/app/[locale]/vs/[slug]/page.tsx +70 -0
- package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +236 -44
- package/templates/apps/web/app/api/revalidate/route.ts +78 -36
- package/templates/apps/web/lib/__tests__/site-brand.test.ts +79 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +104 -11
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +21 -4
- package/templates/apps/web/lib/locale-guard.ts +18 -0
- package/templates/apps/web/lib/meet-placeholder.ts +5 -0
- package/templates/apps/web/lib/nav-config.ts +50 -37
- package/templates/apps/web/lib/site-brand.tsx +13 -9
- package/templates/apps/web/lib/site-identity.ts +14 -0
- package/templates/apps/web/lib/site-logo.tsx +36 -0
- package/templates/apps/web/lib/site-theme.ts +46 -26
- package/templates/apps/web/messages/en.json +45 -5
- package/templates/apps/web/middleware.ts +28 -4
- package/templates/content/_site.mdx +4 -2
- package/templates/content/en/BlogList/blog.mdx +37 -16
- package/templates/content/en/BookCall/book-a-call.mdx +80 -0
- package/templates/content/en/BookCall/onsite-estimate.mdx +56 -0
- package/templates/content/en/BookCall/onsite-service.mdx +56 -0
- package/templates/content/en/BookCall/start-project.mdx +56 -0
- package/templates/content/en/ContactPage/contact.mdx +43 -34
- package/templates/content/en/HomePage/home.mdx +10 -22
- package/templates/content/en/LandingPage/meet.mdx +59 -0
- package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
- package/templates/content/en/UseCasesList/{use-cases.mdx → for.mdx} +19 -18
- package/templates/database/CHANGELOG.md +14 -0
- package/templates/database/package.json +1 -1
- package/templates/package.json +2 -1
- package/templates/tools/check-claude-md.ts +128 -0
- package/versions.json +33 -33
- package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -53
- package/site-templates/_base/content/en/UseCase/first-use-case.mdx +0 -23
- package/site-templates/_base/content/en/UseCase/second-use-case.mdx +0 -23
- package/site-templates/_base/content/en/UseCase/third-use-case.mdx +0 -23
- package/site-templates/minimalism-site/DESIGN.md +0 -162
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +0 -470
- package/site-templates/minimalism-site/ailk-template.json +0 -23
- package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +0 -110
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -53
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +0 -34
- package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +0 -57
- package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +0 -77
- package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +0 -73
- package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +0 -52
- package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +0 -52
- package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +0 -52
- package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +0 -52
- package/site-templates/minimalism-site/flows/referral.flow.json +0 -71
- package/site-templates/minimalism-site/provenance.json +0 -4
- package/site-templates/simple-service/DESIGN.md +0 -162
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -333
- package/site-templates/simple-service/ailk-template.json +0 -11
- package/site-templates/simple-service/content/en/BookCall/book-call.mdx +0 -53
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -32
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +0 -56
- package/site-templates/simple-service/content/en/HomePage/home.mdx +0 -85
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +0 -40
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +0 -40
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +0 -40
- package/site-templates/simple-service/provenance.json +0 -4
- package/templates/.claude/skills/add-page/templates/route.tsx.tmpl +0 -28
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -211
- package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +0 -72
- package/templates/content/en/SoftwareProduct/product.mdx +0 -175
- /package/site-templates/{minimalism-site → designs/minimalism/sample}/content/en/HomePage/home.mdx +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-1.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-2.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-3.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-4.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-5.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-6.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/hero-backdrop.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-1.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-2.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-3.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/wordmark-placeholder.svg +0 -0
- /package/site-templates/{minimalism-site → shapes/organization-service}/content/en/Service/first-engagement.mdx +0 -0
- /package/site-templates/{minimalism-site → shapes/organization-service}/content/en/Service/second-engagement.mdx +0 -0
- /package/site-templates/{minimalism-site → shapes/organization-service}/content/en/Service/third-engagement.mdx +0 -0
package/dist/surfaces.js
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* `MODULE_SURFACES`, and `__tests__/surface-gating.test.ts` asserts those keys
|
|
32
32
|
* equal `RECOGNIZED_MODULES` so the two cannot drift.
|
|
33
33
|
*/
|
|
34
|
+
import { createHash } from "node:crypto";
|
|
34
35
|
/** Template-relative prefix every `apps/web` route directory sits under. */
|
|
35
36
|
const ROUTE_ROOT = "apps/web/app/[locale]";
|
|
36
37
|
/**
|
|
@@ -81,13 +82,6 @@ export const CORE_ROUTE_DIRS = [
|
|
|
81
82
|
// scaffold carrying it and no content is byte-for-byte a scaffold with one
|
|
82
83
|
// more 404, nothing else.
|
|
83
84
|
`${ROUTE_ROOT}/[slug]`,
|
|
84
|
-
// #6658 D3 — the product's own detail page (`/software/product`, a
|
|
85
|
-
// SoftwareProduct → SoftwareApplication), which replaced the /features
|
|
86
|
-
// stand-in. Core for the same reason [slug] is: its content
|
|
87
|
-
// (`content/en/SoftwareProduct/product.mdx`) is in the always-copied overlay
|
|
88
|
-
// and the generated nav always links it, so every scaffold — template or
|
|
89
|
-
// not — must carry the route.
|
|
90
|
-
`${ROUTE_ROOT}/software`,
|
|
91
85
|
// #6084 D12 (the operator's shape-2 disposition) — the checkout return
|
|
92
86
|
// pair. The launchers pointing at them (`apps/mcp/src/tools/purchase.ts`,
|
|
93
87
|
// `apps/web/.../dev/purchase/purchase.actions.ts`) are ALREADY
|
|
@@ -121,19 +115,22 @@ export const NON_ROUTE_DIRS = new Set([
|
|
|
121
115
|
* byte-deterministic).
|
|
122
116
|
*/
|
|
123
117
|
export const GATEABLE_SURFACES = [
|
|
118
|
+
{
|
|
119
|
+
// #6878 D9 — the comparison hub/spoke pair (`ComparisonsList` at
|
|
120
|
+
// /alternatives, `ComparisonPage` at /vs/<slug>). No `navLabelKey`: no
|
|
121
|
+
// roster names it by default, so it excludes at scaffold time exactly
|
|
122
|
+
// like any other gateable surface a roster leaves off. `add-page`
|
|
123
|
+
// installs it (D11) the first time a site adds a comparison.
|
|
124
|
+
id: "alternatives",
|
|
125
|
+
routes: [`${ROUTE_ROOT}/alternatives`, `${ROUTE_ROOT}/vs`],
|
|
126
|
+
content: ["content/<locale>/ComparisonsList", "content/<locale>/ComparisonPage"],
|
|
127
|
+
},
|
|
124
128
|
{
|
|
125
129
|
id: "blog",
|
|
126
130
|
routes: [`${ROUTE_ROOT}/blog`],
|
|
127
131
|
content: ["apps/web/content/<locale>/blog"],
|
|
128
132
|
navLabelKey: "blog",
|
|
129
133
|
},
|
|
130
|
-
{
|
|
131
|
-
// Module-less (#6658 D5), like `pricing`: every starter's roster names
|
|
132
|
-
// it. No `navLabelKey` — the footer links it, gated on this surface.
|
|
133
|
-
id: "book-a-call",
|
|
134
|
-
routes: [`${ROUTE_ROOT}/book-a-call`],
|
|
135
|
-
content: ["content/<locale>/BookCall"],
|
|
136
|
-
},
|
|
137
134
|
{
|
|
138
135
|
id: "careers",
|
|
139
136
|
routes: [`${ROUTE_ROOT}/careers`],
|
|
@@ -180,12 +177,27 @@ export const GATEABLE_SURFACES = [
|
|
|
180
177
|
routes: [`${ROUTE_ROOT}/events`],
|
|
181
178
|
content: ["content/<locale>/EventsList", "content/<locale>/Event"],
|
|
182
179
|
},
|
|
180
|
+
{
|
|
181
|
+
// #6721 — the product's feature pages: a FeaturesList hub over one
|
|
182
|
+
// Feature page per feature. The Free roster enables it by its rows.
|
|
183
|
+
id: "features",
|
|
184
|
+
routes: [`${ROUTE_ROOT}/features`],
|
|
185
|
+
content: ["content/<locale>/FeaturesList", "content/<locale>/Feature"],
|
|
186
|
+
},
|
|
183
187
|
{
|
|
184
188
|
// API-backed (the flow engine); no MDX of its own.
|
|
185
189
|
id: "flows",
|
|
186
190
|
routes: [`${ROUTE_ROOT}/flows`],
|
|
187
191
|
content: [],
|
|
188
192
|
},
|
|
193
|
+
{
|
|
194
|
+
// #6878 D3 — the persona hub, renamed from `use-cases` (route-naming
|
|
195
|
+
// convention #3450: /for/{persona} spokes under a /for hub). Every shape
|
|
196
|
+
// roster carries it now (D7), service shapes included.
|
|
197
|
+
id: "for",
|
|
198
|
+
routes: [`${ROUTE_ROOT}/for`],
|
|
199
|
+
content: ["content/<locale>/UseCasesList", "content/<locale>/UseCase"],
|
|
200
|
+
},
|
|
189
201
|
{
|
|
190
202
|
// Module-less (#6658 D5) — see `book-a-call`.
|
|
191
203
|
id: "glossary",
|
|
@@ -240,6 +252,11 @@ export const GATEABLE_SURFACES = [
|
|
|
240
252
|
routes: [`${ROUTE_ROOT}/partners`],
|
|
241
253
|
content: ["content/<locale>/PartnersList", "content/<locale>/Partner"],
|
|
242
254
|
},
|
|
255
|
+
{
|
|
256
|
+
id: "portfolio",
|
|
257
|
+
routes: [`${ROUTE_ROOT}/portfolio`],
|
|
258
|
+
content: ["content/<locale>/PortfolioList", "content/<locale>/PortfolioItem"],
|
|
259
|
+
},
|
|
243
260
|
{
|
|
244
261
|
// Module-LESS by construction (#5479 D16): no MODULE_SURFACES value names
|
|
245
262
|
// it, so a site template may enable it by roster alone. Same for
|
|
@@ -276,6 +293,13 @@ export const GATEABLE_SURFACES = [
|
|
|
276
293
|
routes: [`${ROUTE_ROOT}/services`],
|
|
277
294
|
content: ["content/<locale>/ServicesList", "content/<locale>/Service"],
|
|
278
295
|
},
|
|
296
|
+
{
|
|
297
|
+
// #6721 — the product's own page (#6658 D3), a gateable surface again now
|
|
298
|
+
// that the Free roster links /features instead of /software/product.
|
|
299
|
+
id: "software",
|
|
300
|
+
routes: [`${ROUTE_ROOT}/software`],
|
|
301
|
+
content: ["content/<locale>/SoftwareProduct"],
|
|
302
|
+
},
|
|
279
303
|
{
|
|
280
304
|
id: "tech-articles",
|
|
281
305
|
routes: [`${ROUTE_ROOT}/tech-articles`],
|
|
@@ -291,11 +315,6 @@ export const GATEABLE_SURFACES = [
|
|
|
291
315
|
routes: [`${ROUTE_ROOT}/testimonials`],
|
|
292
316
|
content: ["content/<locale>/TestimonialsList"],
|
|
293
317
|
},
|
|
294
|
-
{
|
|
295
|
-
id: "use-cases",
|
|
296
|
-
routes: [`${ROUTE_ROOT}/use-cases`],
|
|
297
|
-
content: ["content/<locale>/UseCasesList", "content/<locale>/UseCase"],
|
|
298
|
-
},
|
|
299
318
|
{
|
|
300
319
|
id: "whitepapers",
|
|
301
320
|
routes: [`${ROUTE_ROOT}/whitepapers`],
|
|
@@ -311,6 +330,9 @@ export const RECOGNIZED_SURFACES = GATEABLE_SURFACES.map((s) => s.id);
|
|
|
311
330
|
*/
|
|
312
331
|
export const SURFACE_ALIASES = {
|
|
313
332
|
schedule: "meet",
|
|
333
|
+
// #6878 D3 — one-release alias for the /use-cases -> /for rename, same
|
|
334
|
+
// removal trigger as `schedule` above.
|
|
335
|
+
"use-cases": "for",
|
|
314
336
|
};
|
|
315
337
|
/**
|
|
316
338
|
* Surface ids that became core routes (#6658 D3: `software` — the product's
|
|
@@ -319,7 +341,10 @@ export const SURFACE_ALIASES = {
|
|
|
319
341
|
* so a site scaffolded with `--surfaces software` keeps working: its route
|
|
320
342
|
* is copied regardless.
|
|
321
343
|
*/
|
|
322
|
-
|
|
344
|
+
// #6721 — `software` is a gateable surface again (the Free roster now carries
|
|
345
|
+
// /features + /features/[slug] instead); the set is kept, empty, so the
|
|
346
|
+
// accept-and-drop read sites stay in place for the next promotion.
|
|
347
|
+
export const CORE_PROMOTED_SURFACES = new Set([]);
|
|
323
348
|
/** Canonical id for a `--surfaces` value, resolving a retired alias. */
|
|
324
349
|
function canonicalizeSurface(s) {
|
|
325
350
|
return SURFACE_ALIASES[s] ?? s;
|
|
@@ -492,10 +517,6 @@ export function excludedContentPaths(enabled) {
|
|
|
492
517
|
export const ROUTE_SINCE = {
|
|
493
518
|
about: "0.0.0",
|
|
494
519
|
"blog/[[...slug]]": "0.0.0",
|
|
495
|
-
// #6658 — four genuinely new route directories (D4, D5), attaching at
|
|
496
|
-
// today's fixed-group version, the same "attach at today's version" rule
|
|
497
|
-
// #6084, #5977, #5678, #5479, #5371 and #5048 applied.
|
|
498
|
-
"book-a-call": "0.26.0",
|
|
499
520
|
careers: "0.0.0",
|
|
500
521
|
"careers/[slug]": "0.0.0",
|
|
501
522
|
"case-studies": "0.0.0",
|
|
@@ -509,10 +530,17 @@ export const ROUTE_SINCE = {
|
|
|
509
530
|
downloads: "0.0.0",
|
|
510
531
|
events: "0.0.0",
|
|
511
532
|
"events/[slug]": "0.0.0",
|
|
533
|
+
// #6721 — the feature pages, a new route pair at today's line.
|
|
534
|
+
features: "0.27.0",
|
|
535
|
+
"features/[slug]": "0.27.0",
|
|
512
536
|
faq: "0.0.0",
|
|
513
537
|
"flows/[slug]": "0.0.0",
|
|
514
538
|
"flows/[slug]/results/[payload]": "0.14.0",
|
|
515
539
|
"flows/[slug]/start": "0.18.0",
|
|
540
|
+
// #6658 — genuinely new route directories (D4, D5), attaching at today's
|
|
541
|
+
// fixed-group version, the same "attach at today's version" rule #6084,
|
|
542
|
+
// #5977, #5678, #5479, #5371 and #5048 applied. `book-a-call` was a third
|
|
543
|
+
// such entry, retired by #6897 D10.
|
|
516
544
|
glossary: "0.26.0",
|
|
517
545
|
"how-to": "0.0.0",
|
|
518
546
|
"how-to/[slug]": "0.0.0",
|
|
@@ -531,6 +559,9 @@ export const ROUTE_SINCE = {
|
|
|
531
559
|
// #5479 — three genuinely new route directories, so they attach at today's
|
|
532
560
|
// fixed-group version rather than the "predates the manifest" 0.0.0 above,
|
|
533
561
|
// the same rule #5048 ("projects/apply") and #5014 ("(authed)") applied.
|
|
562
|
+
// #6687 — the before/after portfolio, a new route pair at today's line.
|
|
563
|
+
portfolio: "0.27.0",
|
|
564
|
+
"portfolio/[slug]": "0.27.0",
|
|
534
565
|
pricing: "0.22.0",
|
|
535
566
|
privacy: "0.22.0",
|
|
536
567
|
products: "0.0.0",
|
|
@@ -558,8 +589,14 @@ export const ROUTE_SINCE = {
|
|
|
558
589
|
terms: "0.22.0",
|
|
559
590
|
testimonials: "0.0.0",
|
|
560
591
|
"thanks/[slug]": "0.14.0",
|
|
561
|
-
|
|
562
|
-
|
|
592
|
+
// #6878 — the persona family, renamed from `use-cases`/`use-cases/[slug]`.
|
|
593
|
+
// Attaches at today's fixed-group version, the same "attach at today's
|
|
594
|
+
// version" rule #6084, #5977, #5678, #5479, #5371 and #5048 applied.
|
|
595
|
+
for: "0.28.0",
|
|
596
|
+
"for/[slug]": "0.28.0",
|
|
597
|
+
// #6878 D9 — the comparison hub/spoke pair.
|
|
598
|
+
alternatives: "0.28.0",
|
|
599
|
+
"vs/[slug]": "0.28.0",
|
|
563
600
|
whitepapers: "0.0.0",
|
|
564
601
|
"whitepapers/[slug]": "0.0.0",
|
|
565
602
|
// #5014 — the (authed) route GROUP: layout.tsx + sign-in-gate.tsx sit
|
|
@@ -624,6 +661,10 @@ export const ROUTE_SINCE = {
|
|
|
624
661
|
// directory, so it attaches at today's fixed-group version, the same rule
|
|
625
662
|
// #5479, #5371 and #5048 applied.
|
|
626
663
|
"[slug]": "0.23.0",
|
|
664
|
+
// #6897 D2 — the Free /meet/<slug> placeholder detail route. A genuinely
|
|
665
|
+
// new route directory, so it attaches at today's fixed-group version, the
|
|
666
|
+
// same rule applied above.
|
|
667
|
+
"[slug]/[detail]": "0.28.0",
|
|
627
668
|
};
|
|
628
669
|
/**
|
|
629
670
|
* Surface id of the registry that owns `routeDir` (ROUTE_ROOT-relative): the
|
|
@@ -659,12 +700,18 @@ export function surfaceForRouteDir(routeDir) {
|
|
|
659
700
|
*/
|
|
660
701
|
export const ROUTE_DEPENDENCIES = {
|
|
661
702
|
// #5963 — the (auth) group's layout renders the brand mark through
|
|
662
|
-
// `siteLogo()
|
|
663
|
-
//
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
//
|
|
667
|
-
|
|
703
|
+
// `siteLogo()` — which lives in `site-logo.tsx` since #6820 and reads the
|
|
704
|
+
// raw mark + name from `site-brand.tsx`, so BOTH files travel with the
|
|
705
|
+
// group — and the LOCALE layout above it branches on `isAuthPath()` to drop
|
|
706
|
+
// the marketing chrome for these five paths: without `auth-routes.ts` a
|
|
707
|
+
// synced group renders inside the Nav + Footer it exists to replace. The
|
|
708
|
+
// sign-in page no longer imports `site-brand` — the mark is the shell's
|
|
709
|
+
// since #5963 — so its own list is just the env reader.
|
|
710
|
+
"(auth)": [
|
|
711
|
+
"apps/web/lib/site-brand.tsx",
|
|
712
|
+
"apps/web/lib/site-logo.tsx",
|
|
713
|
+
"apps/web/lib/auth-routes.ts",
|
|
714
|
+
],
|
|
668
715
|
"(auth)/sign-in": ["apps/web/lib/auth-env.ts"],
|
|
669
716
|
"(auth)/sign-up": ["apps/web/lib/auth-env.ts"],
|
|
670
717
|
"(auth)/forgot-password": ["apps/web/lib/auth-env.ts"],
|
|
@@ -739,6 +786,46 @@ export const ROUTE_MESSAGE_NAMESPACES = {
|
|
|
739
786
|
};
|
|
740
787
|
/** Template-relative path of the generated site-nav registry. */
|
|
741
788
|
export const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
|
|
789
|
+
/**
|
|
790
|
+
* `renderNavConfig`'s first-line stamp (#6908 D1): `// ailk:generated
|
|
791
|
+
* sha256=<64 lowercase hex>`, recognised whether it ends in `\n` or `\r\n`.
|
|
792
|
+
* `<hex>` is the SHA-256 of every byte after the stamp line's terminator,
|
|
793
|
+
* CRLF folded to LF first — the same fold-and-hash rule `claude-md.ts`
|
|
794
|
+
* `hashBody` uses for the scaffold's `CLAUDE.md` regions (#6904), given its
|
|
795
|
+
* own definition here (N1) rather than an import, since the two files hash
|
|
796
|
+
* unrelated bodies.
|
|
797
|
+
*/
|
|
798
|
+
const NAV_STAMP_RE = /^\/\/ ailk:generated sha256=([0-9a-f]{64})\r?\n/;
|
|
799
|
+
/** SHA-256, lowercase hex, of a nav body with every CRLF folded to LF (D1, N1). */
|
|
800
|
+
function hashNavBody(body) {
|
|
801
|
+
return createHash("sha256")
|
|
802
|
+
.update(body.replace(/\r\n/g, "\n"), "utf8")
|
|
803
|
+
.digest("hex");
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Verify a rendered nav's self-hashing stamp (#6908 D1/F1–F2): `"valid"` when
|
|
807
|
+
* the first line's hash matches every byte after it, `"invalid"` when a
|
|
808
|
+
* stamp line is present but the hash fails (an edited, still-stamped nav),
|
|
809
|
+
* `"absent"` when there is no stamp line at all. A stamp that verifies proves
|
|
810
|
+
* the file unedited whatever generator wrote it — the check needs only the
|
|
811
|
+
* file, never the generator's code.
|
|
812
|
+
*/
|
|
813
|
+
export function readNavConfigStamp(text) {
|
|
814
|
+
const match = NAV_STAMP_RE.exec(text);
|
|
815
|
+
if (match === null)
|
|
816
|
+
return "absent";
|
|
817
|
+
const body = text.slice(match[0].length);
|
|
818
|
+
return match[1] === hashNavBody(body) ? "valid" : "invalid";
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Every byte after a nav's stamp line, or `text` unchanged when it carries
|
|
822
|
+
* none. The one place the D1 stamp-line shape is matched outside
|
|
823
|
+
* `readNavConfigStamp` itself, so a test simulating an edit below the stamp
|
|
824
|
+
* (or a bare pre-D1 body) never re-derives `NAV_STAMP_RE`'s pattern by hand.
|
|
825
|
+
*/
|
|
826
|
+
export function stripNavConfigStamp(text) {
|
|
827
|
+
return text.replace(NAV_STAMP_RE, "");
|
|
828
|
+
}
|
|
742
829
|
/**
|
|
743
830
|
* Render `apps/web/lib/nav-config.ts` for an enabled surface set.
|
|
744
831
|
*
|
|
@@ -749,14 +836,19 @@ export const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
|
|
|
749
836
|
* surface set for the same reason the route tree does.
|
|
750
837
|
*
|
|
751
838
|
* The base items are the Free marketing-site chrome (#5469 D5, the approved
|
|
752
|
-
* chrome diff recorded on that card, re-pointed by #6658 D8):
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
* the docs-blog module serves it).
|
|
757
|
-
*
|
|
758
|
-
* roster enables it, #5469 D10), so the nav still never links a page
|
|
759
|
-
* scaffold does not carry.
|
|
839
|
+
* chrome diff recorded on that card, re-pointed by #6658 D8, then by #6878 D6):
|
|
840
|
+
* ONE flyout rule for every shape — an offering column (Features on product
|
|
841
|
+
* shapes, Services on service shapes) beside a Customers column linking the
|
|
842
|
+
* /for persona pages — then Blog, Pricing and About. `/blog` is always linked
|
|
843
|
+
* (the teaser or the docs-blog module serves it). Pricing and every other
|
|
844
|
+
* gateable surface: each item is emitted only when its surface is enabled (a
|
|
845
|
+
* template roster enables it, #5469 D10), so the nav still never links a page
|
|
846
|
+
* the scaffold does not carry. Portfolio is never a flyout link (D6): on a
|
|
847
|
+
* service shape it is a plain top-level link directly after the Services
|
|
848
|
+
* flyout, and every shape's footer Site pages column links it (#6895), both
|
|
849
|
+
* under the `portfolio` surface's gate. No empty flyout is ever emitted: with
|
|
850
|
+
* neither column non-empty, no flyout item is emitted at all (closes #6766's
|
|
851
|
+
* AC-1).
|
|
760
852
|
*
|
|
761
853
|
* Only gateable surfaces carrying a `navLabelKey` are linked after the base
|
|
762
854
|
* items, because a nav entry needs a translated label. Emitting an entry whose
|
|
@@ -770,78 +862,155 @@ export const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
|
|
|
770
862
|
* Deterministic: gateable entries follow GATEABLE_SURFACES order (alphabetical
|
|
771
863
|
* by id), after the fixed base items.
|
|
772
864
|
*/
|
|
773
|
-
export function renderNavConfig(enabled) {
|
|
865
|
+
export function renderNavConfig(enabled, options) {
|
|
774
866
|
const on = new Set(enabled);
|
|
775
867
|
// `blog` is linked unconditionally below (operator ruling 2026-09-23), so
|
|
776
|
-
// it is excluded here; `
|
|
777
|
-
//
|
|
778
|
-
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
const
|
|
868
|
+
// it is excluded here; `for` is the Customers flyout/footer column below,
|
|
869
|
+
// linked in its own fixed positions rather than appended.
|
|
870
|
+
const linked = GATEABLE_SURFACES.filter((s) => s.navLabelKey !== undefined && on.has(s.id) && s.id !== "blog");
|
|
871
|
+
const features = on.has("features");
|
|
872
|
+
const services = on.has("services");
|
|
873
|
+
const forEnabled = on.has("for");
|
|
874
|
+
const portfolio = on.has("portfolio");
|
|
875
|
+
const serviceSlugs = [...(options?.serviceSlugs ?? [])].sort().slice(0, 3);
|
|
876
|
+
// #6878 D6 (operator ruling 2026-09-27): one flyout rule for every shape.
|
|
877
|
+
// The `groups` render the desktop panel; `features` carries the same links
|
|
878
|
+
// for the mobile disclosure and the active-path match.
|
|
879
|
+
const featureLinks = features
|
|
880
|
+
? [
|
|
881
|
+
' { labelKey: "feature1", path: "/features/feature-1" },',
|
|
882
|
+
' { labelKey: "feature2", path: "/features/feature-2" },',
|
|
883
|
+
' { labelKey: "feature3", path: "/features/feature-3" },',
|
|
884
|
+
]
|
|
885
|
+
: [];
|
|
886
|
+
const serviceLinks = !features && services
|
|
887
|
+
? serviceSlugs.map((slug, i) => ` { labelKey: "service${i + 1}", path: "/services/${slug}" },`)
|
|
888
|
+
: [];
|
|
889
|
+
const customerLinks = forEnabled
|
|
890
|
+
? [
|
|
891
|
+
' { labelKey: "customer1", path: "/for/persona-1" },',
|
|
892
|
+
' { labelKey: "customer2", path: "/for/persona-2" },',
|
|
893
|
+
' { labelKey: "customer3", path: "/for/persona-3" },',
|
|
894
|
+
]
|
|
895
|
+
: [];
|
|
896
|
+
const indent = (line) => ` ${line}`;
|
|
897
|
+
const group = (headingKey, links) => links.length === 0
|
|
898
|
+
? []
|
|
899
|
+
: [
|
|
900
|
+
" {",
|
|
901
|
+
` headingKey: "${headingKey}",`,
|
|
902
|
+
" items: [",
|
|
903
|
+
...links.map(indent),
|
|
904
|
+
" ],",
|
|
905
|
+
" },",
|
|
906
|
+
];
|
|
907
|
+
// Offering column: Features on a product shape, Services on a service
|
|
908
|
+
// shape. `features` wins when both surfaces are somehow on at once — the
|
|
909
|
+
// Free roster never enables both.
|
|
910
|
+
const offeringLinks = features ? featureLinks : serviceLinks;
|
|
911
|
+
const offeringLabelKey = features ? "product" : "services";
|
|
912
|
+
const offeringHeadingKey = features
|
|
913
|
+
? "flyoutFeaturesHeading"
|
|
914
|
+
: "flyoutServicesHeading";
|
|
915
|
+
const flyoutGroups = [
|
|
916
|
+
...group(offeringHeadingKey, offeringLinks),
|
|
917
|
+
...group("flyoutCustomersHeading", customerLinks),
|
|
918
|
+
];
|
|
919
|
+
// #6942 — the Services flyout's action bar links the /services hub. The
|
|
920
|
+
// hub rides the `services` surface's gate: its route directory serves both
|
|
921
|
+
// /services and /services/[slug], so every roster that enables the surface
|
|
922
|
+
// carries the hub. Emitted only inside a flyout that has a column (never
|
|
923
|
+
// an action-only flyout). A flyout action requires an icon
|
|
924
|
+
// (`NavFlyoutAction`), imported below only when this link is emitted.
|
|
925
|
+
const servicesHub = !features && services && flyoutGroups.length > 0;
|
|
784
926
|
const entries = [
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
927
|
+
// No empty flyout (D6): emitted only when at least one column has links.
|
|
928
|
+
...(flyoutGroups.length === 0
|
|
929
|
+
? []
|
|
930
|
+
: [
|
|
931
|
+
" {",
|
|
932
|
+
` labelKey: "${offeringLabelKey}",`,
|
|
933
|
+
" flyout: {",
|
|
934
|
+
// SOFT_LOCK_OK: #6508 — width "panel" (D4/D5): a fixed 640px panel.
|
|
935
|
+
' width: "panel",',
|
|
936
|
+
" features: [",
|
|
937
|
+
...offeringLinks,
|
|
938
|
+
...customerLinks,
|
|
939
|
+
" ],",
|
|
940
|
+
" groups: [",
|
|
941
|
+
...flyoutGroups,
|
|
942
|
+
" ],",
|
|
943
|
+
servicesHub
|
|
944
|
+
? ' actions: [{ labelKey: "services", icon: ArrowRight, path: "/services" }],'
|
|
945
|
+
: " actions: [],",
|
|
946
|
+
" },",
|
|
947
|
+
" },",
|
|
948
|
+
]),
|
|
949
|
+
// #6895 (operator decision 2026-09-27): a service shape's header reads
|
|
950
|
+
// Services ▾ · Portfolio · Blog · About — Portfolio a plain top-level
|
|
951
|
+
// link, never a flyout link (D6), and the first item when no flyout is
|
|
952
|
+
// emitted. Product shapes keep their header.
|
|
953
|
+
...(!features && services && portfolio
|
|
954
|
+
? [' { labelKey: "portfolio", path: "/portfolio" },']
|
|
796
955
|
: []),
|
|
797
|
-
|
|
798
|
-
' { labelKey: "faq", descKey: "faqDesc", icon: HelpCircle, path: "/faq" },',
|
|
799
|
-
" ],",
|
|
800
|
-
" actions: [],",
|
|
801
|
-
" },",
|
|
802
|
-
" },",
|
|
803
|
-
// Operator ruling 2026-09-23: Product · Use cases · Blog · Pricing · About.
|
|
956
|
+
// Operator ruling 2026-09-25: [flyout ·] Blog · Pricing · About.
|
|
804
957
|
// `/blog` is always linked — the `blog` surface serves the real route, and
|
|
805
958
|
// with it off the `[slug]` route serves the overlay's module page.
|
|
806
|
-
...(useCases ? [' { labelKey: "useCases", path: "/use-cases" },'] : []),
|
|
807
959
|
' { labelKey: "blog", path: "/blog" },',
|
|
808
960
|
...(on.has("pricing") ? [' { labelKey: "pricing", path: "/pricing" },'] : []),
|
|
809
961
|
' { labelKey: "about", path: "/about" },',
|
|
810
962
|
...linked.map((s) => ` { labelKey: "${s.navLabelKey}", path: "/${s.id}" },`),
|
|
811
963
|
];
|
|
812
|
-
// The footer's column groups (operator
|
|
813
|
-
//
|
|
964
|
+
// The footer's column groups (operator rulings 2026-09-23 + 2026-09-25,
|
|
965
|
+
// #6878 D6). Site pages, Features, Customers, Resources. Pricing rides the
|
|
814
966
|
// same `pricing` gate as the header item; `/blog` is always linked (above).
|
|
815
|
-
// The
|
|
816
|
-
//
|
|
817
|
-
// Glossary and Newsletter (#6658 D8) each ride their own
|
|
967
|
+
// The Features column links the three feature pages and the /features hub;
|
|
968
|
+
// the Customers column the three personas and the /for hub — each rides its
|
|
969
|
+
// surface's gate. Glossary and Newsletter (#6658 D8) each ride their own
|
|
970
|
+
// surface's gate. Book a call is always linked (#6897 D9), the same /blog
|
|
971
|
+
// precedent above: it retargets to /meet/book-a-call, served as a Free
|
|
972
|
+
// placeholder or the live scheduling page depending on what the site
|
|
973
|
+
// carries. No other footer column changes (D6) — there is deliberately no
|
|
974
|
+
// footer Services column.
|
|
818
975
|
const footerEntries = [
|
|
819
976
|
" {",
|
|
820
977
|
' headingKey: "footerSitePagesHeading",',
|
|
821
978
|
" items: [",
|
|
822
|
-
' { labelKey: "features", path: "/
|
|
979
|
+
...(features ? [' { labelKey: "features", path: "/features" },'] : []),
|
|
823
980
|
...(on.has("pricing")
|
|
824
981
|
? [' { labelKey: "pricing", path: "/pricing" },']
|
|
825
982
|
: []),
|
|
826
|
-
|
|
983
|
+
// #6895 — every shape's Site pages column links Portfolio, gated.
|
|
984
|
+
...(portfolio
|
|
985
|
+
? [' { labelKey: "portfolio", path: "/portfolio" },']
|
|
986
|
+
: []),
|
|
827
987
|
' { labelKey: "about", path: "/about" },',
|
|
828
988
|
' { labelKey: "contact", path: "/contact" },',
|
|
829
|
-
|
|
830
|
-
? [' { labelKey: "bookACall", path: "/book-a-call" },']
|
|
831
|
-
: []),
|
|
832
|
-
" ],",
|
|
833
|
-
" },",
|
|
834
|
-
" {",
|
|
835
|
-
' headingKey: "footerFeaturesHeading",',
|
|
836
|
-
" items: [",
|
|
837
|
-
' { labelKey: "feature1", path: "/software/product" },',
|
|
838
|
-
' { labelKey: "feature2", path: "/software/product" },',
|
|
839
|
-
' { labelKey: "feature3", path: "/software/product" },',
|
|
840
|
-
' { labelKey: "feature4", path: "/software/product" },',
|
|
841
|
-
' { labelKey: "feature5", path: "/software/product" },',
|
|
842
|
-
' { labelKey: "allFeatures", path: "/software/product" },',
|
|
989
|
+
' { labelKey: "bookACall", path: "/meet/book-a-call" },',
|
|
843
990
|
" ],",
|
|
844
991
|
" },",
|
|
992
|
+
...(features
|
|
993
|
+
? [
|
|
994
|
+
" {",
|
|
995
|
+
' headingKey: "footerFeaturesHeading",',
|
|
996
|
+
" items: [",
|
|
997
|
+
...featureLinks.map((l) => l.slice(2)),
|
|
998
|
+
' { labelKey: "allFeatures", path: "/features" },',
|
|
999
|
+
" ],",
|
|
1000
|
+
" },",
|
|
1001
|
+
]
|
|
1002
|
+
: []),
|
|
1003
|
+
...(forEnabled
|
|
1004
|
+
? [
|
|
1005
|
+
" {",
|
|
1006
|
+
' headingKey: "footerCustomersHeading",',
|
|
1007
|
+
" items: [",
|
|
1008
|
+
...customerLinks.map((l) => l.slice(2)),
|
|
1009
|
+
' { labelKey: "allCustomers", path: "/for" },',
|
|
1010
|
+
" ],",
|
|
1011
|
+
" },",
|
|
1012
|
+
]
|
|
1013
|
+
: []),
|
|
845
1014
|
" {",
|
|
846
1015
|
' headingKey: "footerResourcesHeading",',
|
|
847
1016
|
" items: [",
|
|
@@ -867,19 +1036,23 @@ export function renderNavConfig(enabled) {
|
|
|
867
1036
|
"from ",
|
|
868
1037
|
'"@working-theory/ui";',
|
|
869
1038
|
].join("");
|
|
870
|
-
|
|
1039
|
+
const body = `// nav-config.ts — site-nav registry.
|
|
871
1040
|
//
|
|
872
1041
|
// GENERATED by create-ailk from the declared route surfaces. Every entry here
|
|
873
1042
|
// points at a route this site actually carries — the nav is regenerated with
|
|
874
1043
|
// the surface set so it can never link a page that was not scaffolded.
|
|
875
1044
|
//
|
|
1045
|
+
// The first line hashes the rest of this file: while it matches, \`create-ailk
|
|
1046
|
+
// add-module\` regenerates this nav when a module adds pages; once you edit
|
|
1047
|
+
// below it, the nav is yours and add-module reports the change it wanted
|
|
1048
|
+
// instead.
|
|
1049
|
+
//
|
|
876
1050
|
// Adding a link: scaffold the page (the \`add-page\` skill), then add an
|
|
877
1051
|
// entry below with a \`labelKey\` that exists in the \`nav\` namespace of
|
|
878
1052
|
// \`apps/web/messages/<locale>.json\`. Paths are locale-agnostic — Nav prefixes
|
|
879
1053
|
// them with the active locale at render.
|
|
880
1054
|
${navItemImport}
|
|
881
|
-
import {
|
|
882
|
-
|
|
1055
|
+
${servicesHub ? 'import { ArrowRight } from "lucide-react";\n' : ""}
|
|
883
1056
|
export const navItems: NavItem[] = [
|
|
884
1057
|
${entries.join("\n")}
|
|
885
1058
|
];
|
|
@@ -895,4 +1068,7 @@ export const footerColumns: FooterColumnConfig[] = [
|
|
|
895
1068
|
${footerEntries.join("\n")}
|
|
896
1069
|
];
|
|
897
1070
|
`;
|
|
1071
|
+
// D1 — a first-line stamp holding the SHA-256 of the rest of the file
|
|
1072
|
+
// (`body`), so an unedited nav proves itself whatever generator wrote it.
|
|
1073
|
+
return `// ailk:generated sha256=${hashNavBody(body)}\n${body}`;
|
|
898
1074
|
}
|