create-ailk 0.1.0 → 0.2.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 +124 -3
- package/component-catalog.md +456 -0
- package/dist/cli.js +92 -16
- package/dist/component-catalog.d.ts +11 -0
- package/dist/component-catalog.js +39 -0
- package/dist/copy.d.ts +7 -0
- package/dist/copy.js +24 -0
- package/dist/derive-architecture.d.ts +13 -0
- package/dist/derive-architecture.js +238 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +19 -2
- package/dist/lib/extract-module-bundle.d.ts +16 -0
- package/dist/lib/extract-module-bundle.js +184 -0
- package/dist/lib/fetch-module.d.ts +55 -0
- package/dist/lib/fetch-module.js +125 -0
- package/dist/lib/module-license-gate.d.ts +48 -0
- package/dist/lib/module-license-gate.js +67 -0
- package/dist/lib/module-tier.d.ts +35 -0
- package/dist/lib/module-tier.js +37 -0
- package/dist/module-architecture.d.ts +175 -0
- package/dist/module-architecture.js +663 -0
- package/dist/parse-args.d.ts +47 -1
- package/dist/parse-args.js +143 -2
- package/dist/programmatic.d.ts +160 -3
- package/dist/programmatic.js +196 -7
- package/dist/surfaces.d.ts +211 -0
- package/dist/surfaces.js +532 -0
- package/dist/sync-routes.d.ts +92 -0
- package/dist/sync-routes.js +350 -0
- package/package.json +12 -5
- package/templates/.claude/rules/architecture.md +6 -2
- package/templates/.claude/skills/README.md +9 -9
- package/templates/.claude/skills/add-schema/SKILL.md +14 -7
- package/templates/.claude/skills/add-schema/references/schema-types.md +109 -6
- package/templates/.claude/skills/provision-config/SKILL.md +1 -1
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +498 -1
- package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +370 -0
- package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +376 -0
- package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +471 -0
- package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +365 -0
- package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +60 -0
- package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +446 -0
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +4 -4
- package/templates/.env.example +126 -2
- package/templates/CLAUDE.md +11 -5
- package/templates/README.md +1 -1
- package/templates/apps/api/.env.example +38 -0
- package/templates/apps/api/CLAUDE.md +78 -1
- package/templates/apps/api/LICENSE +201 -0
- package/templates/apps/api/api/index.ts +6 -141
- package/templates/apps/api/jest.config.cjs +16 -0
- package/templates/apps/api/package.json +22 -6
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +141 -0
- package/templates/apps/api/src/__tests__/server.test.ts +46 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +211 -0
- package/templates/apps/api/src/bin/abandon-sweep.ts +42 -0
- package/templates/apps/api/src/bin/deliverable-resend.ts +144 -0
- package/templates/apps/api/src/bin/deliverable-upload.ts +114 -0
- package/templates/apps/api/src/bin/followup-sweep.ts +44 -0
- package/templates/apps/api/src/bin/listing-csv-sweep.ts +46 -0
- package/templates/apps/api/src/bin/seed-presets.ts +58 -0
- package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +104 -0
- package/templates/apps/api/src/bin/session-retention-sweep.ts +46 -0
- package/templates/apps/api/src/cli.ts +5 -11
- package/templates/apps/api/src/config/index.ts +35 -0
- package/templates/apps/api/src/config/modules.ts +43 -0
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +11 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -0
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +2 -2
- package/templates/apps/api/src/lib/site.ts +16 -0
- package/templates/apps/api/src/lib/stripe.ts +22 -0
- package/templates/apps/api/src/lib/tenant-db.ts +225 -0
- package/templates/apps/api/src/lib/ws-token.ts +91 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +7 -1
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +7 -1
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +91 -4
- package/templates/apps/api/src/middleware/auth.ts +43 -8
- package/templates/apps/api/src/middleware/rate-limit.ts +78 -3
- package/templates/apps/api/src/middleware/tenant.ts +99 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +92 -0
- package/templates/apps/api/src/openapi/spec.ts +312 -3
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +175 -0
- package/templates/apps/api/src/routes/aeo/index.ts +107 -0
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +5 -2
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +22 -3
- package/templates/apps/api/src/routes/auth/tokens.ts +13 -1
- package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +450 -0
- package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +262 -0
- package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +436 -0
- package/templates/apps/api/src/routes/billing/index.ts +36 -0
- package/templates/apps/api/src/routes/billing/portal.ts +182 -0
- package/templates/apps/api/src/routes/billing/read.ts +75 -0
- package/templates/apps/api/src/routes/billing/usage.ts +153 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +99 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +20 -8
- package/templates/apps/api/src/routes/content/create.ts +1 -0
- package/templates/apps/api/src/routes/content/delete.ts +1 -0
- package/templates/apps/api/src/routes/content/index.ts +42 -6
- package/templates/apps/api/src/routes/content/update.ts +1 -0
- package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +393 -0
- package/templates/apps/api/src/routes/deliverables/index.ts +200 -0
- package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +443 -0
- package/templates/apps/api/src/routes/flow-checkouts/index.ts +82 -0
- package/templates/apps/api/src/routes/flows/README.md +147 -0
- package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +752 -0
- package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +671 -0
- package/templates/apps/api/src/routes/flows/index.ts +202 -0
- package/templates/apps/api/src/routes/flows/recommender.ts +443 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +438 -31
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +10 -10
- package/templates/apps/api/src/routes/leads/index.ts +43 -9
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +558 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +815 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +869 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +221 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +468 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +424 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +707 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +375 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +467 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +566 -0
- package/templates/apps/api/src/routes/project-listings/get.ts +70 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +120 -0
- package/templates/apps/api/src/routes/project-listings/list.ts +47 -0
- package/templates/apps/api/src/routes/project-listings/me.ts +76 -0
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +222 -0
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +97 -0
- package/templates/apps/api/src/routes/project-listings/patch.ts +136 -0
- package/templates/apps/api/src/routes/project-listings/public.ts +52 -0
- package/templates/apps/api/src/routes/project-listings/respond.ts +37 -0
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +22 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +106 -0
- package/templates/apps/api/src/routes/project-listings/submit.ts +122 -0
- package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +490 -0
- package/templates/apps/api/src/routes/schedule/index.ts +249 -0
- package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +385 -0
- package/templates/apps/api/src/routes/slack/actions.ts +177 -0
- package/templates/apps/api/src/routes/slack/index.ts +38 -0
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +16 -6
- package/templates/apps/api/src/routes/test/auth/session.ts +8 -6
- package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +577 -0
- package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +78 -0
- package/templates/apps/api/src/routes/waitlist-experiments/index.ts +41 -0
- package/templates/apps/api/src/routes/waitlist-experiments/list.ts +64 -0
- package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +42 -0
- package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +84 -0
- package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +119 -0
- package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +238 -0
- package/templates/apps/api/src/routes/waitlist-signups/index.ts +101 -0
- package/templates/apps/api/src/routes/webhooks/README.md +35 -12
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +315 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +270 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +248 -20
- package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +356 -0
- package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +671 -0
- package/templates/apps/api/src/routes/workspaces/config.ts +125 -0
- package/templates/apps/api/src/routes/workspaces/index.ts +194 -0
- package/templates/apps/api/src/routes/workspaces/sessions.ts +218 -0
- package/templates/apps/api/src/server.ts +307 -7
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +489 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +475 -0
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +132 -0
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +523 -0
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +359 -0
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +142 -0
- package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +425 -0
- package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +2840 -0
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +393 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +8 -8
- package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +560 -0
- package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +684 -0
- package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +406 -0
- package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +290 -0
- package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +736 -0
- package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +475 -0
- package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +340 -0
- package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +983 -0
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +278 -0
- package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +71 -0
- package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +379 -0
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +941 -0
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +588 -0
- package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +768 -0
- package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +1314 -0
- package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +341 -0
- package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +567 -0
- package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +474 -0
- package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +354 -0
- package/templates/apps/api/src/services/abandon-email.ts +307 -0
- package/templates/apps/api/src/services/aeo-score.ts +288 -0
- package/templates/apps/api/src/services/answer-distributions.ts +138 -0
- package/templates/apps/api/src/services/consent.ts +236 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +523 -0
- package/templates/apps/api/src/services/entitlements.ts +102 -0
- package/templates/apps/api/src/services/flow-aggregate.ts +232 -0
- package/templates/apps/api/src/services/flow-checkouts.ts +123 -0
- package/templates/apps/api/src/services/flow-engine.ts +1296 -0
- package/templates/apps/api/src/services/lead-promotion.ts +176 -0
- package/templates/apps/api/src/services/lead-routing.ts +3 -3
- package/templates/apps/api/src/services/listing-config.ts +102 -0
- package/templates/apps/api/src/services/listing-csv-sweep.ts +455 -0
- package/templates/apps/api/src/services/listing-promotion.ts +342 -0
- package/templates/apps/api/src/services/playbook-compile.ts +398 -0
- package/templates/apps/api/src/services/playbook-render.ts +263 -0
- package/templates/apps/api/src/services/project-listing-decision.ts +510 -0
- package/templates/apps/api/src/services/project-listing-flow.ts +426 -0
- package/templates/apps/api/src/services/project-listing-issue.ts +251 -0
- package/templates/apps/api/src/services/project-listings.ts +1472 -0
- package/templates/apps/api/src/services/recommender-capture.ts +835 -0
- package/templates/apps/api/src/services/scheduling/cal-provider.ts +392 -0
- package/templates/apps/api/src/services/scheduling/index.ts +63 -0
- package/templates/apps/api/src/services/scheduling/types.ts +88 -0
- package/templates/apps/api/src/services/slack-notify.ts +240 -0
- package/templates/apps/api/src/services/tenant-context.ts +451 -0
- package/templates/apps/api/src/services/usage-aggregation.ts +516 -0
- package/templates/apps/api/src/services/usage-credits.ts +340 -0
- package/templates/apps/api/src/services/usage-metering.ts +699 -0
- package/templates/apps/api/src/services/waitlist-dashboard.ts +947 -0
- package/templates/apps/api/src/services/waitlist-experiments.ts +213 -0
- package/templates/apps/api/src/services/waitlist-followup.ts +486 -0
- package/templates/apps/api/src/services/waitlist-scoring.ts +166 -0
- package/templates/apps/api/src/services/waitlist-signups.ts +165 -0
- package/templates/apps/api/src/vercel-handler.ts +165 -0
- package/templates/apps/api/tsconfig.json +0 -12
- package/templates/apps/mcp/CLAUDE.md +19 -9
- package/templates/apps/mcp/LICENSE +201 -0
- package/templates/apps/mcp/__tests__/create_page.test.ts +1 -0
- package/templates/apps/mcp/__tests__/delete_page.test.ts +1 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +105 -0
- package/templates/apps/mcp/__tests__/parity.test.ts +70 -6
- package/templates/apps/mcp/__tests__/request_callback.test.ts +4 -4
- package/templates/apps/mcp/__tests__/schedule_tools.test.ts +242 -0
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +4 -4
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +4 -4
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +47 -0
- package/templates/apps/mcp/__tests__/update_page.test.ts +1 -0
- package/templates/apps/mcp/package.json +6 -4
- package/templates/apps/mcp/src/config/modules.ts +37 -0
- package/templates/apps/mcp/src/server.ts +109 -18
- package/templates/apps/mcp/src/tools/capture_lead.ts +1 -1
- package/templates/apps/mcp/src/tools/create_booking.ts +58 -0
- package/templates/apps/mcp/src/tools/create_page.ts +1 -0
- package/templates/apps/mcp/src/tools/delete_page.ts +1 -0
- package/templates/apps/mcp/src/tools/get_event_meta.ts +55 -0
- package/templates/apps/mcp/src/tools/get_flow.ts +61 -0
- package/templates/apps/mcp/src/tools/index.ts +25 -0
- package/templates/apps/mcp/src/tools/list_availability.ts +67 -0
- package/templates/apps/mcp/src/tools/purchase.ts +3 -0
- package/templates/apps/mcp/src/tools/submit_flow_step.ts +83 -0
- package/templates/apps/mcp/src/tools/update_page.ts +1 -0
- package/templates/apps/mcp/tsconfig.json +1 -9
- package/templates/apps/web/.env.example +44 -0
- package/templates/apps/web/CLAUDE.md +28 -0
- package/templates/apps/web/LICENSE +201 -0
- package/templates/apps/web/__tests__/__mocks__/code-highlight.jest-stub.ts +24 -0
- package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +33 -0
- package/templates/apps/web/__tests__/__mocks__/next-intl.ts +18 -0
- package/templates/apps/web/__tests__/__mocks__/server-only.ts +13 -0
- package/templates/apps/web/__tests__/__mocks__/style-mock.js +4 -0
- package/templates/apps/web/__tests__/__mocks__/vercel-analytics-server.ts +9 -0
- package/templates/apps/web/__tests__/jest-support/import-meta-transformer.cjs +90 -0
- package/templates/apps/web/__tests__/setup.ts +22 -32
- package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +6 -3
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +8 -2
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +260 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +263 -0
- package/templates/apps/web/app/[locale]/(authed)/account/sign-out-button.tsx +39 -0
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +54 -0
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +96 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +309 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +149 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +273 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/gate.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +171 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +179 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +116 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ForbiddenState.tsx +24 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +66 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +61 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +114 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +139 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +85 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +67 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/format.ts +111 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +179 -0
- package/templates/apps/web/app/[locale]/about/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +6 -1
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/case-studies/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/clients/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +21 -1
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +188 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +242 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +29 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/intake-analytics.test.ts +51 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +39 -15
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +21 -3
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +49 -0
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +66 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +14 -4
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/courses/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/downloads/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/events/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/faq/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +265 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +445 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +341 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +287 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +302 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +128 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +254 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +144 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +108 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +532 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +121 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +457 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +396 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +114 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +52 -0
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/integrations/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/launch-service/page.tsx +14 -1
- package/templates/apps/web/app/[locale]/layout.tsx +127 -16
- package/templates/apps/web/app/[locale]/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/partners/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/products/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +10 -0
- package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +110 -0
- package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/projects/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/resources/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +117 -0
- package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +92 -0
- package/templates/apps/web/app/[locale]/schedule/page.tsx +82 -0
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/services/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/sign-in/__tests__/next-path.test.ts +31 -0
- package/templates/apps/web/app/[locale]/sign-in/__tests__/page.test.tsx +152 -0
- package/templates/apps/web/app/[locale]/sign-in/next-path.ts +0 -0
- package/templates/apps/web/app/[locale]/sign-in/page.tsx +83 -0
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +123 -0
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/testimonials/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +93 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +48 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/globals.css +35 -0
- package/templates/apps/web/app/llms.txt/route.ts +2 -1
- package/templates/apps/web/app/og/route.tsx +10 -2
- package/templates/apps/web/auth.ts +8 -0
- package/templates/apps/web/components/sign-out-trigger.tsx +42 -0
- package/templates/apps/web/content/en/blog/README.txt +25 -0
- package/templates/apps/web/content/en/docs/index.mdx +28 -0
- package/templates/apps/web/jest.config.cjs +90 -27
- package/templates/apps/web/lib/__tests__/site-brand.test.ts +119 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +499 -0
- package/templates/apps/web/lib/account-config.ts +13 -0
- package/templates/apps/web/lib/server-api.ts +85 -0
- package/templates/apps/web/lib/site-brand.tsx +121 -0
- package/templates/apps/web/lib/site-theme.ts +74 -0
- package/templates/apps/web/messages/en.json +275 -7
- package/templates/apps/web/middleware.ts +11 -3
- package/templates/apps/web/next.config.mjs +8 -2
- package/templates/apps/web/package.json +17 -7
- package/templates/apps/web/postcss.config.mjs +23 -0
- package/templates/apps/web/public/.well-known/security.txt +3 -3
- package/templates/apps/web/src/lib/routes.ts +15 -2
- package/templates/apps/web/tsconfig.json +1 -16
- package/templates/content/_site.mdx +36 -25
- package/templates/content/en/AboutPage/about.mdx +24 -14
- package/templates/content/en/ContactPage/contact.mdx +23 -14
- package/templates/content/en/FAQPage/faq.mdx +10 -392
- package/templates/content/en/HomePage/home.mdx +14 -285
- package/templates/database/CHANGELOG.md +443 -0
- package/templates/database/LICENSE +201 -0
- package/templates/database/README.md +3 -1
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +392 -0
- package/templates/database/__tests__/flows-presets.test.ts +173 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +146 -0
- package/templates/database/__tests__/seed.test.ts +88 -0
- package/templates/database/__tests__/user-cascade.test.ts +19 -4
- package/templates/database/auth/schema.prisma +521 -0
- package/templates/database/config/schema.prisma +234 -0
- package/templates/database/content/schema.prisma +263 -0
- package/templates/database/flows/listing-diagnostic.ts +182 -0
- package/templates/database/flows/presets.ts +315 -0
- package/templates/database/inbox/__tests__/schema.test.ts +4 -0
- package/templates/database/inbox/schema.prisma +574 -3
- package/templates/database/jest.config.cjs +8 -0
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +201 -0
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +67 -0
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +46 -0
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +25 -0
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +49 -0
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +197 -0
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +32 -0
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +22 -0
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +151 -0
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +223 -0
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +22 -0
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +32 -0
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +40 -0
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +4 -0
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +2 -0
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +2 -0
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +29 -0
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +6 -0
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +39 -0
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +24 -0
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +3 -0
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +33 -0
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +30 -0
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +47 -0
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +16 -0
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +47 -0
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +10 -0
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +26 -0
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +90 -0
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +174 -0
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +14 -0
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +25 -0
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
- package/templates/database/ops/schema.prisma +27 -0
- package/templates/database/package.json +42 -4
- package/templates/database/prisma.config.ts +1 -1
- package/templates/database/schema.prisma +19 -435
- package/templates/database/seed-test.ts +14 -4
- package/templates/database/seed.ts +148 -0
- package/templates/database/tenancy.ts +45 -0
- package/templates/database/tsconfig.flows.json +15 -0
- package/templates/database/tsconfig.json +7 -1
- package/templates/database/tsconfig.tenancy.json +14 -0
- package/templates/package.json +30 -55
- package/templates/pnpm-workspace.yaml +0 -3
- package/templates/project.yaml +1 -1
- package/templates/tsconfig.json +1 -37
- package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +0 -94
- package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +0 -75
- package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +0 -82
- package/templates/apps/web/__tests__/auth/env-helpers.test.ts +0 -168
- package/templates/apps/web/__tests__/authority-list-routes.test.tsx +0 -208
- package/templates/apps/web/__tests__/better-stack.test.ts +0 -66
- package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +0 -87
- package/templates/apps/web/__tests__/blog-source-populated.test.ts +0 -130
- package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +0 -60
- package/templates/apps/web/__tests__/google-analytics.test.tsx +0 -104
- package/templates/apps/web/__tests__/i18n/middleware.test.ts +0 -119
- package/templates/apps/web/__tests__/i18n/routing.test.ts +0 -114
- package/templates/apps/web/__tests__/i18n/translations.test.ts +0 -191
- package/templates/apps/web/__tests__/json-ld.test.ts +0 -98
- package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +0 -580
- package/templates/apps/web/__tests__/layout-stealth.test.ts +0 -39
- package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +0 -103
- package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +0 -114
- package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +0 -94
- package/templates/apps/web/__tests__/page.test.tsx +0 -230
- package/templates/apps/web/__tests__/products-services-phase1.test.tsx +0 -321
- package/templates/apps/web/__tests__/projects-routes.test.tsx +0 -392
- package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +0 -475
- package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +0 -53
- package/templates/apps/web/__tests__/routes/blog-route.test.tsx +0 -191
- package/templates/apps/web/__tests__/routes/llms-txt.test.ts +0 -123
- package/templates/apps/web/__tests__/routes/robots-txt.test.ts +0 -52
- package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +0 -78
- package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +0 -203
- package/templates/apps/web/__tests__/section-routes.test.tsx +0 -693
- package/templates/apps/web/__tests__/security-headers.test.ts +0 -201
- package/templates/apps/web/__tests__/sentry-config.test.ts +0 -116
- package/templates/apps/web/__tests__/sitemap-locales.test.ts +0 -415
- package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +0 -844
- package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +0 -120
- package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +0 -202
- package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +0 -152
- package/templates/apps/web/content/en/blog/index.mdx +0 -15
- package/templates/apps/web/content/en/blog/launching-ailk.mdx +0 -20
- package/templates/apps/web/content/en/docs/getting-started.mdx +0 -30
- package/templates/content/.gitkeep +0 -0
- package/templates/content/CLAUDE.md +0 -85
- package/templates/content/ar/HomePage/home.mdx +0 -123
- package/templates/content/ar/_site.mdx +0 -35
- package/templates/content/de/_site.mdx +0 -29
- package/templates/content/en/.gitkeep +0 -0
- package/templates/content/en/AboutPage/about.schema.json +0 -82
- package/templates/content/en/BookCall/book-call.mdx +0 -19
- package/templates/content/en/BookCall/book-call.schema.json +0 -121
- package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +0 -20
- package/templates/content/en/CareersList/careers.mdx +0 -24
- package/templates/content/en/CaseStudiesList/case-studies.mdx +0 -22
- package/templates/content/en/CaseStudiesList/showcase.mdx +0 -8
- package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +0 -32
- package/templates/content/en/CaseStudy/schema-builder-migration.mdx +0 -41
- package/templates/content/en/ClientsList/clients.mdx +0 -38
- package/templates/content/en/ContactPage/contact.schema.json +0 -100
- package/templates/content/en/ContactPoint/primary.mdx +0 -14
- package/templates/content/en/Course/aeo-fundamentals.mdx +0 -37
- package/templates/content/en/Course/schema-builder-workshop.mdx +0 -62
- package/templates/content/en/CoursesList/courses.mdx +0 -22
- package/templates/content/en/DownloadsList/downloads.mdx +0 -22
- package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +0 -39
- package/templates/content/en/Event/ailk-launch-day-2026.mdx +0 -35
- package/templates/content/en/EventsList/events.mdx +0 -26
- package/templates/content/en/FAQPage/faq.schema.json +0 -231
- package/templates/content/en/GlossaryList/glossary.mdx +0 -19
- package/templates/content/en/GlossaryList/glossary.schema.json +0 -300
- package/templates/content/en/HomePage/home.schema.json +0 -88
- package/templates/content/en/HomePage/launch-service.mdx +0 -475
- package/templates/content/en/HomePage/launch-service.schema.json +0 -138
- package/templates/content/en/HowTo/add-mdx-content.mdx +0 -38
- package/templates/content/en/HowTo/configure-aeo-scoring.mdx +0 -35
- package/templates/content/en/HowTo/set-up-json-ld.mdx +0 -36
- package/templates/content/en/Integration/github-actions.mdx +0 -31
- package/templates/content/en/Integration/openai-api.mdx +0 -26
- package/templates/content/en/Integration/vercel.mdx +0 -32
- package/templates/content/en/IntegrationsList/integrations.mdx +0 -22
- package/templates/content/en/JobPosting/content-strategist.mdx +0 -46
- package/templates/content/en/JobPosting/developer-advocate.mdx +0 -47
- package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +0 -57
- package/templates/content/en/LandingPage/aeo-audit.mdx +0 -19
- package/templates/content/en/LandingPage/aeo-audit.schema.json +0 -92
- package/templates/content/en/LandingPage/agency-waitlist.mdx +0 -340
- package/templates/content/en/LandingPage/agency-waitlist.schema.json +0 -152
- package/templates/content/en/LandingPage/developers.mdx +0 -19
- package/templates/content/en/LandingPage/developers.schema.json +0 -109
- package/templates/content/en/LandingPage/for-agencies.mdx +0 -390
- package/templates/content/en/LandingPage/for-agencies.schema.json +0 -107
- package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +0 -417
- package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +0 -105
- package/templates/content/en/LandingPage/for-businesses.mdx +0 -19
- package/templates/content/en/LandingPage/for-businesses.schema.json +0 -96
- package/templates/content/en/LandingPage/platform.mdx +0 -369
- package/templates/content/en/LandingPage/platform.schema.json +0 -166
- package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +0 -19
- package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +0 -102
- package/templates/content/en/LandingPage/vs-wordpress.mdx +0 -19
- package/templates/content/en/LandingPage/vs-wordpress.schema.json +0 -53
- package/templates/content/en/NewsletterSignup/newsletter.mdx +0 -236
- package/templates/content/en/NewsletterSignup/newsletter.schema.json +0 -91
- package/templates/content/en/Organization/site-owner.mdx +0 -16
- package/templates/content/en/Partner/prisma.mdx +0 -31
- package/templates/content/en/Partner/supabase.mdx +0 -30
- package/templates/content/en/Partner/vercel.mdx +0 -28
- package/templates/content/en/PartnersList/partners.mdx +0 -22
- package/templates/content/en/Person/founder.mdx +0 -18
- package/templates/content/en/Pricing/pricing.mdx +0 -538
- package/templates/content/en/Pricing/pricing.schema.json +0 -209
- package/templates/content/en/PrivacyPage/privacy.mdx +0 -165
- package/templates/content/en/PrivacyPage/privacy.schema.json +0 -64
- package/templates/content/en/Product/ailk.mdx +0 -19
- package/templates/content/en/Product/ailk.schema.json +0 -94
- package/templates/content/en/Product/api-launch-kit.mdx +0 -26
- package/templates/content/en/Product/launch-kit.mdx +0 -28
- package/templates/content/en/ProductsList/products.mdx +0 -26
- package/templates/content/en/Project/ailk-phase-2.mdx +0 -54
- package/templates/content/en/ProjectsList/projects.mdx +0 -25
- package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +0 -27
- package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +0 -28
- package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +0 -39
- package/templates/content/en/ResourcesHub/resources.mdx +0 -22
- package/templates/content/en/Service/consulting.mdx +0 -22
- package/templates/content/en/Service/implementation.mdx +0 -30
- package/templates/content/en/Service/launch-service.mdx +0 -232
- package/templates/content/en/Service/launch.mdx +0 -228
- package/templates/content/en/ServicesList/services.mdx +0 -84
- package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +0 -44
- package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +0 -47
- package/templates/content/en/SoftwareProduct/ailk-schema.mdx +0 -49
- package/templates/content/en/TeamList/team.mdx +0 -63
- package/templates/content/en/TeamMember/founder.mdx +0 -72
- package/templates/content/en/TermsPage/terms.mdx +0 -19
- package/templates/content/en/TermsPage/terms.schema.json +0 -99
- package/templates/content/en/TestimonialsList/testimonials.mdx +0 -44
- package/templates/content/en/UseCasesList/use-cases.mdx +0 -30
- package/templates/content/en/Whitepaper/ax-first-product-development.mdx +0 -27
- package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +0 -27
- package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +0 -31
- package/templates/content/en-XA/AboutPage/about.mdx +0 -8
- package/templates/content/en-XA/FAQPage/faq.mdx +0 -8
- package/templates/content/en-XA/HomePage/home.mdx +0 -8
- package/templates/content/es/_site.mdx +0 -30
- package/templates/content/fr/_site.mdx +0 -30
- package/templates/content/ja/_site.mdx +0 -30
- package/templates/content/pt-BR/AboutPage/about.mdx +0 -8
- package/templates/content/pt-BR/FAQPage/faq.mdx +0 -8
- package/templates/content/pt-BR/HomePage/home.mdx +0 -23
|
@@ -11,21 +11,30 @@
|
|
|
11
11
|
|
|
12
12
|
/// Lead — anonymous lead submission (Phase B submit_lead tool / B6 route)
|
|
13
13
|
model Lead {
|
|
14
|
-
id String @id @default(
|
|
14
|
+
id String @id @default(dbgenerated("('lead_'::text || (uuidv7())::text)")) @db.Text
|
|
15
15
|
siteId String @map("site_id")
|
|
16
16
|
source String
|
|
17
17
|
email String
|
|
18
18
|
payload Json
|
|
19
19
|
createdAt DateTime @default(now()) @map("created_at")
|
|
20
20
|
|
|
21
|
+
// Lead → user promotion (#3586). Plain scalars — no cross-namespace
|
|
22
|
+
// @relation into auth.User (ADR 0009 invariant #1). Set by
|
|
23
|
+
// apps/api/src/services/lead-promotion.ts once the reconciling user's
|
|
24
|
+
// email is verified and the lead's siteId resolves to one of that user's
|
|
25
|
+
// own workspaces (tenant-safety, spec D3).
|
|
26
|
+
promotedUserId String? @map("promoted_user_id")
|
|
27
|
+
promotedAt DateTime? @map("promoted_at")
|
|
28
|
+
|
|
21
29
|
@@index([siteId])
|
|
22
30
|
@@index([createdAt])
|
|
31
|
+
@@index([email])
|
|
23
32
|
@@map("leads")
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
/// Subscriber — newsletter subscription (Phase B subscribe_newsletter tool / B6 route)
|
|
27
36
|
model Subscriber {
|
|
28
|
-
id String @id @default(
|
|
37
|
+
id String @id @default(dbgenerated("('subscr_'::text || (uuidv7())::text)")) @db.Text
|
|
29
38
|
siteId String @map("site_id")
|
|
30
39
|
email String
|
|
31
40
|
source String
|
|
@@ -38,7 +47,7 @@ model Subscriber {
|
|
|
38
47
|
|
|
39
48
|
/// Callback — callback request (Phase B request_callback tool / B6 route)
|
|
40
49
|
model Callback {
|
|
41
|
-
id String @id @default(
|
|
50
|
+
id String @id @default(dbgenerated("('callbk_'::text || (uuidv7())::text)")) @db.Text
|
|
42
51
|
siteId String @map("site_id")
|
|
43
52
|
name String
|
|
44
53
|
phone String
|
|
@@ -50,3 +59,565 @@ model Callback {
|
|
|
50
59
|
@@index([createdAt])
|
|
51
60
|
@@map("callbacks")
|
|
52
61
|
}
|
|
62
|
+
|
|
63
|
+
/// FlowDefinition — server-owned flow config, one live definition per
|
|
64
|
+
/// (siteId, slug) (#3956 D2). No versioning at this layer — a session
|
|
65
|
+
/// snapshots the config at start (see FlowSession.configSnapshot), so a
|
|
66
|
+
/// mid-flight definition edit never corrupts a live session.
|
|
67
|
+
model FlowDefinition {
|
|
68
|
+
id String @id @default(dbgenerated("('flow_'::text || (uuidv7())::text)")) @db.Text
|
|
69
|
+
siteId String @map("site_id")
|
|
70
|
+
slug String
|
|
71
|
+
config Json
|
|
72
|
+
// #5036 D6d — the listing this definition was projected from, when it was.
|
|
73
|
+
// Null for every flow that is not a listing diagnostic. Before this column
|
|
74
|
+
// the only link was that the generated flow's slug HAPPENED to equal the
|
|
75
|
+
// listing's slug, which nothing enforced; a slug collision would have
|
|
76
|
+
// attributed a submission to the wrong founder. ADR 0009 forbids a relation
|
|
77
|
+
// across the namespace boundary, so this is a plain string — but a stated
|
|
78
|
+
// one rather than a coincidence.
|
|
79
|
+
listingId String? @map("listing_id")
|
|
80
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
81
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
82
|
+
|
|
83
|
+
@@unique([siteId, slug])
|
|
84
|
+
@@map("flow_definitions")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// FlowSession — a flow walk in progress or completed; the server-side
|
|
88
|
+
/// partial-lead record (#3956 D2/D9). `configSnapshot` pins the definition at
|
|
89
|
+
/// session start; `state` accumulates each landed step's values immediately
|
|
90
|
+
/// on submission (the per-step persistence this feature exists for).
|
|
91
|
+
/// `email` is denormalized from the landed answers the moment a
|
|
92
|
+
/// `mapsTo:'email'` field lands, so the #3962 abandon-email query
|
|
93
|
+
/// (`email != null AND completedAt == null`) is one indexed read.
|
|
94
|
+
model FlowSession {
|
|
95
|
+
id String @id @default(dbgenerated("('flsess_'::text || (uuidv7())::text)")) @db.Text
|
|
96
|
+
siteId String @map("site_id")
|
|
97
|
+
// Informational only — never joined on (ADR 0009 D1: logical, not FK,
|
|
98
|
+
// isolation). Session resumption + completion key on flowSlug, not this
|
|
99
|
+
// column; deliberate, per code-review finding on #3956's acceptance PR.
|
|
100
|
+
flowId String @map("flow_id")
|
|
101
|
+
flowSlug String @map("flow_slug")
|
|
102
|
+
configSnapshot Json @map("config_snapshot")
|
|
103
|
+
state Json @default("{}")
|
|
104
|
+
email String?
|
|
105
|
+
// The session's locale (#3962 D3) — set once at create from the first
|
|
106
|
+
// landed step's `input.locale ?? configSnapshot.defaultLocale`; not
|
|
107
|
+
// updated on later steps. Null on pre-migration rows (no backfill; the
|
|
108
|
+
// abandon-email sweep falls back to the live config's defaultLocale).
|
|
109
|
+
locale String?
|
|
110
|
+
// Server-derived segment (#3959 D3) — set from the segmentation step's
|
|
111
|
+
// band map at landing time, persisted in the same write as `state`. No
|
|
112
|
+
// index: #3960/#3961/#3962 read it off rows they already fetch by
|
|
113
|
+
// existing indexes (subtraction-first, spec Clarifications).
|
|
114
|
+
segment String?
|
|
115
|
+
// D2 (#4663) — every derived flag, re-derived and persisted at each
|
|
116
|
+
// landing (`{ [flagName]: true }`). `{}` on a snapshot declaring no
|
|
117
|
+
// `flags` (the regression floor — byte-identical to a pre-#4663 row).
|
|
118
|
+
flags Json @default("{}")
|
|
119
|
+
// D6 (#4663) — the mid-flow aeo_score step's persisted result (`ready` /
|
|
120
|
+
// `unavailable`). Null until the step lands, or after a URL change resets
|
|
121
|
+
// it (D3). Never `computing` — that status never reaches the wire from
|
|
122
|
+
// this repo (packages/validation/src/schemas/aeo-score.ts).
|
|
123
|
+
aeoScore Json? @map("aeo_score")
|
|
124
|
+
leadId String? @map("lead_id")
|
|
125
|
+
// #5036 D6d — copied from the FlowDefinition at session create, so a
|
|
126
|
+
// submission is attributable to ONE listing without going through the slug.
|
|
127
|
+
// Null for every session on a flow that is not a listing diagnostic.
|
|
128
|
+
listingId String? @map("listing_id")
|
|
129
|
+
completedAt DateTime? @map("completed_at")
|
|
130
|
+
// Abandon-email audit stamp (#3962 D5) — set at most once by the sweep,
|
|
131
|
+
// per outcome ('sent' | 'dark' | 'failed'). The `abandonEmailStatus ==
|
|
132
|
+
// null` selection term is what makes the sweep idempotent across any
|
|
133
|
+
// number of overlapping runs; 'failed' is terminal (no auto-retry).
|
|
134
|
+
abandonEmailStatus String? @map("abandon_email_status")
|
|
135
|
+
abandonEmailAt DateTime? @map("abandon_email_at")
|
|
136
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
137
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
138
|
+
|
|
139
|
+
@@index([siteId])
|
|
140
|
+
@@index([siteId, completedAt])
|
|
141
|
+
@@index([email])
|
|
142
|
+
// Backs the abandon-email sweep's per-definition selection query
|
|
143
|
+
// (apps/api/src/services/abandon-email.ts — flowId equality + completedAt
|
|
144
|
+
// null + abandonEmailStatus null + updatedAt window), run once per
|
|
145
|
+
// FlowDefinition on every sweep — code-review finding on #3962's
|
|
146
|
+
// acceptance PR (without this, that filter set is a full table scan).
|
|
147
|
+
@@index([flowId, completedAt, abandonEmailStatus])
|
|
148
|
+
// #5036 D6d — backs the listing-to-submissions read the explicit reference
|
|
149
|
+
// exists to make possible.
|
|
150
|
+
@@index([listingId])
|
|
151
|
+
@@map("flow_sessions")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// DeliverableAsset — an operator-uploaded asset the funnel's `deliverables`
|
|
155
|
+
/// config block references by `key` (#3961 D1). Bytes live IN THE DATABASE
|
|
156
|
+
/// (Postgres bytea) behind one internal resolver — zero new infrastructure;
|
|
157
|
+
/// reversibility is structural (a later blob-storage swap changes only the
|
|
158
|
+
/// resolver + the upload CLI, never the config contract, grant model, or
|
|
159
|
+
/// email link shape). Uploaded via the `deliverable:upload` CLI
|
|
160
|
+
/// (apps/api/src/bin/deliverable-upload.ts) only — no HTTP upload route.
|
|
161
|
+
model DeliverableAsset {
|
|
162
|
+
id String @id @default(dbgenerated("('dasset_'::text || (uuidv7())::text)")) @db.Text
|
|
163
|
+
siteId String @map("site_id")
|
|
164
|
+
// The assetKey a flow config's deliverables.assets entry references.
|
|
165
|
+
key String
|
|
166
|
+
// Download filename (Content-Disposition). Stored RAW at upload time
|
|
167
|
+
// (the `deliverable:upload` CLI writes the operator's `--filename` or the
|
|
168
|
+
// uploaded file's own basename verbatim) — sanitization to a safe charset
|
|
169
|
+
// happens at SERVE time only, in the download route's `sanitizeFilename`
|
|
170
|
+
// (apps/api/src/routes/deliverables/index.ts, #4002). Never trust this
|
|
171
|
+
// column as pre-sanitized; the serve-time guard is load-bearing, not
|
|
172
|
+
// redundant.
|
|
173
|
+
filename String
|
|
174
|
+
contentType String @map("content_type")
|
|
175
|
+
bytes Bytes
|
|
176
|
+
size Int
|
|
177
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
178
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
179
|
+
|
|
180
|
+
@@unique([siteId, key])
|
|
181
|
+
@@map("deliverable_assets")
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// DeliverableGrant — the capability token minted per fulfilled lead (#3961
|
|
185
|
+
/// D5). The grant's id IS the token: the download route
|
|
186
|
+
/// (GET /v1/deliverables/:grantId) looks up bytes by grant id alone, so
|
|
187
|
+
/// nothing is publicly enumerable by key, filename, or any other guessable
|
|
188
|
+
/// value — only the uuidv7-random grant id, delivered once via the lead's
|
|
189
|
+
/// own email. `assetId`/`leadId` are stable-id refs (ADR 0009 invariant 1
|
|
190
|
+
/// posture) — no cross-model `@relation`.
|
|
191
|
+
model DeliverableGrant {
|
|
192
|
+
id String @id @default(dbgenerated("('dgrant_'::text || (uuidv7())::text)")) @db.Text
|
|
193
|
+
siteId String @map("site_id")
|
|
194
|
+
assetId String @map("asset_id")
|
|
195
|
+
leadId String @map("lead_id")
|
|
196
|
+
sessionId String @map("session_id")
|
|
197
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
198
|
+
// Post-mint send-outcome stamp (#3998 D1) — set at most once, by the
|
|
199
|
+
// send-and-stamp tail shared between fulfillDeliverable and the
|
|
200
|
+
// deliverable:resend CLI. 'sent' | 'send_failed' | 'sender_unconfigured' —
|
|
201
|
+
// the exact post-mint subset of deliverable-fulfillment.ts's
|
|
202
|
+
// FulfillmentOutcome union; carried here (not a Prisma enum — this repo
|
|
203
|
+
// uses none), mirroring FlowSession.abandonEmailStatus/abandonEmailAt.
|
|
204
|
+
// NULL means the process never reached a post-mint outcome — the crash
|
|
205
|
+
// window between mint and send (#3961 D5's unhandled consequence) that
|
|
206
|
+
// this column makes queryable. This is the SELECTION term for the resend
|
|
207
|
+
// CLI's list mode only; a resend itself is an explicit operator act on one
|
|
208
|
+
// named grant and is NOT guarded by this column (#3998 D3).
|
|
209
|
+
sendStatus String? @map("send_status")
|
|
210
|
+
sendAt DateTime? @map("send_at")
|
|
211
|
+
|
|
212
|
+
// Backs the deliverable:resend CLI's list-mode query (siteId + sendStatus
|
|
213
|
+
// IS NULL) — without this, that filter set is a full table scan (the same
|
|
214
|
+
// lesson #3962's code-review finding established for FlowSession).
|
|
215
|
+
@@index([siteId, sendStatus])
|
|
216
|
+
@@map("deliverable_grants")
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// WaitlistExperiment — one ratified experiment config per (siteId, key), the
|
|
220
|
+
/// runtime twin of a site-plan waitlistExperiments[] entry (#4661 D3/D7).
|
|
221
|
+
/// `config` is the validated experimentConfig (D1) with the three registers
|
|
222
|
+
/// EMBEDDED. Written only via upsertWaitlistExperiment (D5).
|
|
223
|
+
model WaitlistExperiment {
|
|
224
|
+
id String @id @default(dbgenerated("('wlexp_'::text || (uuidv7())::text)")) @db.Text
|
|
225
|
+
siteId String @map("site_id")
|
|
226
|
+
key String
|
|
227
|
+
mode String // lead-capture | qualifying | paid
|
|
228
|
+
status String // draft | live | closed
|
|
229
|
+
config Json
|
|
230
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
231
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
232
|
+
|
|
233
|
+
@@unique([siteId, key])
|
|
234
|
+
@@map("waitlist_experiments")
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// WaitlistScore — the rules layer's output for one signup (#4661 D3; written
|
|
238
|
+
/// by A2 #4662). leadId is a plain scalar into inbox.Lead (ADR 0009). One row
|
|
239
|
+
/// per lead — re-scoring overwrites, never appends.
|
|
240
|
+
model WaitlistScore {
|
|
241
|
+
id String @id @default(dbgenerated("('wlscore_'::text || (uuidv7())::text)")) @db.Text
|
|
242
|
+
siteId String @map("site_id")
|
|
243
|
+
leadId String @map("lead_id")
|
|
244
|
+
experimentKey String @map("experiment_key")
|
|
245
|
+
waitlistKey String @map("waitlist_key")
|
|
246
|
+
tier String
|
|
247
|
+
score Int
|
|
248
|
+
categoryScores Json @map("category_scores")
|
|
249
|
+
ruleTrace Json @map("rule_trace")
|
|
250
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
251
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
252
|
+
|
|
253
|
+
// #5036 D6d — one score row per (lead, EXPERIMENT), not per lead.
|
|
254
|
+
//
|
|
255
|
+
// `@@unique([leadId])` was right for a single instrument and wrong for a
|
|
256
|
+
// marketplace of them: a visitor who joins two founders' waitlists is one
|
|
257
|
+
// lead with two scores, and the narrower constraint permitted one. The
|
|
258
|
+
// second write silently overwrote the first, so one founder's evidence
|
|
259
|
+
// disappeared and their weekly CSV under-reported with no error raised.
|
|
260
|
+
// Scoping the key to the experiment keeps the overwrite-on-retake behaviour
|
|
261
|
+
// that `scoreCompletedSignup` relies on, per project rather than globally.
|
|
262
|
+
@@unique([leadId, experimentKey])
|
|
263
|
+
@@index([siteId, experimentKey, waitlistKey])
|
|
264
|
+
@@map("waitlist_scores")
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// PlaybookInstance — a compiled, person-scoped Playbook (FEAT-074 U4a,
|
|
268
|
+
/// #4715). Append-only: `scoreCompletedSignup` (A2) fires the compile on
|
|
269
|
+
/// every `targeting-maturity` completion, so a retake writes a NEW row
|
|
270
|
+
/// rather than overwriting — unlike WaitlistScore, which is
|
|
271
|
+
/// @@unique([leadId, experimentKey]) and overwritten on retake of the SAME
|
|
272
|
+
/// experiment (D2; scoped per experiment by #5036 D6d). `tier`/`ruleTrace` are SNAPSHOTS of the
|
|
273
|
+
/// WaitlistScore at compile time; `scoreId` is kept only as the provenance
|
|
274
|
+
/// pointer to the (possibly since-overwritten) live row. `source` is the
|
|
275
|
+
/// U1 `playbook-source` machine-block object; `markdown` is the rendered
|
|
276
|
+
/// `PLAYBOOK-<slug>.md` body U4b (#4718) reads to render + deliver.
|
|
277
|
+
model PlaybookInstance {
|
|
278
|
+
id String @id @default(dbgenerated("('pbk_'::text || (uuidv7())::text)")) @db.Text
|
|
279
|
+
siteId String @map("site_id")
|
|
280
|
+
leadId String @map("lead_id")
|
|
281
|
+
experimentKey String @map("experiment_key")
|
|
282
|
+
scoreId String @map("score_id")
|
|
283
|
+
tier String
|
|
284
|
+
level Int
|
|
285
|
+
ruleTrace Json @map("rule_trace")
|
|
286
|
+
source Json
|
|
287
|
+
markdown String @db.Text
|
|
288
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
289
|
+
|
|
290
|
+
@@index([siteId, leadId, createdAt])
|
|
291
|
+
@@map("playbook_instances")
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// WaitlistFollowUpSend — the audit stamp for one attempted follow-up email
|
|
295
|
+
/// (#4732 D1/D5). `@@unique([leadId, followUpKey])` IS the sweep's
|
|
296
|
+
/// idempotency guarantee (mirrors WaitlistScore's own `@@unique([leadId])`
|
|
297
|
+
/// shape) — the stateless `runWaitlistFollowUpSweep` (#4732)
|
|
298
|
+
/// selects candidates by the ABSENCE of a matching row, so a re-run never
|
|
299
|
+
/// double-sends. `status` is 'sent' | 'failed' (no Prisma enum — this repo
|
|
300
|
+
/// uses none, mirroring DeliverableGrant.sendStatus's free-string
|
|
301
|
+
/// convention); 'failed' is terminal, no auto-retry (the abandon-email
|
|
302
|
+
/// sweep's own posture) — a suppressed/skipped candidate (wrong tier, not
|
|
303
|
+
/// yet due, a booked call, or unsubscribed) is NEVER stamped here at all, so
|
|
304
|
+
/// it is re-evaluated on every later sweep run until it either sends or its
|
|
305
|
+
/// completedAt/offsetDays window is permanently past.
|
|
306
|
+
model WaitlistFollowUpSend {
|
|
307
|
+
id String @id @default(dbgenerated("('wlfu_'::text || (uuidv7())::text)")) @db.Text
|
|
308
|
+
siteId String @map("site_id")
|
|
309
|
+
leadId String @map("lead_id")
|
|
310
|
+
followUpKey String @map("follow_up_key")
|
|
311
|
+
sentAt DateTime @map("sent_at")
|
|
312
|
+
status String
|
|
313
|
+
|
|
314
|
+
@@unique([leadId, followUpKey])
|
|
315
|
+
@@index([siteId])
|
|
316
|
+
@@map("waitlist_followup_sends")
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// ListingCsvSend — the weekly founder-CSV send stamp (#5001). One row per
|
|
320
|
+
/// DELIVERED CSV for one ProjectListing: `sentAt` is the watermark the next
|
|
321
|
+
/// sweep selects after (signups whose WaitlistScore landed at or before it are
|
|
322
|
+
/// already in a delivered CSV), `rowCount` is how many rows that CSV carried,
|
|
323
|
+
/// and `grantId` is the DeliverableGrant whose id is the capability token in
|
|
324
|
+
/// the founder's download link. Plain-scalar refs to ProjectListing /
|
|
325
|
+
/// DeliverableGrant (ADR 0009 invariant 1) — no cross-model @relation. A
|
|
326
|
+
/// listing with no new signups gets no row, so "no new rows ⇒ no send" is
|
|
327
|
+
/// visible in the data, not only in the sweep's control flow.
|
|
328
|
+
model ListingCsvSend {
|
|
329
|
+
id String @id @default(dbgenerated("('lcsv_'::text || (uuidv7())::text)")) @db.Text
|
|
330
|
+
listingId String @map("listing_id")
|
|
331
|
+
sentAt DateTime @map("sent_at")
|
|
332
|
+
rowCount Int @map("row_count")
|
|
333
|
+
grantId String @map("grant_id")
|
|
334
|
+
|
|
335
|
+
@@index([listingId, sentAt])
|
|
336
|
+
@@map("listing_csv_sends")
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/// FlowCheckout — the recommender buyer record (#4837 D2/D4): one row per
|
|
340
|
+
/// checkout ATTEMPT. `amountMinor` is the server's own `price().total` × 100
|
|
341
|
+
/// (D6); `quote` is the full WaitlistPriceQuote beside it, so an onboarding
|
|
342
|
+
/// reader sees the seat line and credit line, not only a total. `status` is
|
|
343
|
+
/// 'created' | 'completed' | 'expired' (no Prisma enum — this repo uses none,
|
|
344
|
+
/// mirroring DeliverableGrant.sendStatus's free-string convention); the
|
|
345
|
+
/// webhook (#4838) advances it with a guarded update on 'created'. Plain-scalar
|
|
346
|
+
/// refs to Lead / FlowSession (ADR 0009 invariant 1) — no cross-model
|
|
347
|
+
/// @relation. `permalink` is the answers-only payload string the visitor's
|
|
348
|
+
/// recommendation was rendered from (#4770). Written only by
|
|
349
|
+
/// apps/api/src/services/recommender-capture.ts.
|
|
350
|
+
model FlowCheckout {
|
|
351
|
+
id String @id @default(dbgenerated("('flchk_'::text || (uuidv7())::text)")) @db.Text
|
|
352
|
+
siteId String @map("site_id")
|
|
353
|
+
flowSlug String @map("flow_slug")
|
|
354
|
+
experimentKey String @map("experiment_key")
|
|
355
|
+
sessionId String @map("session_id")
|
|
356
|
+
leadId String @map("lead_id")
|
|
357
|
+
offerKey String @map("offer_key")
|
|
358
|
+
seats Int
|
|
359
|
+
period String
|
|
360
|
+
creditStop Json @map("credit_stop")
|
|
361
|
+
amountMinor Int @map("amount_minor")
|
|
362
|
+
currency String
|
|
363
|
+
quote Json
|
|
364
|
+
permalink String @db.Text
|
|
365
|
+
stripeSessionId String? @map("stripe_session_id")
|
|
366
|
+
// The hosted Checkout page URL an identical resubmission is replayed with
|
|
367
|
+
// (#4912 D6) — stamped in the same write as `stripeSessionId`, because a
|
|
368
|
+
// Checkout Session URL cannot be reconstructed from its id.
|
|
369
|
+
checkoutUrl String? @db.Text @map("checkout_url")
|
|
370
|
+
// The server-derived fingerprint of the checkout request (#4912 D5): a
|
|
371
|
+
// SHA-256 hex digest, never the request fields themselves. Null on
|
|
372
|
+
// pre-#4912 rows and on a row whose key a reconcile released.
|
|
373
|
+
idempotencyKey String? @map("idempotency_key")
|
|
374
|
+
stripeCustomerId String? @map("stripe_customer_id")
|
|
375
|
+
status String
|
|
376
|
+
completedAt DateTime? @map("completed_at")
|
|
377
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
378
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
379
|
+
|
|
380
|
+
// The webhook's join (#4838) — nullable, so `created` rows with no session
|
|
381
|
+
// id yet never collide.
|
|
382
|
+
@@unique([stripeSessionId])
|
|
383
|
+
// The /checkout idempotency guarantee (#4912 D5), tenant-scoped so a
|
|
384
|
+
// fingerprint can never reach another site's row. Nullable, so pre-#4912
|
|
385
|
+
// rows and reconciled rows never collide.
|
|
386
|
+
@@unique([siteId, idempotencyKey])
|
|
387
|
+
// The dashboard's checkedOut read (#4839 D3): siteId + experimentKey + status.
|
|
388
|
+
@@index([siteId, experimentKey, status])
|
|
389
|
+
// Onboarding's read: everything a lead bought.
|
|
390
|
+
@@index([leadId])
|
|
391
|
+
@@map("flow_checkouts")
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/// ProjectListing — the founder's application row for the project-listings
|
|
395
|
+
/// workflow (#4998): apply → Slack approve/reject → build issue → data-driven
|
|
396
|
+
/// page + diagnostic → weekly CSV. `leadId` is a plain scalar ref into this
|
|
397
|
+
/// namespace's own `Lead` (ADR 0009 invariant 1) — the POST route writes both
|
|
398
|
+
/// rows via `routeLead` (kind `submit`, source `projects-apply`) then this
|
|
399
|
+
/// row, never the reverse. `questions` is the diagnostic template instance
|
|
400
|
+
/// (Q2–Q7 + up to 3 extras, bounded by `projectListingQuestionsSchema` in
|
|
401
|
+
/// `@working-theory/validation`) per
|
|
402
|
+
/// docs/decisions/2026-09-07-project-listings-workflow.md (consumer repo,
|
|
403
|
+
/// salessmyth/samhenry — this row and its routes vendor down, operator
|
|
404
|
+
/// 2026-09-07 option 2). `status` is a free string (`applied` | `approved` |
|
|
405
|
+
/// `rejected` | `active`, no Prisma enum — this repo uses none, mirroring
|
|
406
|
+
/// `FlowCheckout.status`'s convention). Those are the four STORED values
|
|
407
|
+
/// (#5033): the fifth lifecycle state, `closed`, is COMPUTED in the read
|
|
408
|
+
/// from `windowEndsAt` and never written, so an expired listing cannot stay
|
|
409
|
+
/// publicly open because a scheduled job failed. `draft` is defined so the
|
|
410
|
+
/// vocabulary never migrates again and is reachable through no route today.
|
|
411
|
+
/// `decidedAt`/`decidedBy` stamp the approve/reject moment, and an approve
|
|
412
|
+
/// also stamps `windowEndsAt = decidedAt + windowDays`. `windowDays` is the
|
|
413
|
+
/// applicant's requested window ALREADY CLAMPED at create against the site's
|
|
414
|
+
/// `SiteListingConfig` (database/config/schema.prisma) — the config itself
|
|
415
|
+
/// is never accepted from a request body. `issueUrl` (the build issue,
|
|
416
|
+
/// #5000) and `experimentId` (the generated flow's waitlist experiment,
|
|
417
|
+
/// #5001) are null until their respective downstream unit lands.
|
|
418
|
+
/// `featured` is the operator-set shelf flag, surfaced on the public read
|
|
419
|
+
/// only where the site's config enables it. `headline`, `logoMarkUrl` and
|
|
420
|
+
/// `logoWordmarkUrl` are the listing card's presentation fields; the two
|
|
421
|
+
/// logo columns ship nullable with NO upload path (#5033 D5 — no storage
|
|
422
|
+
/// dependency exists, and accepting arbitrary files from anonymous
|
|
423
|
+
/// strangers deserves its own review), and a card with both absent renders
|
|
424
|
+
/// a designed name-only treatment. `slackChannel`/`slackTs`
|
|
425
|
+
/// (#4999) are the posted application message's coordinates, stamped by
|
|
426
|
+
/// `postListingApplication` right after `chat.postMessage` succeeds, so the
|
|
427
|
+
/// decision handler can `chat.update` the same message in place; null when
|
|
428
|
+
/// Slack is unconfigured or the post failed. Still out of scope here: the
|
|
429
|
+
/// GitHub issue (#5000), the diagnostic flow + weekly CSV (#5001).
|
|
430
|
+
model ProjectListing {
|
|
431
|
+
id String @id @default(dbgenerated("('prjl_'::text || (uuidv7())::text)")) @db.Text
|
|
432
|
+
siteId String @map("site_id")
|
|
433
|
+
// #5036 — written at SUBMIT, not at start. The founder's contact details are
|
|
434
|
+
// already captured on THIS row at step one, so a `Lead` at step one adds no
|
|
435
|
+
// capture; what it does add is an operator notification email per abandoned
|
|
436
|
+
// first step, on an anonymous route (PR #5042 security review, finding 4).
|
|
437
|
+
// `routeLead` remains the one Lead write path; it just runs later.
|
|
438
|
+
leadId String? @map("lead_id")
|
|
439
|
+
// #5036 D1 — every field the SUBMIT payload requires is nullable at rest.
|
|
440
|
+
// Validation moved from write-time to submit-time when the application
|
|
441
|
+
// became a ten-step draft that saves as it goes; a half-filled draft is a
|
|
442
|
+
// legitimate row, and a NOT NULL here would make one unstorable. Only
|
|
443
|
+
// `submit` asserts completeness.
|
|
444
|
+
slug String?
|
|
445
|
+
name String?
|
|
446
|
+
oneLiner String? @map("one_liner")
|
|
447
|
+
problem String? @db.Text
|
|
448
|
+
url String?
|
|
449
|
+
// #5036 D2 — the six-rung product ladder that replaced the four-value
|
|
450
|
+
// `stage`. The migration remaps every old value by an explicit map; there
|
|
451
|
+
// is no shim, because the two ladders do not align rung-for-rung.
|
|
452
|
+
product String?
|
|
453
|
+
// #5036 D3/D5 — the two segmentation axes shared with the diagnostic.
|
|
454
|
+
commitment String?
|
|
455
|
+
teamComposition String? @map("team_composition")
|
|
456
|
+
// #5036 D5 — exactly three {question, answer} entries, the detail page's
|
|
457
|
+
// fixed FAQ slot.
|
|
458
|
+
faqs Json?
|
|
459
|
+
anythingElse String? @db.Text @map("anything_else")
|
|
460
|
+
// #5036 D6 — a legal control, not a contact detail: visitors answering this
|
|
461
|
+
// founder's diagnostic are owed the identity and address of the party
|
|
462
|
+
// collecting their data. Carried on the PUBLIC read for that reason.
|
|
463
|
+
//
|
|
464
|
+
// #5279 — still the single-line DISPLAY string. Where the six part columns
|
|
465
|
+
// below are written, this is COMPOSED from them server-side; where they are
|
|
466
|
+
// not, it is the line the founder typed, verbatim.
|
|
467
|
+
address String?
|
|
468
|
+
// #5279 — the structured parts of the address above, beside it rather than
|
|
469
|
+
// instead of it. Nullable for the same reason as the #5036 D1 block above: a
|
|
470
|
+
// row written before #5279 has no parts, and none may ever be GUESSED from
|
|
471
|
+
// its `address` string. Parts compose into a line deterministically; a line
|
|
472
|
+
// does not decompose into parts, and a wrong split silently corrupts the
|
|
473
|
+
// party identification the listing's privacy notice points at — so the
|
|
474
|
+
// migration deliberately backfills nothing and a null part stays null for
|
|
475
|
+
// the life of the row. Composition runs one way only, in `draftUpdateData`
|
|
476
|
+
// (apps/api/src/services/project-listings.ts).
|
|
477
|
+
addressLine1 String? @map("address_line1")
|
|
478
|
+
addressLine2 String? @map("address_line2")
|
|
479
|
+
addressCity String? @map("address_city")
|
|
480
|
+
addressRegion String? @map("address_region")
|
|
481
|
+
addressPostalCode String? @map("address_postal_code")
|
|
482
|
+
addressCountry String? @map("address_country")
|
|
483
|
+
questions Json?
|
|
484
|
+
// #5285 — the answers to the site's OWN questions: one flat JSON object of
|
|
485
|
+
// `{ questionId: answer }` for everything a site asks that the PLATFORM does
|
|
486
|
+
// not read. The field contract (packages/validation/.../listing-
|
|
487
|
+
// application-contract.ts) names what the platform reads and therefore what
|
|
488
|
+
// earns a column; everything else was previously unaskable without a
|
|
489
|
+
// migration and a release, which is what this column removes.
|
|
490
|
+
//
|
|
491
|
+
// BOUNDED, not free-form, and that is the point: the write path is
|
|
492
|
+
// anonymous (a resume token and nothing else), so an unbounded JSON bag on
|
|
493
|
+
// it is a place for a stranger to park arbitrary text in the table. The
|
|
494
|
+
// server accepts only ids the SITE declared in its own `SiteListingConfig`,
|
|
495
|
+
// at that site's own count and length caps, and refuses the whole request
|
|
496
|
+
// otherwise — so what lands here is never larger than the questions the
|
|
497
|
+
// operator chose to ask. Null on every row written before #5285 and on
|
|
498
|
+
// every site that asks nothing of its own.
|
|
499
|
+
//
|
|
500
|
+
// Only the `public`-visibility subset rides the public read; an
|
|
501
|
+
// `operator-only` answer reaches the dashboard and the review payload and
|
|
502
|
+
// stops there.
|
|
503
|
+
siteAnswers Json? @map("site_answers")
|
|
504
|
+
founderName String @map("founder_name")
|
|
505
|
+
founderEmail String @map("founder_email")
|
|
506
|
+
founderCompany String? @map("founder_company")
|
|
507
|
+
status String
|
|
508
|
+
// #5036 D1/D6 — when the founder ticked step one's consent box. Never null:
|
|
509
|
+
// a row cannot exist without it, because `start` requires it.
|
|
510
|
+
consentAt DateTime @default(now()) @map("consent_at")
|
|
511
|
+
// #5036 D1 — sha-256 of the opaque resume token, never the token itself.
|
|
512
|
+
// The token is the ONLY credential on an anonymous surface, so the column
|
|
513
|
+
// holds a digest an attacker who reads the table still cannot present.
|
|
514
|
+
// Cleared at submit, which makes a leaked token inert the moment the draft
|
|
515
|
+
// stops being a draft.
|
|
516
|
+
resumeTokenHash String? @unique @map("resume_token_hash")
|
|
517
|
+
headline String?
|
|
518
|
+
logoMarkUrl String? @map("logo_mark_url")
|
|
519
|
+
logoWordmarkUrl String? @map("logo_wordmark_url")
|
|
520
|
+
featured Boolean @default(false)
|
|
521
|
+
decidedAt DateTime? @map("decided_at")
|
|
522
|
+
decidedBy String? @map("decided_by")
|
|
523
|
+
// The operator's copy-edit audit trio. A listing that is `requested` or
|
|
524
|
+
// `active` can have its PUBLISHED COPY trimmed by the operator (headline,
|
|
525
|
+
// oneLiner, problem, faqs, questions) — the third review outcome beside
|
|
526
|
+
// approve and reject. `copyEditedAt`/`copyEditedBy` mirror the decision pair
|
|
527
|
+
// directly above and are re-stamped on every edit.
|
|
528
|
+
//
|
|
529
|
+
// `submittedCopy` is WRITE-ONCE, on the FIRST edit only: it holds those five
|
|
530
|
+
// fields exactly as the applicant submitted them. Null for the untouched
|
|
531
|
+
// majority, which is why "what did the applicant actually send" reads as the
|
|
532
|
+
// snapshot if present and the row itself otherwise. Never cleared, so the
|
|
533
|
+
// submitted copy stays recoverable for the life of the row.
|
|
534
|
+
submittedCopy Json? @map("submitted_copy")
|
|
535
|
+
copyEditedAt DateTime? @map("copy_edited_at")
|
|
536
|
+
copyEditedBy String? @map("copy_edited_by")
|
|
537
|
+
issueUrl String? @map("issue_url")
|
|
538
|
+
// #5036 D6d — the experiment's KEY, not its id. `WaitlistScore` holds
|
|
539
|
+
// `experimentKey`, so holding the id here meant every listing-to-submissions
|
|
540
|
+
// read fetched the experiment purely to learn its key. One hop, the same
|
|
541
|
+
// string on both sides. ADR 0009 forbids a relation across the namespace
|
|
542
|
+
// boundary, so this is a plain string matched by convention — which is
|
|
543
|
+
// exactly why it has to be the string the other side actually stores.
|
|
544
|
+
experimentKey String? @map("experiment_key")
|
|
545
|
+
windowDays Int? @map("window_days")
|
|
546
|
+
windowEndsAt DateTime? @map("window_ends_at")
|
|
547
|
+
// The posted application message's coordinates (#4999) — set by
|
|
548
|
+
// `postListingApplication` right after `chat.postMessage` succeeds, so the
|
|
549
|
+
// Approve/Reject decision handler can `chat.update` the SAME message in
|
|
550
|
+
// place. Null when Slack is unconfigured or the post failed (create never
|
|
551
|
+
// rolls back on a Slack failure).
|
|
552
|
+
slackChannel String? @map("slack_channel")
|
|
553
|
+
slackTs String? @map("slack_ts")
|
|
554
|
+
// Listing -> owner-user link (#5295). Plain scalars — no cross-namespace
|
|
555
|
+
// @relation into auth.User (ADR 0009 invariant #1), exactly as
|
|
556
|
+
// `Lead.promotedUserId` / `promotedAt` above. Set by
|
|
557
|
+
// apps/api/src/services/listing-promotion.ts once the reconciling user's own
|
|
558
|
+
// email is VERIFIED, that verified address matches `founderEmail`, and this
|
|
559
|
+
// row's `siteId` resolves to one of that user's own workspaces.
|
|
560
|
+
//
|
|
561
|
+
// `founderEmail` is an UNVERIFIED string typed into an anonymous form, so it
|
|
562
|
+
// can never by itself say who owns the row: two people who type the same
|
|
563
|
+
// address are two people. This column is the only ownership claim any read
|
|
564
|
+
// may join on, and nothing derives it from `founderEmail` at read time.
|
|
565
|
+
//
|
|
566
|
+
// Nullable PERMANENTLY, not pending a backfill. A listing submitted by
|
|
567
|
+
// someone who never signs in has no owner for the life of the row, and that
|
|
568
|
+
// is a legitimate resting state: a backfill from `founderEmail` would assert
|
|
569
|
+
// a link nobody proved, which is the bug this column exists to remove.
|
|
570
|
+
ownerUserId String? @map("owner_user_id")
|
|
571
|
+
ownerLinkedAt DateTime? @map("owner_linked_at")
|
|
572
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
573
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
574
|
+
|
|
575
|
+
@@unique([siteId, slug])
|
|
576
|
+
@@index([siteId, status])
|
|
577
|
+
// #5036 D7 — the concurrent-listing limit counts a founder's non-terminal
|
|
578
|
+
// rows on every submit, from an anonymous route. Indexed so a stranger
|
|
579
|
+
// cannot make that count a table scan.
|
|
580
|
+
@@index([siteId, founderEmail])
|
|
581
|
+
// The self-scoped read's whole WHERE clause (#5295) is `{ ownerUserId }` —
|
|
582
|
+
// the owner link IS the scope, so no site term joins it. Single-column by
|
|
583
|
+
// design: the promotion match (`founderEmail` + `siteId` + null owner) is
|
|
584
|
+
// already served by the (site_id, founder_email) index above.
|
|
585
|
+
@@index([ownerUserId])
|
|
586
|
+
@@map("project_listings")
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/// ConsentGrant — one person's answer to one consent purpose, with the exact
|
|
590
|
+
/// wording they read (#5275). APPEND-ONLY: current state is the newest row
|
|
591
|
+
/// per (subject, purpose), so a later withdrawal never destroys the evidence
|
|
592
|
+
/// of the earlier grant (D9) — no `updatedAt`, and deliberately NO unique
|
|
593
|
+
/// constraint on subject + purpose. DECLINES ARE STORED as `granted: false`
|
|
594
|
+
/// (D8): an absent row cannot distinguish "they said no" from "we never
|
|
595
|
+
/// asked", and on an optional purpose that distinction is the only thing the
|
|
596
|
+
/// record is for. The subject is a type + id pair of plain scalars
|
|
597
|
+
/// (`project_listing` / `subscriber` / …) with NO cross-namespace @relation
|
|
598
|
+
/// (D10, ADR 0009 invariant 1) — which is what lets a new surface adopt
|
|
599
|
+
/// consent with a service call and no migration. `statement` is the resolved
|
|
600
|
+
/// text DENORMALIZED at write time (D7): a pointer into the site's config row
|
|
601
|
+
/// would prove nothing once that row is edited, and `statementHash` groups
|
|
602
|
+
/// identical wordings. `locale` is the locale the statement was actually
|
|
603
|
+
/// served in, not the one requested. `purposeId` is the site-config registry
|
|
604
|
+
/// id — the join key across surfaces. Written only by
|
|
605
|
+
/// apps/api/src/services/consent.ts.
|
|
606
|
+
model ConsentGrant {
|
|
607
|
+
id String @id @default(dbgenerated("('cnsnt_'::text || (uuidv7())::text)")) @db.Text
|
|
608
|
+
siteId String @map("site_id")
|
|
609
|
+
subjectType String @map("subject_type")
|
|
610
|
+
subjectId String @map("subject_id")
|
|
611
|
+
purposeId String @map("purpose_id")
|
|
612
|
+
granted Boolean
|
|
613
|
+
locale String
|
|
614
|
+
statement String @db.Text
|
|
615
|
+
statementHash String @map("statement_hash")
|
|
616
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
617
|
+
|
|
618
|
+
// "What did this subject agree to" — every row for one subject, newest last.
|
|
619
|
+
@@index([siteId, subjectType, subjectId])
|
|
620
|
+
// "Who agreed to this purpose" — the cross-surface count the id exists for (D1/D2).
|
|
621
|
+
@@index([siteId, purposeId])
|
|
622
|
+
@@map("consent_grants")
|
|
623
|
+
}
|
|
@@ -6,6 +6,14 @@ module.exports = {
|
|
|
6
6
|
testPathIgnorePatterns: ["/node_modules/", "/generated/"],
|
|
7
7
|
moduleFileExtensions: ["ts", "js", "json"],
|
|
8
8
|
moduleNameMapper: {
|
|
9
|
+
// Map the validation workspace package to its TypeScript source so
|
|
10
|
+
// ts-jest compiles it directly (#3958) — mirrors apps/api/jest.config.cjs's
|
|
11
|
+
// identical mapping. The published dist is ESM-only and this package's
|
|
12
|
+
// CJS ts-jest transform can't load it; database/flows/presets.ts's test
|
|
13
|
+
// needs the real flowConfigSchema value at runtime (a value import, not a
|
|
14
|
+
// type-only one — unlike ../tenancy.ts's `import type { Id }` from
|
|
15
|
+
// @working-theory/ids, which erases to nothing and needs no mapping).
|
|
16
|
+
"^@working-theory/validation$": "<rootDir>/../packages/validation/src/index.ts",
|
|
9
17
|
"^(\\.{1,2}/.*)\\.js$": "$1",
|
|
10
18
|
},
|
|
11
19
|
transform: {
|