create-ailk 0.1.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/LICENSE +201 -0
- package/README.md +39 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +80 -0
- package/dist/copy.d.ts +8 -0
- package/dist/copy.js +109 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/lib/pro-callout.d.ts +12 -0
- package/dist/lib/pro-callout.js +20 -0
- package/dist/parse-args.d.ts +13 -0
- package/dist/parse-args.js +81 -0
- package/dist/programmatic.d.ts +49 -0
- package/dist/programmatic.js +89 -0
- package/dist/substitute.d.ts +31 -0
- package/dist/substitute.js +45 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.js +70 -0
- package/package.json +47 -0
- package/templates/.claude/.gitkeep +0 -0
- package/templates/.claude/agents/web.md +80 -0
- package/templates/.claude/rules/architecture.md +62 -0
- package/templates/.claude/rules/preserve-existing-style.md +51 -0
- package/templates/.claude/skills/README.md +43 -0
- package/templates/.claude/skills/add-schema/SKILL.md +270 -0
- package/templates/.claude/skills/add-schema/references/conflict-handling.md +200 -0
- package/templates/.claude/skills/add-schema/references/file-finding.md +123 -0
- package/templates/.claude/skills/add-schema/references/schema-types.md +131 -0
- package/templates/.claude/skills/audit-site/SKILL.md +213 -0
- package/templates/.claude/skills/audit-site/references/common-errors.md +113 -0
- package/templates/.claude/skills/audit-site/references/findings-glossary.md +138 -0
- package/templates/.claude/skills/audit-site/references/severity-levels.md +114 -0
- package/templates/.claude/skills/provision-config/SKILL.md +243 -0
- package/templates/.claude/skills/provision-config/references/env-vars-template.md +184 -0
- package/templates/.claude/skills/provision-config/references/runbook-template.md +248 -0
- package/templates/.claude/skills/provision-config/references/vercel-json-template.md +98 -0
- package/templates/.claude/skills/scaffold-auth/SKILL.md +261 -0
- package/templates/.claude/skills/scaffold-auth/references/env-vars.md +91 -0
- package/templates/.claude/skills/scaffold-auth/references/oauth-app-setup.md +171 -0
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +310 -0
- package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +71 -0
- package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +83 -0
- package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +72 -0
- package/templates/.claude/skills/scaffold-page/SKILL.md +243 -0
- package/templates/.claude/skills/scaffold-page/references/page-types.md +169 -0
- package/templates/.claude/skills/scaffold-page/templates/content.mdx.tmpl +28 -0
- package/templates/.claude/skills/scaffold-page/templates/route.tsx.tmpl +28 -0
- package/templates/.claude/skills/suggest-site-pages/SKILL.md +242 -0
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +287 -0
- package/templates/.env.example +205 -0
- package/templates/.gitkeep +2 -0
- package/templates/CLAUDE.md +142 -0
- package/templates/CONVENTIONS.md +222 -0
- package/templates/README.md +110 -0
- package/templates/apps/api/.env.example +40 -0
- package/templates/apps/api/CLAUDE.md +63 -0
- package/templates/apps/api/api/index.ts +144 -0
- package/templates/apps/api/jest.config.cjs +74 -0
- package/templates/apps/api/package.json +54 -0
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +138 -0
- package/templates/apps/api/src/__tests__/factories/index.ts +70 -0
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +82 -0
- package/templates/apps/api/src/__tests__/index.test.ts +118 -0
- package/templates/apps/api/src/__tests__/server.test.ts +207 -0
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +126 -0
- package/templates/apps/api/src/__tests__/setup.ts +24 -0
- package/templates/apps/api/src/cli.ts +30 -0
- package/templates/apps/api/src/config/__tests__/index.test.ts +97 -0
- package/templates/apps/api/src/config/index.ts +56 -0
- package/templates/apps/api/src/index.ts +32 -0
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +15 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +45 -0
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +178 -0
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +100 -0
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +94 -0
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +144 -0
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +8 -0
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +227 -0
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +125 -0
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +165 -0
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +129 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +74 -0
- package/templates/apps/api/src/lib/content-adapter.ts +86 -0
- package/templates/apps/api/src/lib/error-serializer.ts +77 -0
- package/templates/apps/api/src/lib/errors.ts +95 -0
- package/templates/apps/api/src/lib/file-fallback.ts +87 -0
- package/templates/apps/api/src/lib/lead-capture-store.ts +110 -0
- package/templates/apps/api/src/lib/locales.ts +201 -0
- package/templates/apps/api/src/lib/outbound-http.ts +122 -0
- package/templates/apps/api/src/lib/prisma.ts +21 -0
- package/templates/apps/api/src/lib/require-paid-tier.ts +53 -0
- package/templates/apps/api/src/lib/stripe.ts +30 -0
- package/templates/apps/api/src/lib/webhook-idempotency.ts +92 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +111 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +172 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +119 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +138 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +149 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +139 -0
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +175 -0
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +76 -0
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +153 -0
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +72 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +292 -0
- package/templates/apps/api/src/middleware/auth.ts +246 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +132 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +129 -0
- package/templates/apps/api/src/openapi/index.ts +12 -0
- package/templates/apps/api/src/openapi/spec.ts +408 -0
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +193 -0
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +201 -0
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +294 -0
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +486 -0
- package/templates/apps/api/src/routes/auth/tokens.ts +199 -0
- package/templates/apps/api/src/routes/auth/user.ts +244 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +588 -0
- package/templates/apps/api/src/routes/checkout/index.ts +13 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +185 -0
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +239 -0
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +201 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +141 -0
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +122 -0
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +44 -0
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +118 -0
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +51 -0
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +252 -0
- package/templates/apps/api/src/routes/content/create.ts +114 -0
- package/templates/apps/api/src/routes/content/delete.ts +81 -0
- package/templates/apps/api/src/routes/content/get-page.ts +98 -0
- package/templates/apps/api/src/routes/content/index.ts +32 -0
- package/templates/apps/api/src/routes/content/list-pages.ts +83 -0
- package/templates/apps/api/src/routes/content/list-types.ts +19 -0
- package/templates/apps/api/src/routes/content/rate-limit.ts +13 -0
- package/templates/apps/api/src/routes/content/slug.ts +9 -0
- package/templates/apps/api/src/routes/content/update.ts +111 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +415 -0
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +165 -0
- package/templates/apps/api/src/routes/leads/index.ts +130 -0
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +296 -0
- package/templates/apps/api/src/routes/test/auth/session.ts +141 -0
- package/templates/apps/api/src/routes/webhooks/README.md +57 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +247 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +198 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +633 -0
- package/templates/apps/api/src/routes/webhooks/index.ts +41 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +253 -0
- package/templates/apps/api/src/server.ts +157 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +395 -0
- package/templates/apps/api/src/services/enrichment-shim.ts +53 -0
- package/templates/apps/api/src/services/lead-routing.ts +338 -0
- package/templates/apps/api/tsconfig.json +34 -0
- package/templates/apps/api/vercel.json +17 -0
- package/templates/apps/mcp/.env.example +52 -0
- package/templates/apps/mcp/CLAUDE.md +84 -0
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +52 -0
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +46 -0
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +36 -0
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +54 -0
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +110 -0
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +213 -0
- package/templates/apps/mcp/__tests__/audit.test.ts +217 -0
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +52 -0
- package/templates/apps/mcp/__tests__/create_page.test.ts +201 -0
- package/templates/apps/mcp/__tests__/delete_page.test.ts +176 -0
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +82 -0
- package/templates/apps/mcp/__tests__/get_faq.test.ts +80 -0
- package/templates/apps/mcp/__tests__/get_locations.test.ts +65 -0
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +90 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +80 -0
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +81 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +323 -0
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +75 -0
- package/templates/apps/mcp/__tests__/lib/require-paid-tier.test.ts +75 -0
- package/templates/apps/mcp/__tests__/list_pages.test.ts +112 -0
- package/templates/apps/mcp/__tests__/locale.test.ts +135 -0
- package/templates/apps/mcp/__tests__/parity.test.ts +205 -0
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +34 -0
- package/templates/apps/mcp/__tests__/read_content.test.ts +153 -0
- package/templates/apps/mcp/__tests__/request_callback.test.ts +163 -0
- package/templates/apps/mcp/__tests__/search_content.test.ts +153 -0
- package/templates/apps/mcp/__tests__/server.test.ts +115 -0
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +165 -0
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +161 -0
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +227 -0
- package/templates/apps/mcp/__tests__/tools/scaffold.test.ts +237 -0
- package/templates/apps/mcp/__tests__/update_page.test.ts +195 -0
- package/templates/apps/mcp/jest.config.cjs +82 -0
- package/templates/apps/mcp/package.json +37 -0
- package/templates/apps/mcp/src/abuse/daily-cap.ts +47 -0
- package/templates/apps/mcp/src/abuse/email-validator.ts +25 -0
- package/templates/apps/mcp/src/abuse/honeypot.ts +21 -0
- package/templates/apps/mcp/src/abuse/index.ts +111 -0
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +58 -0
- package/templates/apps/mcp/src/http-transport.ts +303 -0
- package/templates/apps/mcp/src/index.ts +67 -0
- package/templates/apps/mcp/src/lib/audit-single-page.ts +194 -0
- package/templates/apps/mcp/src/lib/errors.ts +61 -0
- package/templates/apps/mcp/src/lib/locales.ts +171 -0
- package/templates/apps/mcp/src/lib/require-paid-tier.ts +44 -0
- package/templates/apps/mcp/src/lib/resolve-locale.ts +30 -0
- package/templates/apps/mcp/src/server.ts +139 -0
- package/templates/apps/mcp/src/tools/audit.ts +103 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +52 -0
- package/templates/apps/mcp/src/tools/create_page.ts +95 -0
- package/templates/apps/mcp/src/tools/delete_page.ts +73 -0
- package/templates/apps/mcp/src/tools/get_business_hours.ts +66 -0
- package/templates/apps/mcp/src/tools/get_faq.ts +69 -0
- package/templates/apps/mcp/src/tools/get_locations.ts +85 -0
- package/templates/apps/mcp/src/tools/get_offerings.ts +66 -0
- package/templates/apps/mcp/src/tools/get_pricing.ts +74 -0
- package/templates/apps/mcp/src/tools/get_site_info.ts +84 -0
- package/templates/apps/mcp/src/tools/index.ts +125 -0
- package/templates/apps/mcp/src/tools/list_pages.ts +72 -0
- package/templates/apps/mcp/src/tools/purchase.ts +89 -0
- package/templates/apps/mcp/src/tools/read_content.ts +61 -0
- package/templates/apps/mcp/src/tools/request_callback.ts +51 -0
- package/templates/apps/mcp/src/tools/scaffold.ts +119 -0
- package/templates/apps/mcp/src/tools/search_content.ts +59 -0
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +47 -0
- package/templates/apps/mcp/src/tools/update_page.ts +91 -0
- package/templates/apps/mcp/tsconfig.json +19 -0
- package/templates/apps/web/.env.example +47 -0
- package/templates/apps/web/CLAUDE.md +81 -0
- package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +94 -0
- package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +75 -0
- package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +82 -0
- package/templates/apps/web/__tests__/auth/env-helpers.test.ts +168 -0
- package/templates/apps/web/__tests__/authority-list-routes.test.tsx +208 -0
- package/templates/apps/web/__tests__/better-stack.test.ts +66 -0
- package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +87 -0
- package/templates/apps/web/__tests__/blog-source-populated.test.ts +130 -0
- package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +60 -0
- package/templates/apps/web/__tests__/google-analytics.test.tsx +104 -0
- package/templates/apps/web/__tests__/i18n/middleware.test.ts +119 -0
- package/templates/apps/web/__tests__/i18n/routing.test.ts +114 -0
- package/templates/apps/web/__tests__/i18n/translations.test.ts +191 -0
- package/templates/apps/web/__tests__/json-ld.test.ts +98 -0
- package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +580 -0
- package/templates/apps/web/__tests__/layout-stealth.test.ts +39 -0
- package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +103 -0
- package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +114 -0
- package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +94 -0
- package/templates/apps/web/__tests__/page.test.tsx +230 -0
- package/templates/apps/web/__tests__/products-services-phase1.test.tsx +321 -0
- package/templates/apps/web/__tests__/projects-routes.test.tsx +392 -0
- package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +475 -0
- package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +53 -0
- package/templates/apps/web/__tests__/routes/blog-route.test.tsx +191 -0
- package/templates/apps/web/__tests__/routes/llms-txt.test.ts +123 -0
- package/templates/apps/web/__tests__/routes/robots-txt.test.ts +52 -0
- package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +78 -0
- package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +203 -0
- package/templates/apps/web/__tests__/section-routes.test.tsx +693 -0
- package/templates/apps/web/__tests__/security-headers.test.ts +201 -0
- package/templates/apps/web/__tests__/sentry-config.test.ts +116 -0
- package/templates/apps/web/__tests__/setup.ts +82 -0
- package/templates/apps/web/__tests__/sitemap-locales.test.ts +415 -0
- package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +844 -0
- package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +81 -0
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +110 -0
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +58 -0
- package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +120 -0
- package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +202 -0
- package/templates/apps/web/app/[locale]/about/page.tsx +31 -0
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +173 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +45 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +117 -0
- package/templates/apps/web/app/[locale]/careers/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +113 -0
- package/templates/apps/web/app/[locale]/case-studies/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +63 -0
- package/templates/apps/web/app/[locale]/clients/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +130 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +152 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +92 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +79 -0
- package/templates/apps/web/app/[locale]/contact/error.tsx +24 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +38 -0
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +119 -0
- package/templates/apps/web/app/[locale]/courses/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +160 -0
- package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +25 -0
- package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +117 -0
- package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +58 -0
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +118 -0
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +72 -0
- package/templates/apps/web/app/[locale]/downloads/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +47 -0
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +130 -0
- package/templates/apps/web/app/[locale]/events/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/faq/page.tsx +36 -0
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +78 -0
- package/templates/apps/web/app/[locale]/how-to/page.tsx +40 -0
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +43 -0
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +129 -0
- package/templates/apps/web/app/[locale]/integrations/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/launch-service/page.tsx +31 -0
- package/templates/apps/web/app/[locale]/layout.tsx +241 -0
- package/templates/apps/web/app/[locale]/page.tsx +41 -0
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +113 -0
- package/templates/apps/web/app/[locale]/partners/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +43 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +129 -0
- package/templates/apps/web/app/[locale]/products/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +39 -0
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +138 -0
- package/templates/apps/web/app/[locale]/projects/page.tsx +30 -0
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +51 -0
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +117 -0
- package/templates/apps/web/app/[locale]/resources/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +43 -0
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +129 -0
- package/templates/apps/web/app/[locale]/services/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +81 -0
- package/templates/apps/web/app/[locale]/software/page.tsx +39 -0
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +82 -0
- package/templates/apps/web/app/[locale]/tech-articles/page.tsx +39 -0
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +63 -0
- package/templates/apps/web/app/[locale]/testimonials/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +63 -0
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +82 -0
- package/templates/apps/web/app/[locale]/whitepapers/page.tsx +39 -0
- package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +152 -0
- package/templates/apps/web/app/api/auth/[...all]/route.ts +16 -0
- package/templates/apps/web/app/error-fallback.tsx +75 -0
- package/templates/apps/web/app/error.tsx +23 -0
- package/templates/apps/web/app/layout.tsx +18 -0
- package/templates/apps/web/app/llms.txt/route.ts +101 -0
- package/templates/apps/web/app/not-found.tsx +48 -0
- package/templates/apps/web/app/og/__tests__/route.test.ts +164 -0
- package/templates/apps/web/app/og/route.tsx +135 -0
- package/templates/apps/web/app/robots.txt/route.ts +36 -0
- package/templates/apps/web/app/sitemap.xml/route.ts +146 -0
- package/templates/apps/web/auth.ts +141 -0
- package/templates/apps/web/components/ThemeToggle.tsx +60 -0
- package/templates/apps/web/components/google-analytics.tsx +34 -0
- package/templates/apps/web/content/en/blog/index.mdx +15 -0
- package/templates/apps/web/content/en/blog/launching-ailk.mdx +20 -0
- package/templates/apps/web/content/en/docs/getting-started.mdx +30 -0
- package/templates/apps/web/i18n/request.ts +36 -0
- package/templates/apps/web/jest.config.cjs +100 -0
- package/templates/apps/web/lib/__mocks__/next-mdx-remote-rsc.js +12 -0
- package/templates/apps/web/lib/__mocks__/source-server.js +31 -0
- package/templates/apps/web/lib/__tests__/og.test.ts +65 -0
- package/templates/apps/web/lib/auth-env.ts +66 -0
- package/templates/apps/web/lib/blog-list-data.ts +95 -0
- package/templates/apps/web/lib/blog-post-data.ts +79 -0
- package/templates/apps/web/lib/nav-config.ts +13 -0
- package/templates/apps/web/lib/observability/better-stack.ts +27 -0
- package/templates/apps/web/lib/og.ts +23 -0
- package/templates/apps/web/lib/prisma-for-auth.ts +34 -0
- package/templates/apps/web/lib/source.ts +34 -0
- package/templates/apps/web/messages/en.json +131 -0
- package/templates/apps/web/middleware.ts +80 -0
- package/templates/apps/web/next.config.mjs +167 -0
- package/templates/apps/web/package.json +62 -0
- package/templates/apps/web/public/.well-known/security.txt +17 -0
- 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 +4 -0
- package/templates/apps/web/public/logo-dark.svg +21 -0
- package/templates/apps/web/public/logo-light.svg +21 -0
- package/templates/apps/web/public/logomark-dark.svg +4 -0
- package/templates/apps/web/public/logomark-light.svg +4 -0
- package/templates/apps/web/public/og-default.png +0 -0
- package/templates/apps/web/public/site.webmanifest +19 -0
- package/templates/apps/web/scripts/assert-content-bundled.ts +55 -0
- package/templates/apps/web/security-headers.config.ts +132 -0
- package/templates/apps/web/sentry.client.config.ts +27 -0
- package/templates/apps/web/sentry.edge.config.ts +15 -0
- package/templates/apps/web/sentry.server.config.ts +20 -0
- package/templates/apps/web/source.config.ts +78 -0
- package/templates/apps/web/src/components/SiteNav.tsx +21 -0
- package/templates/apps/web/src/lib/routes.ts +37 -0
- package/templates/apps/web/tsconfig.json +35 -0
- package/templates/apps/web/types/messages.d.ts +19 -0
- package/templates/content/.gitkeep +0 -0
- package/templates/content/CLAUDE.md +85 -0
- package/templates/content/_site.mdx +38 -0
- package/templates/content/ar/HomePage/home.mdx +123 -0
- package/templates/content/ar/_site.mdx +35 -0
- package/templates/content/de/_site.mdx +29 -0
- package/templates/content/en/.gitkeep +0 -0
- package/templates/content/en/AboutPage/about.mdx +19 -0
- package/templates/content/en/AboutPage/about.schema.json +82 -0
- package/templates/content/en/BookCall/book-call.mdx +19 -0
- package/templates/content/en/BookCall/book-call.schema.json +121 -0
- package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +20 -0
- package/templates/content/en/CareersList/careers.mdx +24 -0
- package/templates/content/en/CaseStudiesList/case-studies.mdx +22 -0
- package/templates/content/en/CaseStudiesList/showcase.mdx +8 -0
- package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +32 -0
- package/templates/content/en/CaseStudy/schema-builder-migration.mdx +41 -0
- package/templates/content/en/ClientsList/clients.mdx +38 -0
- package/templates/content/en/ContactPage/contact.mdx +19 -0
- package/templates/content/en/ContactPage/contact.schema.json +100 -0
- package/templates/content/en/ContactPoint/primary.mdx +14 -0
- package/templates/content/en/Course/aeo-fundamentals.mdx +37 -0
- package/templates/content/en/Course/schema-builder-workshop.mdx +62 -0
- package/templates/content/en/CoursesList/courses.mdx +22 -0
- package/templates/content/en/DownloadsList/downloads.mdx +22 -0
- package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +39 -0
- package/templates/content/en/Event/ailk-launch-day-2026.mdx +35 -0
- package/templates/content/en/EventsList/events.mdx +26 -0
- package/templates/content/en/FAQPage/faq.mdx +420 -0
- package/templates/content/en/FAQPage/faq.schema.json +231 -0
- package/templates/content/en/GlossaryList/glossary.mdx +19 -0
- package/templates/content/en/GlossaryList/glossary.schema.json +300 -0
- package/templates/content/en/HomePage/home.mdx +312 -0
- package/templates/content/en/HomePage/home.schema.json +88 -0
- package/templates/content/en/HomePage/launch-service.mdx +475 -0
- package/templates/content/en/HomePage/launch-service.schema.json +138 -0
- package/templates/content/en/HowTo/add-mdx-content.mdx +38 -0
- package/templates/content/en/HowTo/configure-aeo-scoring.mdx +35 -0
- package/templates/content/en/HowTo/set-up-json-ld.mdx +36 -0
- package/templates/content/en/Integration/github-actions.mdx +31 -0
- package/templates/content/en/Integration/openai-api.mdx +26 -0
- package/templates/content/en/Integration/vercel.mdx +32 -0
- package/templates/content/en/IntegrationsList/integrations.mdx +22 -0
- package/templates/content/en/JobPosting/content-strategist.mdx +46 -0
- package/templates/content/en/JobPosting/developer-advocate.mdx +47 -0
- package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +57 -0
- package/templates/content/en/LandingPage/aeo-audit.mdx +19 -0
- package/templates/content/en/LandingPage/aeo-audit.schema.json +92 -0
- package/templates/content/en/LandingPage/agency-waitlist.mdx +340 -0
- package/templates/content/en/LandingPage/agency-waitlist.schema.json +152 -0
- package/templates/content/en/LandingPage/developers.mdx +19 -0
- package/templates/content/en/LandingPage/developers.schema.json +109 -0
- package/templates/content/en/LandingPage/for-agencies.mdx +390 -0
- package/templates/content/en/LandingPage/for-agencies.schema.json +107 -0
- package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +417 -0
- package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +105 -0
- package/templates/content/en/LandingPage/for-businesses.mdx +19 -0
- package/templates/content/en/LandingPage/for-businesses.schema.json +96 -0
- package/templates/content/en/LandingPage/platform.mdx +369 -0
- package/templates/content/en/LandingPage/platform.schema.json +166 -0
- package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +19 -0
- package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +102 -0
- package/templates/content/en/LandingPage/vs-wordpress.mdx +19 -0
- package/templates/content/en/LandingPage/vs-wordpress.schema.json +53 -0
- package/templates/content/en/NewsletterSignup/newsletter.mdx +236 -0
- package/templates/content/en/NewsletterSignup/newsletter.schema.json +91 -0
- package/templates/content/en/Organization/site-owner.mdx +16 -0
- package/templates/content/en/Partner/prisma.mdx +31 -0
- package/templates/content/en/Partner/supabase.mdx +30 -0
- package/templates/content/en/Partner/vercel.mdx +28 -0
- package/templates/content/en/PartnersList/partners.mdx +22 -0
- package/templates/content/en/Person/founder.mdx +18 -0
- package/templates/content/en/Pricing/pricing.mdx +538 -0
- package/templates/content/en/Pricing/pricing.schema.json +209 -0
- package/templates/content/en/PrivacyPage/privacy.mdx +165 -0
- package/templates/content/en/PrivacyPage/privacy.schema.json +64 -0
- package/templates/content/en/Product/ailk.mdx +19 -0
- package/templates/content/en/Product/ailk.schema.json +94 -0
- package/templates/content/en/Product/api-launch-kit.mdx +26 -0
- package/templates/content/en/Product/launch-kit.mdx +28 -0
- package/templates/content/en/ProductsList/products.mdx +26 -0
- package/templates/content/en/Project/ailk-phase-2.mdx +54 -0
- package/templates/content/en/ProjectsList/projects.mdx +25 -0
- package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +27 -0
- package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +28 -0
- package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +39 -0
- package/templates/content/en/ResourcesHub/resources.mdx +22 -0
- package/templates/content/en/Service/consulting.mdx +22 -0
- package/templates/content/en/Service/implementation.mdx +30 -0
- package/templates/content/en/Service/launch-service.mdx +232 -0
- package/templates/content/en/Service/launch.mdx +228 -0
- package/templates/content/en/ServicesList/services.mdx +84 -0
- package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +44 -0
- package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +47 -0
- package/templates/content/en/SoftwareProduct/ailk-schema.mdx +49 -0
- package/templates/content/en/TeamList/team.mdx +63 -0
- package/templates/content/en/TeamMember/founder.mdx +72 -0
- package/templates/content/en/TermsPage/terms.mdx +19 -0
- package/templates/content/en/TermsPage/terms.schema.json +99 -0
- package/templates/content/en/TestimonialsList/testimonials.mdx +44 -0
- package/templates/content/en/UseCasesList/use-cases.mdx +30 -0
- package/templates/content/en/Whitepaper/ax-first-product-development.mdx +27 -0
- package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +27 -0
- package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +31 -0
- package/templates/content/en-XA/AboutPage/about.mdx +8 -0
- package/templates/content/en-XA/FAQPage/faq.mdx +8 -0
- package/templates/content/en-XA/HomePage/home.mdx +8 -0
- package/templates/content/es/_site.mdx +30 -0
- package/templates/content/fr/_site.mdx +30 -0
- package/templates/content/ja/_site.mdx +30 -0
- package/templates/content/pt-BR/AboutPage/about.mdx +8 -0
- package/templates/content/pt-BR/FAQPage/faq.mdx +8 -0
- package/templates/content/pt-BR/HomePage/home.mdx +23 -0
- package/templates/database/CHANGELOG.md +5 -0
- package/templates/database/README.md +234 -0
- package/templates/database/__tests__/user-cascade.test.ts +125 -0
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +3 -0
- package/templates/database/inbox/__tests__/schema.test.ts +40 -0
- package/templates/database/inbox/schema.prisma +52 -0
- package/templates/database/jest.config.cjs +29 -0
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +328 -0
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +100 -0
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +28 -0
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +25 -0
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +42 -0
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +67 -0
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +21 -0
- package/templates/database/migrations/migration_lock.toml +3 -0
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/package.json +45 -0
- package/templates/database/prisma.config.ts +20 -0
- package/templates/database/schema.prisma +487 -0
- package/templates/database/scripts/db-generate-locked.sh +80 -0
- package/templates/database/seed-test.ts +85 -0
- package/templates/database/tsconfig.build.json +16 -0
- package/templates/database/tsconfig.json +9 -0
- package/templates/package.json +125 -0
- package/templates/pnpm-workspace.yaml +6 -0
- package/templates/project.yaml +59 -0
- package/templates/tsconfig.base.json +18 -0
- package/templates/tsconfig.json +45 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lighthouse CI improvement fixture — image shrink
|
|
3
|
+
*
|
|
4
|
+
* Documentation-as-code, not loaded in production. Lives under
|
|
5
|
+
* `__tests__/` and is **not** imported by any file under `app/` or
|
|
6
|
+
* `components/` (asserted by spec §3.7 / AC bullet "None of the three
|
|
7
|
+
* fixtures is imported…").
|
|
8
|
+
*
|
|
9
|
+
* **Purpose**
|
|
10
|
+
*
|
|
11
|
+
* Sam's verification checklist (spec §5.1, Layer 1) calls for opening a
|
|
12
|
+
* PR that "improves performance" so the workflow's PR comment shows
|
|
13
|
+
* positive LCP/CLS deltas vs main. This fixture documents a known-good
|
|
14
|
+
* improvement pattern — swapping a hand-coded `<img>` for `next/image`
|
|
15
|
+
* with explicit dimensions and AVIF/WebP negotiation — so Sam (or any
|
|
16
|
+
* implementer running the verification) can apply it deterministically
|
|
17
|
+
* without designing a new improvement from scratch.
|
|
18
|
+
*
|
|
19
|
+
* **How to wire it in for verification**
|
|
20
|
+
*
|
|
21
|
+
* The improvement direction is "make `/` faster than baseline by enough
|
|
22
|
+
* that the PR comment shows a noticeable LCP improvement." The fixture
|
|
23
|
+
* is the *recipe*, not a runtime artifact.
|
|
24
|
+
*
|
|
25
|
+
* 1. On `main`, identify a route whose largest-contentful-paint element
|
|
26
|
+
* is an image — e.g., the home page hero. (Use the most recent
|
|
27
|
+
* `.lighthouseci/lhr-*.html` report's "Largest Contentful Paint
|
|
28
|
+
* element" diagnostic to find it.)
|
|
29
|
+
* 2. If the LCP image is currently rendered via a plain `<img>`, swap
|
|
30
|
+
* to `next/image` per the BEFORE/AFTER below.
|
|
31
|
+
* 3. If it's already `next/image`, the easier improvement is shrinking
|
|
32
|
+
* the source image's dimensions to match the rendered size (avoid
|
|
33
|
+
* serving a 2400×1600 image into a 800×533 slot).
|
|
34
|
+
* 4. Open a PR. The Lighthouse CI workflow's PR comment will show the
|
|
35
|
+
* LCP delta vs main; expect a negative delta on `/` (faster).
|
|
36
|
+
*
|
|
37
|
+
* **BEFORE — plain img**
|
|
38
|
+
*
|
|
39
|
+
* ```tsx
|
|
40
|
+
* <img src="/hero.png" alt="..." />
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* Issues: no width/height (CLS risk), no responsive sources, no
|
|
44
|
+
* priority hint, no format negotiation.
|
|
45
|
+
*
|
|
46
|
+
* **AFTER — next/image**
|
|
47
|
+
*
|
|
48
|
+
* ```tsx
|
|
49
|
+
* import Image from "next/image";
|
|
50
|
+
*
|
|
51
|
+
* <Image
|
|
52
|
+
* src="/hero.png"
|
|
53
|
+
* alt="..."
|
|
54
|
+
* width={800}
|
|
55
|
+
* height={533}
|
|
56
|
+
* priority
|
|
57
|
+
* />
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* Effects: dimensions reserve layout space (CLS → 0), `priority` skips
|
|
61
|
+
* lazy-load for above-the-fold content (LCP improves), Next's image
|
|
62
|
+
* pipeline negotiates AVIF/WebP and serves the right size.
|
|
63
|
+
*
|
|
64
|
+
* **Why this is documented as a fixture, not a real PR**
|
|
65
|
+
*
|
|
66
|
+
* The actual perf improvement is implementation-specific (which route's
|
|
67
|
+
* LCP element is currently a plain `<img>`?). The fixture captures the
|
|
68
|
+
* *recipe* so that when Sam runs the verification, the diff is
|
|
69
|
+
* predictable instead of one-off-discovered. The fixture ships as code
|
|
70
|
+
* (not just docs) so editor tooling can refactor it if `next/image`'s
|
|
71
|
+
* API shifts.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
import type { ImageProps } from "next/image";
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Reference props for the recommended `next/image` invocation. Not
|
|
78
|
+
* exported as a component; the verification PR copies this shape into
|
|
79
|
+
* the actual route file.
|
|
80
|
+
*/
|
|
81
|
+
export const recommendedImageProps: Partial<ImageProps> = {
|
|
82
|
+
width: 800,
|
|
83
|
+
height: 533,
|
|
84
|
+
priority: true,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reference render demonstrating the AFTER shape. Not imported by any
|
|
89
|
+
* route — see file header.
|
|
90
|
+
*/
|
|
91
|
+
export function ImprovementImageShrinkFixture() {
|
|
92
|
+
// Inline `<img>` here is intentional — this fixture documents the
|
|
93
|
+
// anti-pattern (BEFORE) alongside the recommended pattern. Replacing
|
|
94
|
+
// this `<img>` is the verification step, so we keep it visible.
|
|
95
|
+
// eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text
|
|
96
|
+
return (
|
|
97
|
+
<img
|
|
98
|
+
data-fixture="lhci-improvement-image-shrink"
|
|
99
|
+
src="/hero.png"
|
|
100
|
+
alt="placeholder"
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Lighthouse CI override fixture
|
|
2
|
+
|
|
3
|
+
Documentation-only, not loaded in production. Lives under `__tests__/`
|
|
4
|
+
alongside the other Lighthouse fixtures. This file is markdown, not a
|
|
5
|
+
component — Sam's verification checklist for the override path is a
|
|
6
|
+
PR-process exercise, not a runtime artifact.
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Sam's verification checklist (`docs/internal/specs/ailk-lighthouse-ci-spec.md`
|
|
11
|
+
§5.1, Layer 1) calls for confirming the override mechanism: when a
|
|
12
|
+
performance regression is intentional, applying `perf-override` plus a
|
|
13
|
+
qualifying comment lets the PR ship despite the failing assertion. This
|
|
14
|
+
fixture is the recipe.
|
|
15
|
+
|
|
16
|
+
## When to override
|
|
17
|
+
|
|
18
|
+
Override is for **intentional** regressions whose business value
|
|
19
|
+
outweighs the perf cost — e.g., adding a one-time analytics script the
|
|
20
|
+
team has decided to ship, or shipping a heavy demo asset for a launch.
|
|
21
|
+
Routine code that regresses LCP without intent should be fixed, not
|
|
22
|
+
overridden.
|
|
23
|
+
|
|
24
|
+
If you find yourself overriding more than once per quarter, the
|
|
25
|
+
thresholds are wrong. Re-run the §2.5 baseline-measurement procedure on
|
|
26
|
+
post-merge `main` and commit recalibrated thresholds in a follow-up PR
|
|
27
|
+
per `docs/perf.md` § How to update thresholds.
|
|
28
|
+
|
|
29
|
+
## How to override (verification recipe)
|
|
30
|
+
|
|
31
|
+
Run this against the `regression-bloat.tsx` fixture or any PR with a
|
|
32
|
+
genuine intentional regression.
|
|
33
|
+
|
|
34
|
+
1. Confirm the Lighthouse CI workflow has run and failed on the PR. The
|
|
35
|
+
PR comment posted by the workflow names the route + metric that
|
|
36
|
+
exceeded the threshold.
|
|
37
|
+
2. Apply the `perf-override` label to the PR:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
gh pr edit <PR> --add-label perf-override
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
3. Post a comment whose body **starts with** the literal string
|
|
44
|
+
`perf-override:` (case-insensitive — the workflow `ascii_downcase`s
|
|
45
|
+
before checking) followed by the justification:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
perf-override: shipping the launch-day hero asset; will revisit
|
|
49
|
+
thresholds in #<follow-up-issue> after main has the new baseline.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Bot comments are excluded — the comment must be from a non-bot
|
|
53
|
+
author. The comment must be from a non-bot login (the workflow
|
|
54
|
+
filters logins ending in `[bot]`).
|
|
55
|
+
|
|
56
|
+
4. Re-trigger the workflow. The simplest trigger is to push an empty
|
|
57
|
+
commit:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git commit --allow-empty -m "chore: re-run lhci with override"
|
|
61
|
+
git push
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `pull_request: synchronize` event fires the workflow. The
|
|
65
|
+
`labeled` event also fires it, so just adding the label without
|
|
66
|
+
pushing also re-runs the gate (concurrency group will cancel the
|
|
67
|
+
prior in-flight run).
|
|
68
|
+
|
|
69
|
+
5. Confirm the workflow run completes green:
|
|
70
|
+
- The original `Run Lighthouse CI` step still fails (this is
|
|
71
|
+
expected — the override does not change the assertion outcome).
|
|
72
|
+
- The `Evaluate override gate` step posts a comment naming the
|
|
73
|
+
justification: `Lighthouse CI override applied. Justification: …`.
|
|
74
|
+
- The final `Surface result` step exits 0 because the override
|
|
75
|
+
applied.
|
|
76
|
+
|
|
77
|
+
6. Verify the audit trail by reading the PR's comment thread. The
|
|
78
|
+
override comment, the workflow's acknowledgement comment, and the
|
|
79
|
+
original Lighthouse failure summary should all be visible.
|
|
80
|
+
|
|
81
|
+
## How the workflow evaluates the override
|
|
82
|
+
|
|
83
|
+
From `.github/workflows/lighthouse-ci.yml` `Evaluate override gate`
|
|
84
|
+
step:
|
|
85
|
+
|
|
86
|
+
- Reads PR JSON: `gh pr view <PR> --json labels,comments`.
|
|
87
|
+
- Checks `labels` for `perf-override`. If absent → no override; surface
|
|
88
|
+
the original failure.
|
|
89
|
+
- Iterates `comments`, filters out bot logins (`endswith("[bot]")`),
|
|
90
|
+
and finds the first comment whose body, after `ascii_downcase`,
|
|
91
|
+
starts with `perf-override:`. If none → posts the
|
|
92
|
+
"label-without-comment" instructional comment, surfaces the original
|
|
93
|
+
failure.
|
|
94
|
+
- Both present → posts the override-applied comment with the
|
|
95
|
+
justification quoted, sets `applied=true`. The final `Surface result`
|
|
96
|
+
step then exits 0.
|
|
97
|
+
|
|
98
|
+
The override is per-PR-per-run; each push or label event re-evaluates
|
|
99
|
+
the current state. Removing the label or deleting the comment between
|
|
100
|
+
runs returns the gate to enforcing.
|
|
101
|
+
|
|
102
|
+
## Anti-patterns
|
|
103
|
+
|
|
104
|
+
- **Substring match.** A comment that mentions `perf-override` mid-body
|
|
105
|
+
does not count — the body must _start with_ `perf-override:`. This is
|
|
106
|
+
intentional (spec §3.6) so casual references in discussion don't
|
|
107
|
+
silently override the gate.
|
|
108
|
+
- **Bot-authored override.** Comments from automated reviewers (Bugbot,
|
|
109
|
+
claude-review, the workflow itself) are filtered out. A human-author
|
|
110
|
+
comment is required.
|
|
111
|
+
- **Override-only PRs.** If every PR that touches the LCP element ends
|
|
112
|
+
up overridden, recalibrate thresholds instead of overriding. The
|
|
113
|
+
override path is for one-off intent, not a workaround for stale
|
|
114
|
+
budgets.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lighthouse CI regression fixture — bloat
|
|
3
|
+
*
|
|
4
|
+
* Documentation-as-code, not loaded in production. Lives under
|
|
5
|
+
* `__tests__/` and is **not** imported by any file under `app/` or
|
|
6
|
+
* `components/` (asserted by spec §3.7 / AC bullet "None of the three
|
|
7
|
+
* fixtures is imported…").
|
|
8
|
+
*
|
|
9
|
+
* **Purpose**
|
|
10
|
+
*
|
|
11
|
+
* Sam's verification checklist (spec §5.1, Layer 1) calls for opening a
|
|
12
|
+
* PR that adds a "deliberately wasteful 200KB+ unused dependency" to
|
|
13
|
+
* `apps/web` to confirm Lighthouse CI fails on `/` with an LCP
|
|
14
|
+
* regression. Tree-shaking is the failure mode that makes the
|
|
15
|
+
* verification a no-op — if Next.js drops the import because the value
|
|
16
|
+
* is unused, the regression never happens. This fixture is structured
|
|
17
|
+
* so Next.js cannot eliminate it.
|
|
18
|
+
*
|
|
19
|
+
* **How to wire it in for verification**
|
|
20
|
+
*
|
|
21
|
+
* 1. Move this file to a temporary location inside `app/` (e.g.,
|
|
22
|
+
* `app/_lhci-regression-fixture.tsx`) — `__tests__/` is not part of
|
|
23
|
+
* the Next.js compile graph.
|
|
24
|
+
* 2. Add a side-effectful import to `apps/web/app/page.tsx`:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import "./_lhci-regression-fixture";
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* A side-effect import (no symbol, no destructure) is the strongest
|
|
31
|
+
* signal to bundlers that the module must be evaluated. Combined
|
|
32
|
+
* with the eager `moment` + locales evaluation below, the bundler
|
|
33
|
+
* will not drop the dependency.
|
|
34
|
+
* 3. Open a PR to `main` containing the move + the import line.
|
|
35
|
+
* 4. Wait for the `Lighthouse CI` workflow on the PR to fail with an
|
|
36
|
+
* `largest-contentful-paint` assertion error on `http://localhost:3000/$`.
|
|
37
|
+
* The PR comment will name the regressed metric and route.
|
|
38
|
+
* 5. Revert the changes after verification.
|
|
39
|
+
*
|
|
40
|
+
* **Why `moment` + locales**
|
|
41
|
+
*
|
|
42
|
+
* `moment` ships every locale by default (≈230 KB minified). Webpack
|
|
43
|
+
* tree-shaking cannot drop locales that are reachable via dynamic
|
|
44
|
+
* `require()` paths inside moment's source. The eager `moment().locale(...)`
|
|
45
|
+
* calls below force the runtime to walk the locale registry. The result
|
|
46
|
+
* is a deterministic ~250 KB JS payload added to the route's first-load
|
|
47
|
+
* bundle, which translates to a measurable LCP regression on `/` under
|
|
48
|
+
* the desktop preset.
|
|
49
|
+
*
|
|
50
|
+
* Note: this fixture is intentionally *not* shipped — it's a recipe for
|
|
51
|
+
* Sam, not a runtime artifact.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
// eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-require-imports
|
|
55
|
+
const moment: any = (globalThis as any).require?.("moment") ?? null;
|
|
56
|
+
|
|
57
|
+
const NOW = moment ? moment() : null;
|
|
58
|
+
|
|
59
|
+
if (NOW) {
|
|
60
|
+
// Touch every locale to defeat tree-shaking. Each .locale() call walks
|
|
61
|
+
// moment's locale registry and pulls in the locale module.
|
|
62
|
+
for (const code of [
|
|
63
|
+
"en",
|
|
64
|
+
"fr",
|
|
65
|
+
"de",
|
|
66
|
+
"es",
|
|
67
|
+
"it",
|
|
68
|
+
"pt",
|
|
69
|
+
"nl",
|
|
70
|
+
"ru",
|
|
71
|
+
"ja",
|
|
72
|
+
"zh-cn",
|
|
73
|
+
"ko",
|
|
74
|
+
"ar",
|
|
75
|
+
"he",
|
|
76
|
+
"tr",
|
|
77
|
+
"pl",
|
|
78
|
+
"sv",
|
|
79
|
+
"da",
|
|
80
|
+
"fi",
|
|
81
|
+
"no",
|
|
82
|
+
"cs",
|
|
83
|
+
]) {
|
|
84
|
+
NOW.locale(code);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function RegressionBloatFixture() {
|
|
89
|
+
return (
|
|
90
|
+
<div data-fixture="lhci-regression-bloat">
|
|
91
|
+
{NOW ? NOW.format("YYYY-MM-DD") : "no-moment"}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Home page tests. The page is an async server component; jest can render
|
|
2
|
+
// it by awaiting the returned ReactElement promise. getMdxSource and
|
|
3
|
+
// getPageSections are injected via optional props instead of mocking internal
|
|
4
|
+
// modules — the test is independent of the on-disk /content/ tree without
|
|
5
|
+
// coupling to module internals.
|
|
6
|
+
// next-mdx-remote/rsc is mocked (external boundary — RSC compiler not available under jsdom).
|
|
7
|
+
|
|
8
|
+
import { render, screen } from "@testing-library/react";
|
|
9
|
+
|
|
10
|
+
jest.mock("next-mdx-remote/rsc", () => ({
|
|
11
|
+
__esModule: true,
|
|
12
|
+
MDXRemote: ({ source }: { source: string }): React.ReactElement => (
|
|
13
|
+
<div data-testid="mdx-rendered">{source}</div>
|
|
14
|
+
),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Renders a real <h1>/<p> for a leading hero section — mirroring the real
|
|
18
|
+
// Hero section component (packages/ui) closely enough to exercise the
|
|
19
|
+
// renderPage leading-hero carve-out (#3301: renderPage omits its own
|
|
20
|
+
// top-level <h1>/<p> when sections[0] is a hero, trusting the hero section
|
|
21
|
+
// to supply the page's sole heading + lead paragraph).
|
|
22
|
+
jest.mock("@working-theory/ui", () => ({
|
|
23
|
+
__esModule: true,
|
|
24
|
+
SectionRenderer: ({
|
|
25
|
+
sections,
|
|
26
|
+
}: {
|
|
27
|
+
sections: Array<{
|
|
28
|
+
type: string;
|
|
29
|
+
data?: { headline?: string; subheadline?: string };
|
|
30
|
+
}>;
|
|
31
|
+
}): React.ReactElement => (
|
|
32
|
+
<div data-testid="section-renderer" data-section-count={sections.length}>
|
|
33
|
+
{sections.map((section, i) =>
|
|
34
|
+
section.type === "hero" ? (
|
|
35
|
+
<div key={i} data-testid="section-item">
|
|
36
|
+
<h1>{section.data?.headline}</h1>
|
|
37
|
+
{section.data?.subheadline ? (
|
|
38
|
+
<p>{section.data.subheadline}</p>
|
|
39
|
+
) : null}
|
|
40
|
+
</div>
|
|
41
|
+
) : (
|
|
42
|
+
<div key={i} data-testid="section-item" />
|
|
43
|
+
),
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
import * as React from "react";
|
|
50
|
+
|
|
51
|
+
import Page from "../app/[locale]/page";
|
|
52
|
+
import type { getMdxSource, getPageSections } from "@working-theory/page-renderer";
|
|
53
|
+
|
|
54
|
+
type GetMdxSource = typeof getMdxSource;
|
|
55
|
+
type GetPageSections = typeof getPageSections;
|
|
56
|
+
|
|
57
|
+
async function renderAsync(element: Promise<React.ReactElement>): Promise<void> {
|
|
58
|
+
const resolved = await element;
|
|
59
|
+
render(resolved);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Mirrors the real content-authoring convention (#3301): the hero's
|
|
63
|
+
// headline/subheadline mirror the page's frontmatter title/description.
|
|
64
|
+
const HERO_SECTION = {
|
|
65
|
+
id: "hero-1",
|
|
66
|
+
type: "hero" as const,
|
|
67
|
+
data: {
|
|
68
|
+
headline: "Welcome to AILK",
|
|
69
|
+
subheadline: "AX-first OSS template.",
|
|
70
|
+
primaryCta: { label: "Get started", href: "/docs" },
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const FEATURE_SECTION = {
|
|
75
|
+
id: "feature-1",
|
|
76
|
+
type: "feature" as const,
|
|
77
|
+
data: {
|
|
78
|
+
headline: "Built for agents",
|
|
79
|
+
features: [
|
|
80
|
+
{ title: "API-first", description: "Canonical write surface." },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const ITEMS_SECTION = {
|
|
86
|
+
id: "items-1",
|
|
87
|
+
type: "items" as const,
|
|
88
|
+
data: {
|
|
89
|
+
headline: "What's inside",
|
|
90
|
+
items: [
|
|
91
|
+
{ title: "apps/web", description: "Next.js App Router" },
|
|
92
|
+
{ title: "apps/api", description: "Fastify HTTP API" },
|
|
93
|
+
{ title: "apps/mcp", description: "MCP server" },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const CTA_SECTION = {
|
|
99
|
+
id: "cta-1",
|
|
100
|
+
type: "cta" as const,
|
|
101
|
+
data: {
|
|
102
|
+
headline: "Ready to ship?",
|
|
103
|
+
primaryCta: { label: "Clone now", href: "https://github.com/tailwind-ai/ai-launch-kit" },
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
describe("apps/web — home page", () => {
|
|
108
|
+
describe("with HomePage MDX present and sections in frontmatter", () => {
|
|
109
|
+
const stubGetMdxSource = jest.fn<ReturnType<GetMdxSource>, Parameters<GetMdxSource>>().mockResolvedValue({
|
|
110
|
+
frontmatter: {
|
|
111
|
+
type: "HomePage",
|
|
112
|
+
slug: "home",
|
|
113
|
+
title: "Welcome to AILK",
|
|
114
|
+
description: "AX-first OSS template.",
|
|
115
|
+
},
|
|
116
|
+
body: "# Welcome\n\nBody copy.",
|
|
117
|
+
});
|
|
118
|
+
const stubGetPageSections = jest.fn<ReturnType<GetPageSections>, Parameters<GetPageSections>>().mockResolvedValue([
|
|
119
|
+
HERO_SECTION,
|
|
120
|
+
FEATURE_SECTION,
|
|
121
|
+
ITEMS_SECTION,
|
|
122
|
+
CTA_SECTION,
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
stubGetMdxSource.mockClear();
|
|
127
|
+
stubGetPageSections.mockClear();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("renders the MDX-sourced title in the h1", async () => {
|
|
131
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
132
|
+
const heading = screen.getByRole("heading", { level: 1 });
|
|
133
|
+
expect(heading).toHaveTextContent("Welcome to AILK");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("renders the description via the leading hero section, not a duplicate top-level paragraph (#3301)", async () => {
|
|
137
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
138
|
+
// renderPage's own <p data-testid="home-description"> is skipped when
|
|
139
|
+
// the first section is a hero — the hero renders the lead paragraph.
|
|
140
|
+
expect(screen.queryByTestId("home-description")).toBeNull();
|
|
141
|
+
expect(screen.getByText("AX-first OSS template.")).toBeInTheDocument();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("renders exactly one h1 (no duplicate title from renderPage + hero, #3301)", async () => {
|
|
145
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
146
|
+
expect(screen.getAllByRole("heading", { level: 1 })).toHaveLength(1);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("emits a parseable <script type='application/ld+json'>", async () => {
|
|
150
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
151
|
+
const script = screen.getByTestId("json-ld");
|
|
152
|
+
expect(script.getAttribute("type")).toBe("application/ld+json");
|
|
153
|
+
// The app's responsibility is emitting a parseable JSON-LD <script>.
|
|
154
|
+
// The emitted @graph SHAPE is pinned once in @working-theory/schema's HomePage
|
|
155
|
+
// builder test — re-asserting @type/mainEntity here duplicated that pin
|
|
156
|
+
// and turned every builder change into per-app churn (#2635).
|
|
157
|
+
expect(() => JSON.parse(script.innerHTML)).not.toThrow();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("renders SectionRenderer with sections from frontmatter", async () => {
|
|
161
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
162
|
+
const renderer = screen.getByTestId("section-renderer");
|
|
163
|
+
expect(renderer).toBeInTheDocument();
|
|
164
|
+
expect(renderer.dataset.sectionCount).toBe("4");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("renders at least 3 section items beyond the hero", async () => {
|
|
168
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
169
|
+
const items = screen.getAllByTestId("section-item");
|
|
170
|
+
expect(items.length).toBeGreaterThanOrEqual(4);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("does not render the MDX body directly", async () => {
|
|
174
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
175
|
+
expect(screen.queryByTestId("mdx-body")).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("with HomePage MDX present but no sections in frontmatter", () => {
|
|
180
|
+
const stubGetMdxSource = jest.fn<ReturnType<GetMdxSource>, Parameters<GetMdxSource>>().mockResolvedValue({
|
|
181
|
+
frontmatter: {
|
|
182
|
+
type: "HomePage",
|
|
183
|
+
slug: "home",
|
|
184
|
+
title: "Welcome to AILK",
|
|
185
|
+
description: "AX-first OSS template.",
|
|
186
|
+
},
|
|
187
|
+
body: "# Welcome\n\nBody copy.",
|
|
188
|
+
});
|
|
189
|
+
const stubGetPageSections = jest.fn<ReturnType<GetPageSections>, Parameters<GetPageSections>>().mockResolvedValue([]);
|
|
190
|
+
|
|
191
|
+
beforeEach(() => {
|
|
192
|
+
stubGetMdxSource.mockClear();
|
|
193
|
+
stubGetPageSections.mockClear();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("renders the page without crashing", async () => {
|
|
197
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
198
|
+
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent(
|
|
199
|
+
"Welcome to AILK",
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("renders a SectionRenderer with empty sections (returns 200)", async () => {
|
|
204
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
205
|
+
expect(screen.queryByTestId("section-item")).toBeNull();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("with no HomePage MDX (fresh clone)", () => {
|
|
210
|
+
const stubGetMdxSource = jest.fn<ReturnType<GetMdxSource>, Parameters<GetMdxSource>>().mockResolvedValue(null);
|
|
211
|
+
const stubGetPageSections = jest.fn<ReturnType<GetPageSections>, Parameters<GetPageSections>>().mockResolvedValue([]);
|
|
212
|
+
|
|
213
|
+
beforeEach(() => {
|
|
214
|
+
stubGetMdxSource.mockClear();
|
|
215
|
+
stubGetPageSections.mockClear();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("falls back to the placeholder h1", async () => {
|
|
219
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
220
|
+
const heading = screen.getByRole("heading", { level: 1 });
|
|
221
|
+
expect(heading).toHaveTextContent("AI Launch Kit");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("renders a non-empty fallback description", async () => {
|
|
225
|
+
await renderAsync(Page({ params: Promise.resolve({ locale: "en" }), getMdxSource: stubGetMdxSource, getPageSections: stubGetPageSections }));
|
|
226
|
+
const description = screen.getByTestId("home-description");
|
|
227
|
+
expect(description.textContent?.trim().length ?? 0).toBeGreaterThan(0);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|