create-ailk 0.4.0 → 0.6.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 +55 -8
- package/component-catalog.json +12248 -0
- package/component-catalog.md +193 -68
- package/dist/cli.js +29 -1
- package/dist/derive-architecture.js +5 -2
- package/dist/lib/api-boot-assertion.d.ts +46 -0
- package/dist/lib/api-boot-assertion.js +70 -0
- package/dist/lib/extract-module-bundle.d.ts +50 -2
- package/dist/lib/extract-module-bundle.js +223 -15
- package/dist/lib/fetch-guard.d.ts +28 -0
- package/dist/lib/fetch-guard.js +101 -0
- package/dist/lib/fetch-module.js +4 -0
- package/dist/lib/fetch-template.js +4 -0
- package/dist/lib/import-graph.d.ts +29 -0
- package/dist/lib/import-graph.js +140 -0
- package/dist/module-architecture.d.ts +60 -8
- package/dist/module-architecture.js +587 -242
- package/dist/parse-args.js +6 -2
- package/dist/programmatic.d.ts +10 -0
- package/dist/programmatic.js +134 -19
- package/dist/site-chrome.d.ts +80 -0
- package/dist/site-chrome.js +135 -0
- package/dist/surfaces.d.ts +21 -6
- package/dist/surfaces.js +209 -36
- package/dist/template-manifest.d.ts +88 -3
- package/dist/template-manifest.js +281 -29
- package/dist/template-names.d.ts +1 -1
- package/dist/template-names.js +2 -0
- package/package.json +10 -2
- package/site-templates/_base/ailk-template.json +6 -0
- package/site-templates/_base/content/en/Pricing/pricing.mdx +169 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +351 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +400 -0
- package/site-templates/marketing-site/ailk-template.json +11 -0
- package/site-templates/marketing-site/content/en/blog/05-this-vc-morning-routine-will-make-you-super-annoying.mdx +36 -0
- package/site-templates/marketing-site/provenance.json +4 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +288 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +286 -0
- package/site-templates/marketing-site-free/ailk-template.json +2 -1
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +279 -0
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +336 -0
- package/site-templates/minimalism-site/ailk-template.json +22 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-1.svg +7 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-2.svg +7 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-3.svg +7 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-4.svg +7 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-5.svg +7 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-6.svg +7 -0
- package/site-templates/minimalism-site/assets/hero-backdrop.svg +17 -0
- package/site-templates/minimalism-site/assets/logo-placeholder-1.svg +6 -0
- package/site-templates/minimalism-site/assets/logo-placeholder-2.svg +6 -0
- package/site-templates/minimalism-site/assets/logo-placeholder-3.svg +6 -0
- package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
- package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +110 -0
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +68 -0
- package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +57 -0
- package/site-templates/minimalism-site/content/en/HomePage/home.mdx +244 -0
- package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +77 -0
- package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +73 -0
- package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +52 -0
- package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +52 -0
- package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +52 -0
- package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +52 -0
- package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +73 -0
- package/site-templates/minimalism-site/flows/referral.flow.json +71 -0
- package/site-templates/minimalism-site/provenance.json +4 -0
- package/site-templates/saas-product/TEMPLATE-MANIFEST.md +61 -0
- package/site-templates/saas-product/ailk-template.json +16 -0
- package/site-templates/saas-product/provenance.json +10 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
- package/site-templates/simple-service/ailk-template.json +10 -0
- package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
- package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
- package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/simple-service/provenance.json +4 -0
- package/templates/.claude/skills/README.md +16 -25
- package/templates/.env.example +102 -12
- package/templates/.github/workflows/ci.yml +48 -0
- package/templates/README.md +3 -1
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
- package/templates/apps/api/.env.example +13 -0
- package/templates/apps/api/CLAUDE.md +29 -2
- package/templates/apps/api/eslint.config.mjs +69 -0
- package/templates/apps/api/jest.config.cjs +3 -0
- package/templates/apps/api/package.json +7 -2
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +89 -9
- package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +167 -0
- package/templates/apps/api/src/__tests__/server.test.ts +471 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +10 -1
- package/templates/apps/api/src/config/index.ts +62 -0
- package/templates/apps/api/src/config/modules.ts +7 -3
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -2
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
- package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
- package/templates/apps/api/src/lib/content-adapter.ts +57 -31
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
- package/templates/apps/api/src/lib/error-serializer.ts +23 -3
- package/templates/apps/api/src/lib/outbound-http.ts +7 -2
- package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
- package/templates/apps/api/src/lib/site.ts +208 -9
- package/templates/apps/api/src/lib/stripe.ts +66 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +521 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +150 -0
- package/templates/apps/api/src/middleware/default-tenant.ts +136 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
- package/templates/apps/api/src/openapi/spec.ts +543 -2
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
- package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
- package/templates/apps/api/src/routes/auth/user.ts +61 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
- package/templates/apps/api/src/routes/checkout/index.ts +34 -0
- package/templates/apps/api/src/routes/checkout/session.ts +139 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
- package/templates/apps/api/src/routes/content/get-page.ts +94 -12
- package/templates/apps/api/src/routes/content/index.ts +13 -73
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
- package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
- package/templates/apps/api/src/routes/content/search.ts +129 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +171 -0
- package/templates/apps/api/src/routes/leads/index.ts +100 -13
- package/templates/apps/api/src/routes/leads/list.ts +104 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
- package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
- package/templates/apps/api/src/routes/requests/index.ts +54 -0
- package/templates/apps/api/src/routes/webhooks/README.md +265 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
- package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
- package/templates/apps/api/src/server.ts +521 -55
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +408 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +329 -0
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
- package/templates/apps/api/src/services/abandon-email.ts +8 -2
- package/templates/apps/api/src/services/aeo-score.ts +49 -12
- package/templates/apps/api/src/services/audit-log.ts +668 -0
- package/templates/apps/api/src/services/consent.ts +18 -0
- package/templates/apps/api/src/services/default-tenant.ts +300 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
- package/templates/apps/api/src/services/dunning-policy.ts +294 -0
- package/templates/apps/api/src/services/entitlements.ts +80 -0
- package/templates/apps/api/src/services/lead-promotion.ts +160 -0
- package/templates/apps/api/src/services/lead-routing.ts +161 -2
- package/templates/apps/api/src/services/leads-inbox.ts +284 -0
- package/templates/apps/api/src/services/listing-config.ts +27 -9
- package/templates/apps/api/src/services/owner-workspaces.ts +132 -0
- package/templates/apps/api/src/services/project-listings.ts +201 -6
- package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
- package/templates/apps/api/src/services/request-confirm.ts +646 -0
- package/templates/apps/api/src/services/revalidation.ts +173 -0
- package/templates/apps/api/src/services/verified-email.ts +71 -0
- package/templates/apps/api/src/vercel-handler.ts +75 -35
- package/templates/apps/mcp/.env.example +17 -0
- package/templates/apps/mcp/CLAUDE.md +58 -25
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
- package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
- package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
- package/templates/apps/mcp/__tests__/index.test.ts +39 -0
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
- package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
- package/templates/apps/mcp/__tests__/parity.test.ts +12 -9
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
- package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
- package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
- package/templates/apps/mcp/__tests__/server.test.ts +67 -14
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
- package/templates/apps/mcp/__tests__/tools/{scaffold.test.ts → create_project.test.ts} +23 -23
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
- package/templates/apps/mcp/eslint.config.mjs +69 -0
- package/templates/apps/mcp/jest.config.cjs +13 -0
- package/templates/apps/mcp/package.json +8 -2
- package/templates/apps/mcp/src/abuse/index.ts +159 -24
- package/templates/apps/mcp/src/config/modules.ts +10 -5
- package/templates/apps/mcp/src/http-transport.ts +102 -29
- package/templates/apps/mcp/src/index.ts +73 -11
- package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
- package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
- package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
- package/templates/apps/mcp/src/lib/prisma.ts +39 -0
- package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
- package/templates/apps/mcp/src/server.ts +61 -37
- package/templates/apps/mcp/src/tools/audit.ts +26 -2
- package/templates/apps/mcp/src/tools/{scaffold.ts → create_project.ts} +3 -3
- package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
- package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
- package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
- package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
- package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
- package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
- package/templates/apps/mcp/src/tools/index.ts +9 -7
- package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
- package/templates/apps/mcp/src/tools/purchase.ts +128 -45
- package/templates/apps/mcp/src/tools/read_content.ts +20 -6
- package/templates/apps/mcp/src/tools/search_content.ts +11 -4
- package/templates/apps/web/.env.example +74 -8
- package/templates/apps/web/CLAUDE.md +46 -0
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
- package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +67 -14
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +19 -2
- package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +181 -0
- package/templates/apps/web/app/[locale]/(authed)/leads/components/LeadsTable.tsx +137 -0
- package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +111 -0
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +1 -1
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +111 -0
- package/templates/apps/web/app/[locale]/[slug]/page.tsx +65 -0
- package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +6 -5
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +1 -1
- package/templates/apps/web/app/[locale]/layout.tsx +308 -65
- package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
- package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
- package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
- package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
- package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
- package/templates/apps/web/app/api/revalidate/route.ts +194 -0
- package/templates/apps/web/auth.ts +407 -8
- package/templates/apps/web/components/google-analytics.tsx +18 -1
- package/templates/apps/web/eslint.config.mjs +69 -0
- package/templates/apps/web/jest.config.cjs +15 -19
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +50 -6
- package/templates/apps/web/lib/auth-email.ts +318 -0
- package/templates/apps/web/lib/auth-env.ts +85 -0
- package/templates/apps/web/lib/auth-routes.ts +55 -0
- package/templates/apps/web/lib/meeting-slugs.ts +53 -0
- package/templates/apps/web/lib/nav-config.ts +27 -7
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/lib/site-chrome.ts +29 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +50 -0
- package/templates/apps/web/messages/en.json +216 -2
- package/templates/apps/web/next.config.mjs +20 -0
- package/templates/apps/web/package.json +8 -3
- package/templates/apps/web/sentry.client.config.ts +131 -0
- package/templates/apps/web/sentry.edge.config.ts +72 -0
- package/templates/apps/web/sentry.server.config.ts +72 -0
- package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
- package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
- package/templates/apps/web/src/lib/routes.ts +13 -18
- package/templates/brand_assets/README.md +24 -0
- package/templates/content/en/AboutPage/about.mdx +160 -15
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
- package/templates/content/en/ContactPage/contact.mdx +90 -13
- package/templates/content/en/FAQPage/faq.mdx +62 -22
- package/templates/content/en/HomePage/home.mdx +244 -20
- package/templates/content/en/LandingPage/blog.mdx +83 -0
- package/templates/content/en/LandingPage/features.mdx +103 -0
- package/templates/content/en/LandingPage/how-it-works.mdx +143 -0
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
- package/templates/database/CHANGELOG.md +149 -0
- package/templates/database/README.md +10 -3
- package/templates/database/__tests__/appointment.db.test.ts +106 -0
- package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
- package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
- package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
- package/templates/database/__tests__/seed.test.ts +1 -1
- package/templates/database/__tests__/tenancy.test.ts +141 -0
- package/templates/database/config/schema.prisma +79 -2
- package/templates/database/content/schema.prisma +87 -11
- package/templates/database/eslint.config.mjs +69 -0
- package/templates/database/inbox/schema.prisma +452 -9
- package/templates/database/jest.config.cjs +5 -3
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
- package/templates/database/package.json +7 -3
- package/templates/database/seed.ts +1 -1
- package/templates/database/tenancy.ts +86 -1
- package/templates/database/tsconfig.json +1 -0
- package/templates/package.json +1 -14
- package/versions.json +52 -0
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -103
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
- package/templates/apps/api/src/routes/content/create.ts +0 -115
- package/templates/apps/api/src/routes/content/delete.ts +0 -82
- package/templates/apps/api/src/routes/content/update.ts +0 -112
- package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
- package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
- package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
- package/templates/apps/mcp/src/tools/create_page.ts +0 -96
- package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
- package/templates/apps/mcp/src/tools/update_page.ts +0 -92
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
- /package/site-templates/{marketing-site-free → _base}/content/en/PrivacyPage/privacy.mdx +0 -0
- /package/site-templates/{marketing-site-free → _base}/content/en/TermsPage/terms.mdx +0 -0
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
- /package/templates/apps/web/{src/components → components}/SiteNav.tsx +0 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// Chrome extract — Nav + Footer from @working-theory/ui (#1451)
|
|
5
5
|
// Shell/Chrome composition — Nav + Footer wrap via @working-theory/templates MarketingShell (#2820)
|
|
6
6
|
// Session-aware Nav actions — Sign in / Account + Sign out from the server-read session (#4984)
|
|
7
|
+
// Auth route group — the marketing chrome is dropped on the five (auth) paths (#5963)
|
|
7
8
|
// Site-declared theme — `_site.mdx` `theme:` block → token overlay → CSS vars
|
|
8
9
|
//
|
|
9
10
|
// Sits under app/[locale]/ and sets the <html lang> attribute to the
|
|
@@ -25,8 +26,10 @@ import "../globals.css";
|
|
|
25
26
|
import { Telemetry, VerificationMeta } from "@working-theory/analytics";
|
|
26
27
|
import { MarketingShell } from "@working-theory/templates/shells";
|
|
27
28
|
import { themeToCssVarsBothModes } from "@working-theory/theme";
|
|
28
|
-
import { isRtl } from "@working-theory/ui";
|
|
29
|
+
import { Link, isRtl, localeHref } from "@working-theory/ui";
|
|
30
|
+
import { LocaleSwitcherNav } from "@working-theory/ui/blocks";
|
|
29
31
|
import {
|
|
32
|
+
Banner,
|
|
30
33
|
Footer,
|
|
31
34
|
Nav,
|
|
32
35
|
type NavAction,
|
|
@@ -46,9 +49,11 @@ import type { ReactElement, ReactNode } from "react";
|
|
|
46
49
|
import { auth } from "../../auth";
|
|
47
50
|
import { GoogleAnalytics } from "../../components/google-analytics";
|
|
48
51
|
import { SignOutTrigger } from "../../components/sign-out-trigger";
|
|
52
|
+
import { isAuthPath } from "../../lib/auth-routes";
|
|
49
53
|
import { navItems } from "../../lib/nav-config";
|
|
50
54
|
import { getOgImageUrl } from "../../lib/og";
|
|
51
55
|
import { SITE_NAME, siteLogo } from "../../lib/site-brand";
|
|
56
|
+
import { siteChrome } from "../../lib/site-chrome";
|
|
52
57
|
import { loadSiteBrandTokens } from "../../lib/site-theme";
|
|
53
58
|
import { resolveBaseUrl } from "../../src/lib/routes";
|
|
54
59
|
|
|
@@ -57,6 +62,12 @@ import { resolveBaseUrl } from "../../src/lib/routes";
|
|
|
57
62
|
const DEFAULT_LOCALE = "en";
|
|
58
63
|
const THEME_COOKIE_NAME =
|
|
59
64
|
process.env.NEXT_PUBLIC_THEME_COOKIE_NAME ?? "ailk-theme";
|
|
65
|
+
/**
|
|
66
|
+
* The cookie the consent island persists the visitor's choice under (#5557).
|
|
67
|
+
* Passed to Banner explicitly so the server-side read below and the island's
|
|
68
|
+
* client-side write name the same cookie by construction.
|
|
69
|
+
*/
|
|
70
|
+
const CONSENT_COOKIE_NAME = "ailk-consent";
|
|
60
71
|
|
|
61
72
|
type ThemeMode = "light" | "dark";
|
|
62
73
|
|
|
@@ -73,7 +84,9 @@ type ThemeMode = "light" | "dark";
|
|
|
73
84
|
* has to satisfy the type: one minimal fields-step and a dead `action`.
|
|
74
85
|
*/
|
|
75
86
|
const NAV_CLIENT_ACTION_FLOW: NavActionFlow["flow"] = {
|
|
76
|
-
steps: [
|
|
87
|
+
steps: [
|
|
88
|
+
{ id: "noop", fields: [{ name: "noop", label: "noop", type: "text" }] },
|
|
89
|
+
],
|
|
77
90
|
action: "#",
|
|
78
91
|
};
|
|
79
92
|
|
|
@@ -100,6 +113,26 @@ async function resolveThemeMode(): Promise<ThemeMode> {
|
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Whether this visitor has already accepted or declined cookies (#5557).
|
|
118
|
+
*
|
|
119
|
+
* Read on the server, not left to the island alone: `BannerConsentIsland`
|
|
120
|
+
* initialises its `consented` state from `document.cookie`, which makes the
|
|
121
|
+
* CLIENT render null while the SERVER (no `document`) has already streamed
|
|
122
|
+
* the banner — React then leaves that server markup in place, un-hydrated, so
|
|
123
|
+
* a returning visitor sees a dead banner whose buttons do nothing. Deciding
|
|
124
|
+
* here means a decided visitor gets no banner markup at all, and a fresh one
|
|
125
|
+
* gets the live island that hides itself on Accept / Reject.
|
|
126
|
+
*/
|
|
127
|
+
async function hasConsentChoice(): Promise<boolean> {
|
|
128
|
+
try {
|
|
129
|
+
const cookieStore = await cookies();
|
|
130
|
+
return cookieStore.get(CONSENT_COOKIE_NAME)?.value !== undefined;
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
103
136
|
/**
|
|
104
137
|
* Blocking inline script that runs synchronously before any styled element.
|
|
105
138
|
*
|
|
@@ -177,6 +210,7 @@ export default async function LocaleLayout({
|
|
|
177
210
|
const { locale } = await params;
|
|
178
211
|
const messages = await getMessages();
|
|
179
212
|
const themeMode = await resolveThemeMode();
|
|
213
|
+
const consentDecided = await hasConsentChoice();
|
|
180
214
|
// Server-side session read (#4984) — the same `auth.api.getSession` idiom
|
|
181
215
|
// the `(authed)` segment guard uses. Null when signed out or auth is unset.
|
|
182
216
|
const session = await auth.api.getSession({ headers: await headers() });
|
|
@@ -187,12 +221,26 @@ export default async function LocaleLayout({
|
|
|
187
221
|
const navLabels = {
|
|
188
222
|
items: {
|
|
189
223
|
home: tNav("home"),
|
|
224
|
+
// The Free marketing-site chrome (#5469 D5): the Product flyout's three
|
|
225
|
+
// entries + descriptions, the top-level items, and the Get started CTA.
|
|
226
|
+
product: tNav("product"),
|
|
227
|
+
howItWorks: tNav("howItWorks"),
|
|
228
|
+
howItWorksDesc: tNav("howItWorksDesc"),
|
|
229
|
+
features: tNav("features"),
|
|
230
|
+
featuresDesc: tNav("featuresDesc"),
|
|
231
|
+
faq: tNav("faq"),
|
|
232
|
+
faqDesc: tNav("faqDesc"),
|
|
233
|
+
pricing: tNav("pricing"),
|
|
234
|
+
about: tNav("about"),
|
|
235
|
+
getStarted: tNav("getStarted"),
|
|
190
236
|
docs: tNav("docs"),
|
|
191
237
|
blog: tNav("blog"),
|
|
192
|
-
// The scheduling module's booking page (#4633
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
// `
|
|
238
|
+
// The scheduling module's booking page, /meet (#4633; renamed from the
|
|
239
|
+
// schedule/ route by #5563 — the label key itself keeps its name, D1
|
|
240
|
+
// R2: it names copy, not a route). Present in the map unconditionally,
|
|
241
|
+
// like `docs` and `blog`: the map is a label lookup, and an entry for
|
|
242
|
+
// a surface this site did not scaffold is never reached — `nav-config.ts`
|
|
243
|
+
// only lists surfaces the declared architecture carries.
|
|
196
244
|
schedule: tNav("schedule"),
|
|
197
245
|
// Session-aware action labels (#4984). `account` carries the signed-in
|
|
198
246
|
// user's display name (name, else email) so the Nav shows WHO is signed
|
|
@@ -207,8 +255,13 @@ export default async function LocaleLayout({
|
|
|
207
255
|
closeMenu: tNav("closeMenu"),
|
|
208
256
|
};
|
|
209
257
|
const navOverflowLabel = tNav("moreLinks");
|
|
258
|
+
// Cookie-consent copy (#5557). The `consent` namespace ships in every
|
|
259
|
+
// scaffold's messages file; the Banner below is UI-only — OSS ships no
|
|
260
|
+
// enforcement, the callbacks are the hook for @working-theory/consent.
|
|
261
|
+
const tConsent = await getTranslations({ locale, namespace: "consent" });
|
|
210
262
|
|
|
211
|
-
// Right-side action cluster (#4984). Signed out →
|
|
263
|
+
// Right-side action cluster (#4984). Signed out → the Free template's pair
|
|
264
|
+
// (#5469 D5): a primary "Get started" → /contact beside the outline-strong
|
|
212
265
|
// "Sign in" link (the operator's auth-pair idiom, #4370, MINUS Sign up —
|
|
213
266
|
// this site offers no sign-up flow). Signed in → the user's name (a
|
|
214
267
|
// non-navigating label) + an outline-strong "Sign out"; both authored as
|
|
@@ -224,7 +277,10 @@ export default async function LocaleLayout({
|
|
|
224
277
|
variant: "outline-strong",
|
|
225
278
|
},
|
|
226
279
|
]
|
|
227
|
-
: [
|
|
280
|
+
: [
|
|
281
|
+
{ labelKey: "getStarted", path: "/contact", variant: "primary" },
|
|
282
|
+
{ labelKey: "signIn", path: "/sign-in", variant: "outline-strong" },
|
|
283
|
+
];
|
|
228
284
|
|
|
229
285
|
// Only compose the flow seam when there is a flow action to compose. This
|
|
230
286
|
// layout is a Server Component and `renderFlowTrigger` is a function, so
|
|
@@ -237,8 +293,79 @@ export default async function LocaleLayout({
|
|
|
237
293
|
// this is the caller half of that contract.
|
|
238
294
|
const hasFlowAction = navActions.some((action) => action.kind === "flow");
|
|
239
295
|
|
|
240
|
-
// Shared brand mark injected into both Nav and Footer chrome slots.
|
|
296
|
+
// Shared brand mark injected into both Nav and Footer chrome slots. The
|
|
297
|
+
// mark comes from site-brand.tsx (the light/dark pair the /sign-in card
|
|
298
|
+
// also renders); the Nav adds the shipped wordmark beside it so BrandLogo
|
|
299
|
+
// composes mark + wordmark on desktop and mark-only below `md` (#5469 D5).
|
|
241
300
|
const brandLogo = siteLogo();
|
|
301
|
+
const brandWordmark = {
|
|
302
|
+
src: "/wordmark.svg",
|
|
303
|
+
alt: SITE_NAME,
|
|
304
|
+
width: 140,
|
|
305
|
+
height: 16,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// The locales the footer's language switcher offers — the same list the
|
|
309
|
+
// middleware and hreflang alternates read (NEXT_PUBLIC_ENABLED_LOCALES).
|
|
310
|
+
const enabledLocales = readLocales();
|
|
311
|
+
|
|
312
|
+
// The announcement bar is HOME-ONLY (#5469 D5): its route gate matches the
|
|
313
|
+
// pathname literally, and under next-intl's `as-needed` prefixing the home
|
|
314
|
+
// path is `/` for the default locale and `/<locale>` for every other one.
|
|
315
|
+
const homePath = locale === DEFAULT_LOCALE ? "/" : `/${locale}`;
|
|
316
|
+
|
|
317
|
+
// Deck home (#5612 D3): `siteChrome.home === "deck"` only ever applies
|
|
318
|
+
// `snap="mandatory"` + `deckHero` on the locale-root route — every other
|
|
319
|
+
// path renders byte-identically regardless of the knob. `x-pathname` is the
|
|
320
|
+
// same header `generateMetadata` above reads (forwarded by middleware.ts);
|
|
321
|
+
// Next.js's `headers()` is request-scoped and cheap to call again here.
|
|
322
|
+
const pathname = (await headers()).get("x-pathname") ?? homePath;
|
|
323
|
+
const isDeckHome = siteChrome.home === "deck" && pathname === homePath;
|
|
324
|
+
|
|
325
|
+
// The (auth) route group's chrome branch (#5963).
|
|
326
|
+
//
|
|
327
|
+
// A route group's layout nests INSIDE its parent segment's layout, so
|
|
328
|
+
// `(auth)/layout.tsx` renders BETWEEN the Nav and the Footer below — a group
|
|
329
|
+
// changes which siblings share a layout, it does not let a child escape an
|
|
330
|
+
// ancestor. The only place the marketing frame can be dropped for those five
|
|
331
|
+
// paths is here, where it is composed.
|
|
332
|
+
//
|
|
333
|
+
// Fail-safe direction: `isAuthPath()` answers false for an absent
|
|
334
|
+
// `x-pathname`, so a missing header can never strip a marketing page's
|
|
335
|
+
// chrome; the worst case is an auth page rendered WITH chrome, which the
|
|
336
|
+
// group's own suite catches. The marketing tree is NOT restructured into a
|
|
337
|
+
// `(marketing)` group — a ~35-directory move for a five-page delta (spec D1).
|
|
338
|
+
const isAuthRoute = isAuthPath(pathname, enabledLocales);
|
|
339
|
+
|
|
340
|
+
// Placeholder social handles + copyright (#5469 D5): `{{COMPANY_HANDLE}}` /
|
|
341
|
+
// `{{COMPANY_NAME}}` are create-ailk markers substituted at scaffold time
|
|
342
|
+
// (packages/create-ailk/src/programmatic.ts), so a customer's site never
|
|
343
|
+
// carries the kit vendor's own identity here. The year is computed, never
|
|
344
|
+
// typed, so the placeholder does not go stale.
|
|
345
|
+
const socialLinks = [
|
|
346
|
+
{ platform: "facebook", href: "https://facebook.com/{{COMPANY_HANDLE}}" },
|
|
347
|
+
{ platform: "instagram", href: "https://instagram.com/{{COMPANY_HANDLE}}" },
|
|
348
|
+
{ platform: "x", href: "https://x.com/{{COMPANY_HANDLE}}" },
|
|
349
|
+
{
|
|
350
|
+
platform: "linkedin",
|
|
351
|
+
href: "https://linkedin.com/company/{{COMPANY_HANDLE}}",
|
|
352
|
+
},
|
|
353
|
+
{ platform: "youtube", href: "https://youtube.com/@{{COMPANY_HANDLE}}" },
|
|
354
|
+
] as const;
|
|
355
|
+
const copyrightText = `© ${new Date().getFullYear()} {{COMPANY_NAME}}`;
|
|
356
|
+
|
|
357
|
+
// Footer legal links (#5612 D3). The centered variant folds in FAQ — the
|
|
358
|
+
// roster's third always-scaffolded core route (`/faq`, alongside `/privacy`
|
|
359
|
+
// and `/terms`) — since a mark-only nav (its usual pairing, #5589) has
|
|
360
|
+
// nowhere else on the page linking it. The default `columns` footer keeps
|
|
361
|
+
// exactly today's two-entry list, byte-identical.
|
|
362
|
+
const legalLinks = [
|
|
363
|
+
{ label: "Privacy Policy", href: "/privacy" },
|
|
364
|
+
{ label: "Terms of Service", href: "/terms" },
|
|
365
|
+
...(siteChrome.footer === "centered"
|
|
366
|
+
? [{ label: navLabels.items.faq, href: "/faq" }]
|
|
367
|
+
: []),
|
|
368
|
+
];
|
|
242
369
|
|
|
243
370
|
// The site's own declared colours, read from `content/_site.mdx`'s `theme:`
|
|
244
371
|
// block and translated from that customer-facing vocabulary onto a token
|
|
@@ -257,7 +384,11 @@ export default async function LocaleLayout({
|
|
|
257
384
|
);
|
|
258
385
|
|
|
259
386
|
return (
|
|
260
|
-
<html
|
|
387
|
+
<html
|
|
388
|
+
lang={locale}
|
|
389
|
+
dir={isRtl(locale) ? "rtl" : "ltr"}
|
|
390
|
+
data-theme={themeMode}
|
|
391
|
+
>
|
|
261
392
|
<head>
|
|
262
393
|
{/* Flash-prevention: runs synchronously before any styled element */}
|
|
263
394
|
<script
|
|
@@ -271,10 +402,24 @@ export default async function LocaleLayout({
|
|
|
271
402
|
/>
|
|
272
403
|
{/* Favicons — single brand mark (theme-independent), PNG fallbacks, ICO legacy */}
|
|
273
404
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
274
|
-
<link
|
|
275
|
-
|
|
405
|
+
<link
|
|
406
|
+
rel="icon"
|
|
407
|
+
type="image/png"
|
|
408
|
+
sizes="32x32"
|
|
409
|
+
href="/favicon-32x32.png"
|
|
410
|
+
/>
|
|
411
|
+
<link
|
|
412
|
+
rel="icon"
|
|
413
|
+
type="image/png"
|
|
414
|
+
sizes="16x16"
|
|
415
|
+
href="/favicon-16x16.png"
|
|
416
|
+
/>
|
|
276
417
|
<link rel="shortcut icon" href="/favicon.ico" />
|
|
277
|
-
<link
|
|
418
|
+
<link
|
|
419
|
+
rel="apple-touch-icon"
|
|
420
|
+
sizes="180x180"
|
|
421
|
+
href="/apple-touch-icon.png"
|
|
422
|
+
/>
|
|
278
423
|
<link rel="manifest" href="/site.webmanifest" />
|
|
279
424
|
<GoogleAnalytics />
|
|
280
425
|
<VerificationMeta />
|
|
@@ -292,58 +437,156 @@ export default async function LocaleLayout({
|
|
|
292
437
|
* emitted unconditionally in OSS forks; paying customers install
|
|
293
438
|
* @working-theory/branding and gate it in their own layout.
|
|
294
439
|
*/}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
440
|
+
{isAuthRoute ? (
|
|
441
|
+
/*
|
|
442
|
+
* The (auth) branch (#5963): the five auth paths render their own
|
|
443
|
+
* `(auth)/layout.tsx` — AuthShell — with no Nav and no marketing
|
|
444
|
+
* Footer above or below it. Everything outside this ternary (the
|
|
445
|
+
* document, the flash-prevention script, the theme vars, the
|
|
446
|
+
* favicons, NextIntlClientProvider, the consent Banner and
|
|
447
|
+
* Telemetry) is shared by both branches and written once.
|
|
448
|
+
*/
|
|
449
|
+
children
|
|
450
|
+
) : (
|
|
451
|
+
<MarketingShell
|
|
452
|
+
contentOwnsMain
|
|
453
|
+
gutters="plain"
|
|
454
|
+
bannerPlacement="above-nav"
|
|
455
|
+
{...(siteChrome.announcementBanner
|
|
456
|
+
? {
|
|
457
|
+
banner: (
|
|
458
|
+
<Banner
|
|
459
|
+
variant="contrast"
|
|
460
|
+
placement="above-nav"
|
|
461
|
+
message="Medium length banner heading with link goes here"
|
|
462
|
+
action={{
|
|
463
|
+
label: "Link",
|
|
464
|
+
href: "/pricing",
|
|
465
|
+
kind: "link",
|
|
466
|
+
}}
|
|
467
|
+
routes={{ include: [homePath] }}
|
|
468
|
+
dismissible={false}
|
|
469
|
+
locale={locale}
|
|
470
|
+
/>
|
|
471
|
+
),
|
|
472
|
+
}
|
|
473
|
+
: {})}
|
|
474
|
+
{...(isDeckHome
|
|
475
|
+
? { snap: "mandatory" as const, deckHero: true }
|
|
476
|
+
: {})}
|
|
477
|
+
nav={
|
|
478
|
+
<Nav
|
|
479
|
+
locale={locale}
|
|
480
|
+
labels={navLabels}
|
|
481
|
+
navItems={siteChrome.nav === "mark-only" ? [] : navItems}
|
|
482
|
+
// Deck home (#5697): the two forwards that join #5426 D4 to a
|
|
483
|
+
// real deck. `sticky` pins the bar — MarketingShell renders
|
|
484
|
+
// {nav} as a plain child of the scroll container, so under
|
|
485
|
+
// `scroll-snap-type: y mandatory` the browser snaps slide 1 to
|
|
486
|
+
// the top on load and the unpinned bar is gone before the
|
|
487
|
+
// reader touches anything. `deckMorph` is what puts BrandLogo's
|
|
488
|
+
// `.ailk-deck-brand-mark` / `.ailk-deck-brand-wordmark` hooks in
|
|
489
|
+
// the document for the D4 CSS to key off; it requires both
|
|
490
|
+
// `logo` and `wordmark` (Nav.tsx:344), which the layout already
|
|
491
|
+
// passes above. Scoped to the SAME `isDeckHome` gate
|
|
492
|
+
// MarketingShell's snap/deckHero use, so a non-deck route's
|
|
493
|
+
// render is byte-identical to before this branch existed.
|
|
494
|
+
{...(isDeckHome ? { sticky: true, deckMorph: true } : {})}
|
|
495
|
+
siteName={SITE_NAME}
|
|
496
|
+
showThemeToggle={false}
|
|
497
|
+
logo={brandLogo}
|
|
498
|
+
wordmark={brandWordmark}
|
|
499
|
+
overflowLabel={navOverflowLabel}
|
|
500
|
+
actions={navActions}
|
|
501
|
+
{...(hasFlowAction
|
|
502
|
+
? {
|
|
503
|
+
renderFlowTrigger: (action, trigger) =>
|
|
504
|
+
action.labelKey === "signOut" ? (
|
|
505
|
+
<SignOutTrigger>{trigger}</SignOutTrigger>
|
|
506
|
+
) : (
|
|
507
|
+
<span
|
|
508
|
+
className="text-body-sm text-(--color-text-muted)"
|
|
509
|
+
data-testid="nav-account-name"
|
|
510
|
+
>
|
|
511
|
+
{navLabels.items.account}
|
|
512
|
+
</span>
|
|
513
|
+
),
|
|
514
|
+
}
|
|
515
|
+
: {})}
|
|
516
|
+
/>
|
|
517
|
+
}
|
|
518
|
+
footer={
|
|
519
|
+
<Footer
|
|
520
|
+
locale={locale}
|
|
521
|
+
itemLabels={navLabels.items}
|
|
522
|
+
navItems={navItems}
|
|
523
|
+
siteName={SITE_NAME}
|
|
524
|
+
logo={brandLogo}
|
|
525
|
+
social={[...socialLinks]}
|
|
526
|
+
{...(siteChrome.footer === "centered"
|
|
527
|
+
? { layout: "centered" as const }
|
|
528
|
+
: {})}
|
|
529
|
+
showThemeToggle
|
|
530
|
+
copyrightText={copyrightText}
|
|
531
|
+
legalLinks={legalLinks}
|
|
532
|
+
languageSwitcher={
|
|
533
|
+
<LocaleSwitcherNav
|
|
534
|
+
current={locale}
|
|
535
|
+
locales={enabledLocales}
|
|
536
|
+
pathname={pathname}
|
|
537
|
+
/>
|
|
538
|
+
}
|
|
539
|
+
// #4157 — deliberately left as a literal "Built by Working
|
|
540
|
+
// Theory Labs" / https://ailaunchkit.ai, not SITE_NAME: this credits
|
|
541
|
+
// the OSS TEMPLATE this site is built with (a "powered by"
|
|
542
|
+
// link, unconditional in OSS forks per the comment above),
|
|
543
|
+
// never the customer's own identity, so it isn't the vendor-
|
|
544
|
+
// brand-as-default defect #4157 fixes. See
|
|
545
|
+
// docs/specs/4157-scaffold-brand-defaults-spec.md § Design
|
|
546
|
+
// Decisions for the fuller reasoning.
|
|
547
|
+
attribution={{
|
|
548
|
+
label: "Built by Working Theory Labs",
|
|
549
|
+
href: "https://ailaunchkit.ai",
|
|
550
|
+
}}
|
|
551
|
+
/>
|
|
552
|
+
}
|
|
553
|
+
>
|
|
554
|
+
{children}
|
|
555
|
+
</MarketingShell>
|
|
556
|
+
)}
|
|
557
|
+
{/*
|
|
558
|
+
* Cookie consent (#5557). Bottom-pinned `info` Banner in consent
|
|
559
|
+
* mode: `secondaryAction` makes Banner compose BannerConsentIsland,
|
|
560
|
+
* which reads/writes the `ailk-consent` cookie itself — a fresh
|
|
561
|
+
* visitor sees it, an accepted/declined one does not, on every
|
|
562
|
+
* page. Out of the shell's flow (placement `bottom` is a fixed
|
|
563
|
+
* overlay), so accepting causes no layout shift.
|
|
564
|
+
*/}
|
|
565
|
+
{consentDecided ? null : (
|
|
566
|
+
<Banner
|
|
567
|
+
variant="info"
|
|
568
|
+
placement="bottom"
|
|
569
|
+
consentCookieName={CONSENT_COOKIE_NAME}
|
|
570
|
+
title={tConsent("title")}
|
|
571
|
+
message={
|
|
572
|
+
<>
|
|
573
|
+
{tConsent("body")}{" "}
|
|
574
|
+
<Link
|
|
575
|
+
href={localeHref(locale, "/privacy")}
|
|
576
|
+
variant="unstyled"
|
|
577
|
+
className="font-semibold text-current underline underline-offset-4"
|
|
578
|
+
>
|
|
579
|
+
{tConsent("policyLinkLabel")}
|
|
580
|
+
</Link>
|
|
581
|
+
</>
|
|
582
|
+
}
|
|
583
|
+
dismissible={false}
|
|
584
|
+
acceptLabel={tConsent("acceptLabel")}
|
|
585
|
+
secondaryAction={{ label: tConsent("rejectLabel") }}
|
|
586
|
+
rejectLabel={tConsent("rejectLabel")}
|
|
587
|
+
locale={locale}
|
|
588
|
+
/>
|
|
589
|
+
)}
|
|
347
590
|
<Telemetry />
|
|
348
591
|
</NextIntlClientProvider>
|
|
349
592
|
</body>
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for /[locale]/purchase/cancel (#6084).
|
|
3
|
+
*
|
|
4
|
+
* D4 — reads nothing. AC-2 — states nothing was charged, links to
|
|
5
|
+
* `/services`, noindex, every string from the `purchase` namespace.
|
|
6
|
+
*/
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { render } from "@testing-library/react";
|
|
9
|
+
|
|
10
|
+
jest.mock("next/navigation", () => ({
|
|
11
|
+
__esModule: true,
|
|
12
|
+
notFound: jest.fn(() => {
|
|
13
|
+
throw new Error("NEXT_NOT_FOUND");
|
|
14
|
+
}),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const requestedKeys: string[] = [];
|
|
18
|
+
jest.mock("next-intl/server", () => ({
|
|
19
|
+
__esModule: true,
|
|
20
|
+
getTranslations: jest.fn().mockResolvedValue((key: string) => {
|
|
21
|
+
requestedKeys.push(key);
|
|
22
|
+
return `copy:${key}`;
|
|
23
|
+
}),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const capturedSections: unknown[] = [];
|
|
27
|
+
jest.mock("@working-theory/ui/sections", () => ({
|
|
28
|
+
__esModule: true,
|
|
29
|
+
SectionRenderer: ({ sections }: { sections: unknown[] }) => {
|
|
30
|
+
capturedSections.splice(0, capturedSections.length, ...sections);
|
|
31
|
+
return React.createElement("div", {
|
|
32
|
+
"data-testid": "section-renderer",
|
|
33
|
+
"data-section-count": String(sections.length),
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const mockGetSession = jest.fn();
|
|
39
|
+
jest.mock("@working-theory/api-client", () => ({
|
|
40
|
+
__esModule: true,
|
|
41
|
+
checkout: { getSession: (...args: unknown[]) => mockGetSession(...args) },
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
// Mocks the REAL boundary `resolveCommerceClaim()` itself calls
|
|
45
|
+
// (`checkLicense` from `@working-theory/license`), not the internal wrapper
|
|
46
|
+
// module — matching the eslint "no internal-module mocks" rule (#507).
|
|
47
|
+
const mockCheckLicense = jest.fn();
|
|
48
|
+
jest.mock("@working-theory/license", () => ({
|
|
49
|
+
__esModule: true,
|
|
50
|
+
checkLicense: (...args: unknown[]) => mockCheckLicense(...args),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// `../../../../src/lib/commerce-sections` wraps a lazy, webpack-ignored
|
|
54
|
+
// `import()` of the paid `@working-theory/ui-commerce` package — mocked
|
|
55
|
+
// directly, mirroring `../__tests__/success.test.tsx`'s identical mock.
|
|
56
|
+
const mockEnsureCommerceSectionsRegistered = jest.fn();
|
|
57
|
+
// eslint-disable-next-line no-restricted-syntax -- see the identical exception in success.test.tsx
|
|
58
|
+
jest.mock("../../../../src/lib/commerce-sections", () => ({
|
|
59
|
+
__esModule: true,
|
|
60
|
+
ensureCommerceSectionsRegistered: () =>
|
|
61
|
+
mockEnsureCommerceSectionsRegistered(),
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
import { notFound } from "next/navigation";
|
|
65
|
+
|
|
66
|
+
import Page, { generateMetadata } from "../cancel/page";
|
|
67
|
+
|
|
68
|
+
const mockNotFound = notFound as jest.MockedFunction<typeof notFound>;
|
|
69
|
+
|
|
70
|
+
function sectionTypes(): string[] {
|
|
71
|
+
return capturedSections.map((s) => (s as { type: string }).type);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function renderPage() {
|
|
75
|
+
const element = await Page({ params: Promise.resolve({ locale: "en" }) });
|
|
76
|
+
return render(element);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
describe("/purchase/cancel", () => {
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
capturedSections.length = 0;
|
|
82
|
+
requestedKeys.length = 0;
|
|
83
|
+
mockGetSession.mockReset();
|
|
84
|
+
mockCheckLicense.mockReset();
|
|
85
|
+
mockCheckLicense.mockResolvedValue({ revoked: false, features: ["commerce"] });
|
|
86
|
+
mockNotFound.mockClear();
|
|
87
|
+
mockEnsureCommerceSectionsRegistered.mockReset();
|
|
88
|
+
mockEnsureCommerceSectionsRegistered.mockResolvedValue(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("renders payment-receipt(outcome:cancel) → cta, with a primary link to /services (AC-2, D5)", async () => {
|
|
92
|
+
await renderPage();
|
|
93
|
+
|
|
94
|
+
expect(sectionTypes()).toEqual(["payment-receipt", "cta"]);
|
|
95
|
+
const receipt = capturedSections[0] as {
|
|
96
|
+
data: Record<string, unknown>;
|
|
97
|
+
};
|
|
98
|
+
expect(receipt.data.outcome).toBe("cancel");
|
|
99
|
+
const cta = capturedSections[1] as { data: Record<string, unknown> };
|
|
100
|
+
expect((cta.data.primaryCta as { href: string }).href).toBe("/services");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("falls back to content-block instead of payment-receipt when @working-theory/ui-commerce is not installed", async () => {
|
|
104
|
+
mockEnsureCommerceSectionsRegistered.mockResolvedValue(false);
|
|
105
|
+
|
|
106
|
+
await renderPage();
|
|
107
|
+
|
|
108
|
+
expect(sectionTypes()).toEqual(["content-block", "cta"]);
|
|
109
|
+
const fallback = capturedSections[0] as {
|
|
110
|
+
data: { headline: string; body: string };
|
|
111
|
+
};
|
|
112
|
+
expect(fallback.data.headline).toBe("copy:cancel.title");
|
|
113
|
+
expect(fallback.data.body).toContain("copy:cancel.message");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("reads NOTHING — no API call at all (D4)", async () => {
|
|
117
|
+
await renderPage();
|
|
118
|
+
expect(mockGetSession).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("takes every string from the purchase namespace (D4/AC-2)", async () => {
|
|
122
|
+
await renderPage();
|
|
123
|
+
expect(requestedKeys.length).toBeGreaterThan(0);
|
|
124
|
+
expect(requestedKeys.every((k) => k.startsWith("cancel."))).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("is not indexable", async () => {
|
|
128
|
+
const metadata = await generateMetadata({
|
|
129
|
+
params: Promise.resolve({ locale: "en" }),
|
|
130
|
+
});
|
|
131
|
+
expect(metadata.robots).toEqual({ index: false, follow: true });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("carries no data-outcome=success and no PaymentComplete string — a cancel is not a payment claim", async () => {
|
|
135
|
+
await renderPage();
|
|
136
|
+
const serialized = JSON.stringify(capturedSections);
|
|
137
|
+
expect(serialized).not.toContain('"outcome":"success"');
|
|
138
|
+
expect(serialized).not.toContain("PaymentComplete");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ── T-16 / D12 — the request-time commerce-claim gate ───────────────────
|
|
142
|
+
|
|
143
|
+
it("answers notFound() when the commerce claim is NOT held (D12)", async () => {
|
|
144
|
+
mockCheckLicense.mockResolvedValue({ revoked: false, features: [] });
|
|
145
|
+
|
|
146
|
+
await expect(renderPage()).rejects.toThrow("NEXT_NOT_FOUND");
|
|
147
|
+
expect(mockNotFound).toHaveBeenCalledTimes(1);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("fail-closed: answers notFound() when the licence read THROWS", async () => {
|
|
151
|
+
mockCheckLicense.mockRejectedValue(new Error("licence read blew up"));
|
|
152
|
+
|
|
153
|
+
await expect(renderPage()).rejects.toThrow();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("renders normally when the commerce claim IS held", async () => {
|
|
157
|
+
mockCheckLicense.mockResolvedValue({ revoked: false, features: ["commerce"] });
|
|
158
|
+
await renderPage();
|
|
159
|
+
expect(mockNotFound).not.toHaveBeenCalled();
|
|
160
|
+
expect(sectionTypes()).toEqual(["payment-receipt", "cta"]);
|
|
161
|
+
});
|
|
162
|
+
});
|