create-ailk 0.6.1 → 0.8.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 +87 -14
- package/component-catalog.json +735 -95
- package/component-catalog.md +13 -7
- package/dist/add-module-core.d.ts +22 -0
- package/dist/add-module-core.js +50 -0
- package/dist/add-module.d.ts +89 -0
- package/dist/add-module.js +334 -0
- package/dist/category-profile.d.ts +126 -0
- package/dist/category-profile.js +262 -0
- package/dist/cli.js +89 -29
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -1
- package/dist/lib/extract-module-bundle.d.ts +56 -1
- package/dist/lib/extract-module-bundle.js +170 -14
- package/dist/lib/license-key-files.d.ts +15 -0
- package/dist/lib/license-key-files.js +102 -0
- package/dist/lib/module-license-gate.d.ts +79 -0
- package/dist/lib/module-license-gate.js +160 -12
- package/dist/lib/module-tier.d.ts +45 -16
- package/dist/lib/module-tier.js +76 -0
- package/dist/lib/pro-callout.d.ts +16 -10
- package/dist/lib/pro-callout.js +34 -3
- package/dist/license-set.d.ts +34 -0
- package/dist/license-set.js +194 -0
- package/dist/module-architecture.d.ts +20 -0
- package/dist/module-architecture.js +77 -19
- package/dist/modules/commerce/0.6.1.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
- package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
- package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
- package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
- package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
- package/dist/modules/workspace/0.6.1.tar.gz +0 -0
- package/dist/parse-args.d.ts +85 -10
- package/dist/parse-args.js +227 -15
- package/dist/programmatic.d.ts +81 -5
- package/dist/programmatic.js +291 -32
- package/dist/site-chrome.d.ts +18 -7
- package/dist/site-chrome.js +32 -14
- package/dist/site-roster.d.ts +127 -0
- package/dist/site-roster.js +499 -0
- package/dist/starter-grid.d.ts +216 -0
- package/dist/starter-grid.js +449 -0
- package/dist/surfaces.d.ts +20 -7
- package/dist/surfaces.js +204 -24
- package/dist/sync-routes.d.ts +9 -0
- package/dist/sync-routes.js +2 -2
- package/dist/template-manifest.d.ts +94 -19
- package/dist/template-manifest.js +170 -289
- package/dist/template-names.d.ts +14 -12
- package/dist/template-names.js +13 -17
- package/package.json +6 -4
- package/site-templates/_base/content/en/BookCall/book-call.mdx +52 -0
- 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 +65 -0
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +67 -0
- 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/Pricing/pricing.mdx +71 -62
- package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +17 -3
- package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
- package/site-templates/_base/content/en/UseCase/persona-1.mdx +69 -0
- package/site-templates/_base/content/en/UseCase/persona-2.mdx +69 -0
- package/site-templates/_base/content/en/UseCase/persona-3.mdx +69 -0
- package/site-templates/designs/minimalism/DESIGN.md +162 -0
- package/site-templates/designs/standard/DESIGN.md +167 -0
- package/site-templates/marketing-site/DESIGN.md +59 -40
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +376 -57
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +230 -90
- package/site-templates/marketing-site/ailk-template.json +1 -0
- package/site-templates/marketing-site-free/DESIGN.md +59 -40
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +323 -63
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +323 -113
- package/site-templates/marketing-site-free/ailk-template.json +3 -2
- 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/minimalism-site/DESIGN.md +54 -40
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +173 -51
- package/site-templates/minimalism-site/ailk-template.json +3 -2
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +52 -0
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
- package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/saas-product/DESIGN.md +59 -40
- package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +369 -0
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +302 -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/shapes/local-business-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- 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 +362 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +300 -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/shapes/organization-service/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/shapes/organization-service/provenance.json +4 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +369 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +299 -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 +169 -0
- package/site-templates/shapes/person-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- 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/settings.json +15 -0
- package/templates/.claude/skills/README.md +10 -9
- package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
- package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
- package/templates/.claude/skills/add-page/references/page-types.md +115 -0
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
- package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
- package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
- package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
- package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
- package/templates/.claude/skills/fill-page/SKILL.md +180 -0
- package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
- package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
- 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/{provision-config → setup-deploy}/SKILL.md +73 -41
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +21 -30
- package/templates/.claude/skills/setup-deploy/references/runbook-template.md +204 -0
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
- package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
- package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
- package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
- package/templates/.github/workflows/ci.yml +5 -0
- package/templates/CLAUDE.md +8 -2
- package/templates/DESIGN.md +56 -44
- package/templates/README.md +116 -46
- package/templates/apps/api/.env.example +2 -1
- package/templates/apps/api/CLAUDE.md +3 -2
- package/templates/apps/api/package.json +1 -1
- package/templates/apps/api/src/config/__tests__/index.test.ts +146 -10
- package/templates/apps/api/src/config/index.ts +46 -14
- 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/license-key-store.ts +45 -0
- package/templates/apps/api/src/lib/site.ts +5 -3
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
- package/templates/apps/api/src/middleware/auth.ts +9 -0
- package/templates/apps/api/src/openapi/spec.ts +63 -12
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
- package/templates/apps/api/src/routes/content/get-page.ts +86 -2
- 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/server.ts +13 -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/lead-routing.ts +140 -30
- package/templates/apps/api/src/services/leads-inbox.ts +48 -11
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
- package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
- package/templates/apps/mcp/package.json +2 -1
- package/templates/apps/mcp/src/index.ts +7 -0
- package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
- package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
- package/templates/apps/mcp/src/tools/create_project.ts +21 -6
- package/templates/apps/web/__tests__/setup.ts +18 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
- package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
- package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +67 -29
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +94 -11
- 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 +228 -86
- 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 +115 -7
- package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
- 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]/glossary/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/layout.tsx +146 -52
- 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 +142 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
- package/templates/apps/web/app/[locale]/portfolio/page.tsx +49 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/{cancellation → purchase-policy}/page.tsx +15 -10
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +63 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
- package/templates/apps/web/app/error-fallback.tsx +17 -45
- package/templates/apps/web/app/not-found.tsx +14 -38
- package/templates/apps/web/app/og/route.tsx +6 -0
- package/templates/apps/web/eslint.config.mjs +3 -0
- package/templates/apps/web/instrumentation.ts +14 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
- package/templates/apps/web/lib/license-key-store.ts +52 -0
- package/templates/apps/web/lib/nav-config.ts +82 -18
- package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
- package/templates/apps/web/lib/site-brand.tsx +1 -1
- package/templates/apps/web/lib/site-chrome.ts +3 -3
- package/templates/apps/web/lib/site-identity.ts +148 -0
- package/templates/apps/web/lib/site-theme.ts +11 -0
- package/templates/apps/web/messages/en.json +78 -37
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +3 -3
- package/templates/apps/web/public/logomark.svg +6 -3
- package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
- package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +4 -4
- package/templates/apps/web/public/wordmark-light.svg +4 -0
- package/templates/apps/web/public/wordmark.svg +3 -3
- package/templates/apps/web/security-headers.config.ts +5 -1
- package/templates/content/_site.mdx +25 -0
- package/templates/content/en/AboutPage/about.mdx +150 -144
- package/templates/content/en/BlogList/blog.mdx +126 -0
- package/templates/content/en/ContactPage/contact.mdx +21 -92
- package/templates/content/en/FAQPage/faq.mdx +86 -57
- package/templates/content/en/HomePage/home.mdx +268 -217
- package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +181 -0
- package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
- package/templates/database/CHANGELOG.md +14 -0
- package/templates/database/package.json +1 -1
- package/templates/package.json +3 -1
- package/templates/tools/lint-architecture.ts +315 -0
- package/templates/tools/tsconfig.json +14 -0
- package/versions.json +33 -33
- package/site-templates/simple-service/DESIGN.md +0 -148
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +0 -240
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -278
- package/site-templates/simple-service/ailk-template.json +0 -10
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -67
- 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-schema/SKILL.md +0 -277
- package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
- package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
- package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
- package/templates/.claude/skills/provision-config/references/runbook-template.md +0 -248
- package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
- package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -150
- package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
- package/templates/apps/web/app/[locale]/shipping/page.tsx +0 -44
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
- package/templates/content/en/LandingPage/blog.mdx +0 -83
- package/templates/content/en/LandingPage/features.mdx +0 -103
- package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
* as fallback). Cross-domain URLs are rejected synchronously before any I/O.
|
|
10
10
|
* No `fetch()` is called anywhere in this file.
|
|
11
11
|
*
|
|
12
|
+
* Sitewide identity (#6597): the content root's `_site.mdx` (or its parent's)
|
|
13
|
+
* is loaded, and the page's JSON-LD is stored as the flat node array
|
|
14
|
+
* `composeGraph` produces — the page's primary first, then the identity nodes
|
|
15
|
+
* the web render path emits — with the audit's identity mode following the
|
|
16
|
+
* site's kind. The same composition the `ailk-audit` CLI uses.
|
|
17
|
+
*
|
|
12
18
|
* Input: { slug, type? } | { url } (exactly one branch; not both, not neither)
|
|
13
19
|
* Output: AuditInput ready for runAudit
|
|
14
20
|
* Session/scope: none — read-only filesystem access via content adapter.
|
|
@@ -18,15 +24,25 @@ import { join } from "node:path";
|
|
|
18
24
|
|
|
19
25
|
import {
|
|
20
26
|
READINESS_SCAN_TYPES,
|
|
27
|
+
chassisSectionIds,
|
|
21
28
|
type AuditInput,
|
|
22
29
|
type ReadinessPageFacts,
|
|
23
30
|
} from "@working-theory/aeo";
|
|
31
|
+
import {
|
|
32
|
+
identityModeForSite,
|
|
33
|
+
loadSitewideIdentity,
|
|
34
|
+
} from "@working-theory/aeo/cli/build-input";
|
|
24
35
|
import {
|
|
25
36
|
type ContentAdapter,
|
|
26
37
|
type ContentPage,
|
|
27
38
|
type SchemaOrgType,
|
|
28
39
|
} from "@working-theory/content-adapters";
|
|
29
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
buildJsonLd,
|
|
42
|
+
composeGraph,
|
|
43
|
+
graphNodes,
|
|
44
|
+
pageTypeRegistry,
|
|
45
|
+
} from "@working-theory/schema";
|
|
30
46
|
|
|
31
47
|
import { getMcpContentAdapter } from "./content-adapter.js";
|
|
32
48
|
import { resolveLocale } from "./resolve-locale.js";
|
|
@@ -134,14 +150,14 @@ function extractTypeSlugFromUrl(raw: string): {
|
|
|
134
150
|
/**
|
|
135
151
|
* The site-level view the readiness rules grade (#5949 FR9/D5).
|
|
136
152
|
*
|
|
137
|
-
*
|
|
138
|
-
* `Product` / `Service`, the landing-style
|
|
139
|
-
* Independent of site size and of which page was audited (NFR4). The
|
|
140
|
-
* are independent of each other, so they run concurrently (#6012).
|
|
153
|
+
* Three adapter calls, bounded and type-closed: the collection-style pair for
|
|
154
|
+
* `Product` / `Service`, the landing-style read for `PurchasePolicyPage`
|
|
155
|
+
* (#6646). Independent of site size and of which page was audited (NFR4). The
|
|
156
|
+
* three are independent of each other, so they run concurrently (#6012).
|
|
141
157
|
*
|
|
142
158
|
* Fail-open-to-pass, stated: under an unresolved adapter every list is `[]`,
|
|
143
159
|
* `sitePages` is empty, and all three rules pass — a site with no known priced
|
|
144
|
-
* page owes no policy
|
|
160
|
+
* page owes no purchase policy, which is the correct verdict for "the audit could
|
|
145
161
|
* not see the site". A type the adapter legitimately has no pages of resolves
|
|
146
162
|
* this way too, silently — it is not an error.
|
|
147
163
|
*
|
|
@@ -185,6 +201,8 @@ async function scanSitePages(
|
|
|
185
201
|
const facts: ReadinessPageFacts[] = [];
|
|
186
202
|
for (const { type, pages } of perType) {
|
|
187
203
|
for (const page of pages) {
|
|
204
|
+
// The purchase policy's section anchors (#6646) — which policies it carries.
|
|
205
|
+
const sectionIds = chassisSectionIds(page.sections);
|
|
188
206
|
facts.push({
|
|
189
207
|
slug: page.slug,
|
|
190
208
|
type,
|
|
@@ -192,6 +210,7 @@ async function scanSitePages(
|
|
|
192
210
|
...(page.stripe?.taxCode !== undefined && {
|
|
193
211
|
taxCode: page.stripe.taxCode,
|
|
194
212
|
}),
|
|
213
|
+
...(sectionIds !== undefined && { chassisSectionIds: sectionIds }),
|
|
195
214
|
});
|
|
196
215
|
}
|
|
197
216
|
}
|
|
@@ -251,15 +270,21 @@ export async function buildSinglePageAuditInput(
|
|
|
251
270
|
);
|
|
252
271
|
}
|
|
253
272
|
|
|
273
|
+
const site = loadSitewideIdentity(root, locale);
|
|
274
|
+
|
|
254
275
|
const pageKey = `${type}/${slug}`;
|
|
255
276
|
const jsonLdByPage: Record<string, unknown> = {};
|
|
256
277
|
try {
|
|
257
278
|
// Cast to ContentPageInput — structurally compatible but exactOptionalPropertyTypes
|
|
258
279
|
// differs between @working-theory/content-adapters ContentPage and @working-theory/schema ContentPageInput.
|
|
259
280
|
// This is the same pattern used in packages/aeo/src/cli/build-input.ts.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
281
|
+
const primary = buildJsonLd(page as Parameters<typeof buildJsonLd>[0]);
|
|
282
|
+
jsonLdByPage[pageKey] =
|
|
283
|
+
site === undefined
|
|
284
|
+
? primary
|
|
285
|
+
: graphNodes(
|
|
286
|
+
composeGraph({ primary, site, isHomePage: type === "HomePage" }),
|
|
287
|
+
);
|
|
263
288
|
} catch {
|
|
264
289
|
// Omit key on build failure — downstream rule reports "no JSON-LD".
|
|
265
290
|
// This mirrors packages/aeo/src/cli/build-input.ts behavior.
|
|
@@ -270,7 +295,8 @@ export async function buildSinglePageAuditInput(
|
|
|
270
295
|
return {
|
|
271
296
|
pages: [{ slug, type, locale }],
|
|
272
297
|
locale,
|
|
273
|
-
identityMode:
|
|
298
|
+
identityMode:
|
|
299
|
+
site !== undefined ? identityModeForSite(site) : DEFAULT_IDENTITY_MODE,
|
|
274
300
|
registry: pageTypeRegistry,
|
|
275
301
|
jsonLdByPage,
|
|
276
302
|
readiness: {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers the durable home of a renewed licence key (#6677 DD-6).
|
|
3
|
+
*
|
|
4
|
+
* `@working-theory/license` renews the deployment's key before it lapses and
|
|
5
|
+
* keeps the renewed key in a `LicenseKeyStore`. Its default store is a file,
|
|
6
|
+
* which a redeploy wipes. With a database configured, this registers the
|
|
7
|
+
* store backed by ONE deployment-wide `SiteSetting` row,
|
|
8
|
+
* `(DEFAULT_WORKSPACE_ID, "license.renewed_key")`, shared with apps/api and
|
|
9
|
+
* apps/web — so the key apps/api renewed is the key this server resolves.
|
|
10
|
+
*
|
|
11
|
+
* `index.ts` awaits this BEFORE `initMcpCommerceClaim()`, which memoises the
|
|
12
|
+
* claim at boot. Both imports are dynamic and reached only when
|
|
13
|
+
* `DATABASE_URL` is set, so an MDX-only deployment never constructs a Prisma
|
|
14
|
+
* client (the `prisma.ts` header's rule).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register the SiteSetting-backed store when `DATABASE_URL` is set. An
|
|
19
|
+
* explicitly set `AILK_LICENSE_STORAGE_PATH` is the operator choosing the
|
|
20
|
+
* file store, and is honoured. Never throws: a failure logs and leaves the
|
|
21
|
+
* file store in place.
|
|
22
|
+
*/
|
|
23
|
+
export async function registerLicenseKeyStore(): Promise<void> {
|
|
24
|
+
if (!process.env["DATABASE_URL"] || process.env["AILK_LICENSE_STORAGE_PATH"]) return;
|
|
25
|
+
try {
|
|
26
|
+
const [{ getMcpPrismaClient }, { DEFAULT_WORKSPACE_ID }, license] =
|
|
27
|
+
await Promise.all([
|
|
28
|
+
import("./prisma.js"),
|
|
29
|
+
import("@working-theory/database/tenancy"),
|
|
30
|
+
import("@working-theory/license"),
|
|
31
|
+
]);
|
|
32
|
+
const prisma = getMcpPrismaClient();
|
|
33
|
+
license.setLicenseKeyStore(
|
|
34
|
+
license.createSiteSettingLicenseKeyStore(
|
|
35
|
+
prisma.siteSetting,
|
|
36
|
+
DEFAULT_WORKSPACE_ID,
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error(
|
|
41
|
+
"[mcp] could not register the SiteSetting licence-key store — the file store stays in place:",
|
|
42
|
+
err instanceof Error ? err.name : "non-Error thrown",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
* correctly in production and in integration tests.
|
|
17
17
|
*
|
|
18
18
|
* Input: { targetDir, projectName, preset? }
|
|
19
|
+
* The tool builds the Free organization-product starter (`--shape
|
|
20
|
+
* organization-product`, #6754 D9); `preset` stays reserved. Always builds
|
|
21
|
+
* the Free module set (`--modules marketing`, #6759 D6, SEC-7): a licence
|
|
22
|
+
* key sitting in the MCP server's own environment never widens what an
|
|
23
|
+
* anonymous caller receives — the explicit keep list takes precedence over
|
|
24
|
+
* any key-derived level probe before the child process reads its env.
|
|
19
25
|
* targetDir — absolute or relative path to scaffold into; must not exist
|
|
20
26
|
* or must be empty (enforced by create-ailk ValidationError)
|
|
21
27
|
* projectName — project name used in README and package.json
|
|
@@ -94,12 +100,21 @@ export async function ailkCreateProject(
|
|
|
94
100
|
const targetAbs = resolve(input.targetDir);
|
|
95
101
|
|
|
96
102
|
// Spawn the CLI synchronously — no network, no async I/O needed.
|
|
97
|
-
// Hard-coded node invocation; only targetDir flows through user input.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
// Hard-coded node invocation; only targetDir flows through user input. The
|
|
104
|
+
// CLI refuses a run naming no shape and no template, so the tool names one.
|
|
105
|
+
// `--modules marketing` is pinned explicitly (#6759 D6, SEC-7): an explicit
|
|
106
|
+
// keep list takes the keep branch and resolves before the child ever reads
|
|
107
|
+
// AILK_LICENSE_KEY, so a licence key in the server's own environment can
|
|
108
|
+
// never turn this anonymous call into the keyed level probe.
|
|
109
|
+
const result = spawnSync(
|
|
110
|
+
"node",
|
|
111
|
+
[bin, targetAbs, "--shape", "organization-product", "--modules", "marketing"],
|
|
112
|
+
{
|
|
113
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
114
|
+
shell: false,
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
},
|
|
117
|
+
);
|
|
103
118
|
|
|
104
119
|
if (result.status !== 0) {
|
|
105
120
|
// Extract the error message from stderr (CLI writes "[create-ailk] <msg>").
|
|
@@ -17,6 +17,24 @@ if (typeof globalThis.TextEncoder === "undefined") {
|
|
|
17
17
|
(globalThis as unknown as { TextDecoder: unknown }).TextDecoder = TextDecoder;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// SOFT_LOCK_OK: #6510 (D1) — jsdom does not implement ResizeObserver.
|
|
21
|
+
// BannerConsentIsland (the layout's always-mounted consent Banner,
|
|
22
|
+
// placement="bottom") now constructs one when reserveSpace is set — the
|
|
23
|
+
// SAME stub @working-theory/ui's own package test setup already provides
|
|
24
|
+
// (packages/ui/src/__tests__/setup.ts), needed here too because this
|
|
25
|
+
// workspace's jest environment is separate from that package's.
|
|
26
|
+
if (typeof globalThis.ResizeObserver === "undefined") {
|
|
27
|
+
/* eslint-disable no-restricted-syntax -- jsdom stub cannot fully implement ResizeObserver; a double-cast is unavoidable in a global setup file */
|
|
28
|
+
class ResizeObserverStub {
|
|
29
|
+
observe(): void {}
|
|
30
|
+
unobserve(): void {}
|
|
31
|
+
disconnect(): void {}
|
|
32
|
+
}
|
|
33
|
+
globalThis.ResizeObserver =
|
|
34
|
+
ResizeObserverStub as unknown as typeof ResizeObserver;
|
|
35
|
+
/* eslint-enable no-restricted-syntax */
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
// jsdom does not implement window.matchMedia. Provide a minimal stub so any
|
|
21
39
|
// component that calls matchMedia (e.g. ThemeToggle via Nav) can run
|
|
22
40
|
// in tests without crashing. Guard with typeof so server-environment tests
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
* its rendering on anything the response says about the address.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
AuthCard,
|
|
31
|
+
type AuthCardLabels,
|
|
32
|
+
type AuthSubmitData,
|
|
33
|
+
} from "@working-theory/ui/blocks";
|
|
30
34
|
import { createAuthClient } from "better-auth/react";
|
|
31
35
|
import type { ReactElement } from "react";
|
|
32
36
|
import { useState } from "react";
|
|
@@ -74,6 +78,10 @@ export function ForgotPasswordCard({
|
|
|
74
78
|
setSent(true);
|
|
75
79
|
};
|
|
76
80
|
|
|
81
|
+
const handleSubmit = (data: AuthSubmitData): void => {
|
|
82
|
+
void submit(data.email);
|
|
83
|
+
};
|
|
84
|
+
|
|
77
85
|
return (
|
|
78
86
|
<div className="flex flex-col gap-4" data-testid="forgot-password-page">
|
|
79
87
|
<AuthCard
|
|
@@ -81,7 +89,7 @@ export function ForgotPasswordCard({
|
|
|
81
89
|
signInHref={signInHref}
|
|
82
90
|
labels={labels}
|
|
83
91
|
{...(sent ? { sentTo } : {})}
|
|
84
|
-
onSubmit={
|
|
92
|
+
onSubmit={handleSubmit}
|
|
85
93
|
errors={errors}
|
|
86
94
|
/>
|
|
87
95
|
</div>
|
|
@@ -59,9 +59,9 @@ export default async function AuthLayout({
|
|
|
59
59
|
* container — it rendered 48 x 350 here before the clamp. The shell
|
|
60
60
|
* owns the row's geometry; the site owns its mark's size.
|
|
61
61
|
*/
|
|
62
|
-
<
|
|
62
|
+
<div className="inline-flex h-7 items-center [&>img]:h-full [&>img]:w-auto [&>img]:object-contain">
|
|
63
63
|
{siteLogo()}
|
|
64
|
-
</
|
|
64
|
+
</div>
|
|
65
65
|
}
|
|
66
66
|
brandHref={localeHref(locale, "/")}
|
|
67
67
|
footerLinks={footerLinks}
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
* submits nothing and fails obscurely is worse than the honest message.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
AuthCard,
|
|
22
|
+
type AuthCardLabels,
|
|
23
|
+
type AuthSubmitData,
|
|
24
|
+
} from "@working-theory/ui/blocks";
|
|
21
25
|
import { createAuthClient } from "better-auth/react";
|
|
22
26
|
import type { ReactElement } from "react";
|
|
23
27
|
import { useState } from "react";
|
|
@@ -93,15 +97,17 @@ export function ResetPasswordCard({
|
|
|
93
97
|
navigate(signInHref);
|
|
94
98
|
};
|
|
95
99
|
|
|
100
|
+
const handleSubmit = (data: AuthSubmitData): void => {
|
|
101
|
+
void submit(data.password ?? "", data.confirmPassword ?? "");
|
|
102
|
+
};
|
|
103
|
+
|
|
96
104
|
return (
|
|
97
105
|
<div className="flex flex-col gap-4" data-testid="reset-password-page">
|
|
98
106
|
<AuthCard
|
|
99
107
|
intent="reset"
|
|
100
108
|
signInHref={signInHref}
|
|
101
109
|
labels={labels}
|
|
102
|
-
onSubmit={
|
|
103
|
-
void submit(data.password ?? "", data.confirmPassword ?? "")
|
|
104
|
-
}
|
|
110
|
+
onSubmit={handleSubmit}
|
|
105
111
|
errors={errors}
|
|
106
112
|
/>
|
|
107
113
|
</div>
|
|
@@ -38,7 +38,11 @@
|
|
|
38
38
|
* a dead end — AuthCard's own default (`"link"`) assumes reset is wired.
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
AuthCard,
|
|
43
|
+
type AuthCardLabels,
|
|
44
|
+
type AuthSubmitData,
|
|
45
|
+
} from "@working-theory/ui/blocks";
|
|
42
46
|
import { Alert, AlertDescription } from "@working-theory/ui/primitives";
|
|
43
47
|
import type { ReactElement } from "react";
|
|
44
48
|
|
|
@@ -84,6 +88,21 @@ export function SignInCard({
|
|
|
84
88
|
|
|
85
89
|
const notConfigured = method === "sso" && providers.length === 0;
|
|
86
90
|
|
|
91
|
+
const providerButtons = providers.map((provider) => ({
|
|
92
|
+
provider,
|
|
93
|
+
onClick: () => void launchProvider(provider),
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
const handleSubmit = (data: AuthSubmitData): void => {
|
|
97
|
+
void submitPassword(
|
|
98
|
+
authClient.signIn.email({
|
|
99
|
+
email: data.email,
|
|
100
|
+
password: data.password ?? "",
|
|
101
|
+
callbackURL: next,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
87
106
|
return (
|
|
88
107
|
/*
|
|
89
108
|
* No page framing of its own (#5963): AuthShell owns the brand row, the
|
|
@@ -104,19 +123,8 @@ export function SignInCard({
|
|
|
104
123
|
{...(forgotHref !== undefined ? { forgotHref } : {})}
|
|
105
124
|
{...(forgot !== undefined ? { forgot } : {})}
|
|
106
125
|
{...(notConfigured ? { subhead: notConfiguredSubhead } : {})}
|
|
107
|
-
providers={
|
|
108
|
-
|
|
109
|
-
onClick: () => void launchProvider(provider),
|
|
110
|
-
}))}
|
|
111
|
-
onSubmit={(data) =>
|
|
112
|
-
void submitPassword(
|
|
113
|
-
authClient.signIn.email({
|
|
114
|
-
email: data.email,
|
|
115
|
-
password: data.password ?? "",
|
|
116
|
-
callbackURL: next,
|
|
117
|
-
}),
|
|
118
|
-
)
|
|
119
|
-
}
|
|
126
|
+
providers={providerButtons}
|
|
127
|
+
onSubmit={handleSubmit}
|
|
120
128
|
errors={errors}
|
|
121
129
|
labels={labels}
|
|
122
130
|
/>
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
* rendered as an `Alert` above the card for `method="sso"`.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
AuthCard,
|
|
26
|
+
type AuthCardLabels,
|
|
27
|
+
type AuthSubmitData,
|
|
28
|
+
} from "@working-theory/ui/blocks";
|
|
25
29
|
import { Alert, AlertDescription } from "@working-theory/ui/primitives";
|
|
26
30
|
import type { ReactElement } from "react";
|
|
27
31
|
|
|
@@ -64,6 +68,25 @@ export function SignUpCard({
|
|
|
64
68
|
|
|
65
69
|
const notConfigured = method === "sso" && providers.length === 0;
|
|
66
70
|
|
|
71
|
+
const providerButtons = providers.map((provider) => ({
|
|
72
|
+
provider,
|
|
73
|
+
onClick: () => void launchProvider(provider),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
const handleSubmit = (data: AuthSubmitData): void => {
|
|
77
|
+
// Better Auth requires a `name` on email sign-up; this card collects
|
|
78
|
+
// none, so the address doubles as the display name until the person
|
|
79
|
+
// sets one.
|
|
80
|
+
void submitPassword(
|
|
81
|
+
authClient.signUp.email({
|
|
82
|
+
email: data.email,
|
|
83
|
+
password: data.password ?? "",
|
|
84
|
+
name: data.email,
|
|
85
|
+
callbackURL: next,
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
67
90
|
return (
|
|
68
91
|
<div className="flex flex-col gap-4" data-testid="sign-up-page">
|
|
69
92
|
{method === "sso" && providerError ? (
|
|
@@ -76,23 +99,8 @@ export function SignUpCard({
|
|
|
76
99
|
method={method}
|
|
77
100
|
signInHref={signInHref}
|
|
78
101
|
{...(notConfigured ? { subhead: notConfiguredSubhead } : {})}
|
|
79
|
-
providers={
|
|
80
|
-
|
|
81
|
-
onClick: () => void launchProvider(provider),
|
|
82
|
-
}))}
|
|
83
|
-
onSubmit={(data) =>
|
|
84
|
-
// Better Auth requires a `name` on email sign-up; this card collects
|
|
85
|
-
// none, so the address doubles as the display name until the person
|
|
86
|
-
// sets one.
|
|
87
|
-
void submitPassword(
|
|
88
|
-
authClient.signUp.email({
|
|
89
|
-
email: data.email,
|
|
90
|
-
password: data.password ?? "",
|
|
91
|
-
name: data.email,
|
|
92
|
-
callbackURL: next,
|
|
93
|
-
}),
|
|
94
|
-
)
|
|
95
|
-
}
|
|
102
|
+
providers={providerButtons}
|
|
103
|
+
onSubmit={handleSubmit}
|
|
96
104
|
errors={errors}
|
|
97
105
|
labels={labels}
|
|
98
106
|
/>
|
|
@@ -153,7 +153,11 @@ export default async function AccountPage({ params }: Props): Promise<ReactEleme
|
|
|
153
153
|
locale={locale}
|
|
154
154
|
navItems={navItems}
|
|
155
155
|
activePath={localeHref(locale, "/account")}
|
|
156
|
-
logo={
|
|
156
|
+
logo={
|
|
157
|
+
<Text as="span" variant="body-sm" className="font-semibold">
|
|
158
|
+
{siteName}
|
|
159
|
+
</Text>
|
|
160
|
+
}
|
|
157
161
|
user={{ name: displayName }}
|
|
158
162
|
/>
|
|
159
163
|
);
|
|
@@ -212,8 +216,12 @@ export default async function AccountPage({ params }: Props): Promise<ReactEleme
|
|
|
212
216
|
{
|
|
213
217
|
label: t("profile.email"),
|
|
214
218
|
value: (
|
|
215
|
-
<
|
|
216
|
-
<
|
|
219
|
+
<div className="inline-flex flex-wrap items-center gap-2">
|
|
220
|
+
<div data-testid="account-profile-email">
|
|
221
|
+
<Text as="span" variant="body-sm">
|
|
222
|
+
{email}
|
|
223
|
+
</Text>
|
|
224
|
+
</div>
|
|
217
225
|
<Badge
|
|
218
226
|
variant={emailVerified ? "success" : "warning"}
|
|
219
227
|
size="sm"
|
|
@@ -222,7 +230,7 @@ export default async function AccountPage({ params }: Props): Promise<ReactEleme
|
|
|
222
230
|
>
|
|
223
231
|
{emailVerified ? t("profile.verified") : t("profile.unverified")}
|
|
224
232
|
</Badge>
|
|
225
|
-
</
|
|
233
|
+
</div>
|
|
226
234
|
),
|
|
227
235
|
},
|
|
228
236
|
]}
|
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
* emitted JSON-LD (the escape hatch, proven with a `HowTo` fixture).
|
|
8
8
|
* - Unknown slug: getMdxSource returns null → renderPage's own detail-route
|
|
9
9
|
* branch (no translationsNamespace passed) calls notFound().
|
|
10
|
-
* - Resolution: the route asks the adapter for
|
|
11
|
-
* one
|
|
10
|
+
* - Resolution: the route asks the adapter for `LandingPage` first and, only
|
|
11
|
+
* when that is null, `BlogList` (the Free /blog teaser — the one list type
|
|
12
|
+
* that cannot own a route directory, since the docs-blog module owns
|
|
13
|
+
* `blog/`). A BlogList source renders through renderPage with its own type,
|
|
14
|
+
* so the JSON-LD is the BlogList builder's `Blog`.
|
|
12
15
|
*/
|
|
13
16
|
import React from "react";
|
|
14
17
|
import { render } from "@testing-library/react";
|
|
@@ -61,7 +64,7 @@ function makeSource(slug: string, schema?: Record<string, unknown>) {
|
|
|
61
64
|
};
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
describe("[slug] — the generic LandingPage
|
|
67
|
+
describe("[slug] — the generic content route (LandingPage, then BlogList)", () => {
|
|
65
68
|
beforeEach(() => {
|
|
66
69
|
mockNotFound.mockClear();
|
|
67
70
|
});
|
|
@@ -97,7 +100,35 @@ describe("[slug] — the generic LandingPage content route", () => {
|
|
|
97
100
|
expect(script?.textContent ?? "").toContain('"HowTo"');
|
|
98
101
|
});
|
|
99
102
|
|
|
100
|
-
it("
|
|
103
|
+
it("falls back to BlogList when no LandingPage source exists (the Free /blog teaser)", async () => {
|
|
104
|
+
const blog = {
|
|
105
|
+
frontmatter: {
|
|
106
|
+
type: "BlogList" as const,
|
|
107
|
+
slug: "blog",
|
|
108
|
+
title: "Blog",
|
|
109
|
+
description: "This template does not include a blog.",
|
|
110
|
+
sections: [{ id: "hero", type: "hero" as const, data: { headline: "Blog" } }],
|
|
111
|
+
},
|
|
112
|
+
body: "",
|
|
113
|
+
};
|
|
114
|
+
const getMdxSource = jest
|
|
115
|
+
.fn()
|
|
116
|
+
.mockImplementation(async (type: string) => (type === "BlogList" ? blog : null));
|
|
117
|
+
|
|
118
|
+
const element = await SlugPage({
|
|
119
|
+
params: Promise.resolve({ locale: "en", slug: "blog" }),
|
|
120
|
+
getMdxSource,
|
|
121
|
+
});
|
|
122
|
+
const { container } = render(element);
|
|
123
|
+
|
|
124
|
+
expect(getMdxSource).toHaveBeenNthCalledWith(1, "LandingPage", "blog", "en");
|
|
125
|
+
expect(getMdxSource).toHaveBeenNthCalledWith(2, "BlogList", "blog", "en");
|
|
126
|
+
const script = container.querySelector("script[type='application/ld+json']");
|
|
127
|
+
expect(script?.textContent ?? "").toContain('"@type":"Blog"');
|
|
128
|
+
expect(mockNotFound).not.toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("calls notFound() for a slug with neither a LandingPage nor a BlogList source", async () => {
|
|
101
132
|
const getMdxSource = jest.fn().mockResolvedValue(null);
|
|
102
133
|
|
|
103
134
|
await expect(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The one generic content route (#5469 D2). Serves the Free marketing-site
|
|
2
|
-
// template's /
|
|
2
|
+
// template's /blog teaser — and any other
|
|
3
3
|
// `content/<locale>/LandingPage/<slug>.mdx` a site adds — through exactly the
|
|
4
4
|
// render path the static routes use (`renderPage`, sections mode, JSON-LD
|
|
5
5
|
// from frontmatter — a page's `schema:` override rides the escape hatch,
|
|
@@ -12,11 +12,17 @@
|
|
|
12
12
|
// renders here; with the docs-blog module its `blog/[[...slug]]` directory
|
|
13
13
|
// wins and the teaser file is inert (#5469 D3).
|
|
14
14
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
15
|
+
// Two content types, in a fixed order: `LandingPage` first (the coined
|
|
16
|
+
// slug-keyed landing surface whose builder emits a `WebPage` envelope), then
|
|
17
|
+
// `BlogList` — the one list type that cannot have a route directory of its
|
|
18
|
+
// own, because the docs-blog module owns `blog/` (`blog/[[...slug]]`) and
|
|
19
|
+
// Next.js refuses a `blog/page.tsx` beside an optional catch-all. So the
|
|
20
|
+
// Free `/blog` teaser is a `BlogList` page (schema `Blog` + an empty
|
|
21
|
+
// `ItemList` — the correct page type, not a LandingPage wearing a `schema:`
|
|
22
|
+
// override the structured-data gate rejects) served here, and still yields
|
|
23
|
+
// to the module's directory by the same precedence. Every other list type
|
|
24
|
+
// has its own route directory (`/features` → ProductsList, `/use-cases` →
|
|
25
|
+
// UseCasesList) and never reaches this route.
|
|
20
26
|
//
|
|
21
27
|
// Unknown slug → `notFound()` (renderPage's own detail-route branch: no
|
|
22
28
|
// `translationsNamespace` is passed, so a null source 404s rather than
|
|
@@ -28,7 +34,11 @@
|
|
|
28
34
|
// segment and fail any slug outside the build-time list with
|
|
29
35
|
// DYNAMIC_SERVER_USAGE (a 500, not the 404 an unknown slug must get).
|
|
30
36
|
import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
|
|
31
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
getMdxSource as loadMdxSource,
|
|
39
|
+
renderPage,
|
|
40
|
+
renderPageMetadata,
|
|
41
|
+
} from "@working-theory/page-renderer";
|
|
32
42
|
import type { Metadata } from "next";
|
|
33
43
|
import type { ReactElement } from "react";
|
|
34
44
|
|
|
@@ -43,11 +53,30 @@ interface Props {
|
|
|
43
53
|
getMdxSource?: typeof defaultGetMdxSource;
|
|
44
54
|
}
|
|
45
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Which of the two content types carries this slug. `LandingPage` wins when
|
|
58
|
+
* it exists; `BlogList` is probed only when it does not, so a LandingPage
|
|
59
|
+
* slug never pays a second adapter read. Neither → `LandingPage`, whose null
|
|
60
|
+
* source is what makes renderPage 404.
|
|
61
|
+
*/
|
|
62
|
+
async function resolveType(
|
|
63
|
+
slug: string,
|
|
64
|
+
locale: string,
|
|
65
|
+
getMdxSource: typeof defaultGetMdxSource,
|
|
66
|
+
): Promise<"LandingPage" | "BlogList"> {
|
|
67
|
+
if ((await getMdxSource("LandingPage", slug, locale)) !== null) return "LandingPage";
|
|
68
|
+
if ((await getMdxSource("BlogList", slug, locale)) !== null) return "BlogList";
|
|
69
|
+
return "LandingPage";
|
|
70
|
+
}
|
|
71
|
+
|
|
46
72
|
export async function generateMetadata({
|
|
47
73
|
params,
|
|
48
|
-
|
|
74
|
+
getMdxSource,
|
|
75
|
+
}: Pick<Props, "params" | "getMdxSource">): Promise<Metadata> {
|
|
49
76
|
const { locale, slug } = await params;
|
|
50
|
-
|
|
77
|
+
const load = getMdxSource ?? loadMdxSource;
|
|
78
|
+
const type = await resolveType(slug, locale, load);
|
|
79
|
+
return renderPageMetadata({ type, slug, locale });
|
|
51
80
|
}
|
|
52
81
|
|
|
53
82
|
export default async function Page({
|
|
@@ -55,6 +84,17 @@ export default async function Page({
|
|
|
55
84
|
getMdxSource,
|
|
56
85
|
}: Props): Promise<ReactElement> {
|
|
57
86
|
const { locale, slug } = await params;
|
|
87
|
+
const load = getMdxSource ?? loadMdxSource;
|
|
88
|
+
const type = await resolveType(slug, locale, load);
|
|
89
|
+
if (type === "BlogList") {
|
|
90
|
+
return renderPage({
|
|
91
|
+
type: "BlogList",
|
|
92
|
+
slug,
|
|
93
|
+
locale,
|
|
94
|
+
populatedDescriptionTestId: `${slug}-description`,
|
|
95
|
+
...(getMdxSource !== undefined && { getMdxSource }),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
58
98
|
return renderPage({
|
|
59
99
|
type: "LandingPage",
|
|
60
100
|
slug,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Book-a-call page (#6658 D5) — a BookCall at /book-a-call, reading the
|
|
2
|
+
// fixed slug `book-call` (the monorepo's own content already uses it). Its
|
|
3
|
+
// CTA points at /contact, or at /meet where a template carries scheduling. A
|
|
4
|
+
// module-less surface a template enables by naming it in its roster; with no
|
|
5
|
+
// content the route 404s. Mirrors use-cases/page.tsx: a sections-composed page
|
|
6
|
+
// renders its sections, a prose-only page its MDX body.
|
|
7
|
+
import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
|
|
8
|
+
import {
|
|
9
|
+
getMdxSource as loadMdxSource,
|
|
10
|
+
renderPage,
|
|
11
|
+
renderPageMetadata,
|
|
12
|
+
} from "@working-theory/page-renderer";
|
|
13
|
+
import type { Metadata } from "next";
|
|
14
|
+
import type { ReactElement } from "react";
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
params: Promise<{ locale: string }>;
|
|
18
|
+
getMdxSource?: typeof defaultGetMdxSource;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function generateMetadata({
|
|
22
|
+
params,
|
|
23
|
+
}: Pick<Props, "params">): Promise<Metadata> {
|
|
24
|
+
const { locale } = await params;
|
|
25
|
+
return renderPageMetadata({
|
|
26
|
+
type: "BookCall",
|
|
27
|
+
slug: "book-call",
|
|
28
|
+
locale,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default async function Page({
|
|
33
|
+
params,
|
|
34
|
+
getMdxSource,
|
|
35
|
+
}: Props): Promise<ReactElement> {
|
|
36
|
+
const { locale } = await params;
|
|
37
|
+
const source = await (getMdxSource ?? loadMdxSource)("BookCall", "book-call", locale);
|
|
38
|
+
const composed =
|
|
39
|
+
source !== null &&
|
|
40
|
+
Array.isArray((source.frontmatter as { sections?: unknown[] }).sections) &&
|
|
41
|
+
((source.frontmatter as { sections?: unknown[] }).sections?.length ?? 0) >
|
|
42
|
+
0;
|
|
43
|
+
return renderPage({
|
|
44
|
+
type: "BookCall",
|
|
45
|
+
slug: "book-call",
|
|
46
|
+
locale,
|
|
47
|
+
...(composed ? {} : { bodyMode: "mdx-body" as const }),
|
|
48
|
+
...(getMdxSource !== undefined && { getMdxSource }),
|
|
49
|
+
});
|
|
50
|
+
}
|