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.
Files changed (620) hide show
  1. package/README.md +124 -3
  2. package/component-catalog.md +456 -0
  3. package/dist/cli.js +92 -16
  4. package/dist/component-catalog.d.ts +11 -0
  5. package/dist/component-catalog.js +39 -0
  6. package/dist/copy.d.ts +7 -0
  7. package/dist/copy.js +24 -0
  8. package/dist/derive-architecture.d.ts +13 -0
  9. package/dist/derive-architecture.js +238 -0
  10. package/dist/index.d.ts +7 -2
  11. package/dist/index.js +19 -2
  12. package/dist/lib/extract-module-bundle.d.ts +16 -0
  13. package/dist/lib/extract-module-bundle.js +184 -0
  14. package/dist/lib/fetch-module.d.ts +55 -0
  15. package/dist/lib/fetch-module.js +125 -0
  16. package/dist/lib/module-license-gate.d.ts +48 -0
  17. package/dist/lib/module-license-gate.js +67 -0
  18. package/dist/lib/module-tier.d.ts +35 -0
  19. package/dist/lib/module-tier.js +37 -0
  20. package/dist/module-architecture.d.ts +175 -0
  21. package/dist/module-architecture.js +663 -0
  22. package/dist/parse-args.d.ts +47 -1
  23. package/dist/parse-args.js +143 -2
  24. package/dist/programmatic.d.ts +160 -3
  25. package/dist/programmatic.js +196 -7
  26. package/dist/surfaces.d.ts +211 -0
  27. package/dist/surfaces.js +532 -0
  28. package/dist/sync-routes.d.ts +92 -0
  29. package/dist/sync-routes.js +350 -0
  30. package/package.json +12 -5
  31. package/templates/.claude/rules/architecture.md +6 -2
  32. package/templates/.claude/skills/README.md +9 -9
  33. package/templates/.claude/skills/add-schema/SKILL.md +14 -7
  34. package/templates/.claude/skills/add-schema/references/schema-types.md +109 -6
  35. package/templates/.claude/skills/provision-config/SKILL.md +1 -1
  36. package/templates/.claude/skills/scaffold-commerce/SKILL.md +498 -1
  37. package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +370 -0
  38. package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +376 -0
  39. package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +471 -0
  40. package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +365 -0
  41. package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +60 -0
  42. package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +446 -0
  43. package/templates/.claude/skills/suggest-site-pages/references/categories.md +4 -4
  44. package/templates/.env.example +126 -2
  45. package/templates/CLAUDE.md +11 -5
  46. package/templates/README.md +1 -1
  47. package/templates/apps/api/.env.example +38 -0
  48. package/templates/apps/api/CLAUDE.md +78 -1
  49. package/templates/apps/api/LICENSE +201 -0
  50. package/templates/apps/api/api/index.ts +6 -141
  51. package/templates/apps/api/jest.config.cjs +16 -0
  52. package/templates/apps/api/package.json +22 -6
  53. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +141 -0
  54. package/templates/apps/api/src/__tests__/server.test.ts +46 -0
  55. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +211 -0
  56. package/templates/apps/api/src/bin/abandon-sweep.ts +42 -0
  57. package/templates/apps/api/src/bin/deliverable-resend.ts +144 -0
  58. package/templates/apps/api/src/bin/deliverable-upload.ts +114 -0
  59. package/templates/apps/api/src/bin/followup-sweep.ts +44 -0
  60. package/templates/apps/api/src/bin/listing-csv-sweep.ts +46 -0
  61. package/templates/apps/api/src/bin/seed-presets.ts +58 -0
  62. package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +104 -0
  63. package/templates/apps/api/src/bin/session-retention-sweep.ts +46 -0
  64. package/templates/apps/api/src/cli.ts +5 -11
  65. package/templates/apps/api/src/config/index.ts +35 -0
  66. package/templates/apps/api/src/config/modules.ts +43 -0
  67. package/templates/apps/api/src/lib/__mocks__/prisma-client.js +11 -0
  68. package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -0
  69. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +2 -2
  70. package/templates/apps/api/src/lib/site.ts +16 -0
  71. package/templates/apps/api/src/lib/stripe.ts +22 -0
  72. package/templates/apps/api/src/lib/tenant-db.ts +225 -0
  73. package/templates/apps/api/src/lib/ws-token.ts +91 -0
  74. package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +7 -1
  75. package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +7 -1
  76. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +91 -4
  77. package/templates/apps/api/src/middleware/auth.ts +43 -8
  78. package/templates/apps/api/src/middleware/rate-limit.ts +78 -3
  79. package/templates/apps/api/src/middleware/tenant.ts +99 -0
  80. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +92 -0
  81. package/templates/apps/api/src/openapi/spec.ts +312 -3
  82. package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +175 -0
  83. package/templates/apps/api/src/routes/aeo/index.ts +107 -0
  84. package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +5 -2
  85. package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +22 -3
  86. package/templates/apps/api/src/routes/auth/tokens.ts +13 -1
  87. package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +450 -0
  88. package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +262 -0
  89. package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +436 -0
  90. package/templates/apps/api/src/routes/billing/index.ts +36 -0
  91. package/templates/apps/api/src/routes/billing/portal.ts +182 -0
  92. package/templates/apps/api/src/routes/billing/read.ts +75 -0
  93. package/templates/apps/api/src/routes/billing/usage.ts +153 -0
  94. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +99 -0
  95. package/templates/apps/api/src/routes/checkout/sessions.ts +20 -8
  96. package/templates/apps/api/src/routes/content/create.ts +1 -0
  97. package/templates/apps/api/src/routes/content/delete.ts +1 -0
  98. package/templates/apps/api/src/routes/content/index.ts +42 -6
  99. package/templates/apps/api/src/routes/content/update.ts +1 -0
  100. package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +393 -0
  101. package/templates/apps/api/src/routes/deliverables/index.ts +200 -0
  102. package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +443 -0
  103. package/templates/apps/api/src/routes/flow-checkouts/index.ts +82 -0
  104. package/templates/apps/api/src/routes/flows/README.md +147 -0
  105. package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +752 -0
  106. package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +671 -0
  107. package/templates/apps/api/src/routes/flows/index.ts +202 -0
  108. package/templates/apps/api/src/routes/flows/recommender.ts +443 -0
  109. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +438 -31
  110. package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +10 -10
  111. package/templates/apps/api/src/routes/leads/index.ts +43 -9
  112. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +558 -0
  113. package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +815 -0
  114. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +869 -0
  115. package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +221 -0
  116. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +468 -0
  117. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +424 -0
  118. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +707 -0
  119. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +375 -0
  120. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +467 -0
  121. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +566 -0
  122. package/templates/apps/api/src/routes/project-listings/get.ts +70 -0
  123. package/templates/apps/api/src/routes/project-listings/index.ts +120 -0
  124. package/templates/apps/api/src/routes/project-listings/list.ts +47 -0
  125. package/templates/apps/api/src/routes/project-listings/me.ts +76 -0
  126. package/templates/apps/api/src/routes/project-listings/patch-copy.ts +222 -0
  127. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +97 -0
  128. package/templates/apps/api/src/routes/project-listings/patch.ts +136 -0
  129. package/templates/apps/api/src/routes/project-listings/public.ts +52 -0
  130. package/templates/apps/api/src/routes/project-listings/respond.ts +37 -0
  131. package/templates/apps/api/src/routes/project-listings/resume-token.ts +22 -0
  132. package/templates/apps/api/src/routes/project-listings/start.ts +106 -0
  133. package/templates/apps/api/src/routes/project-listings/submit.ts +122 -0
  134. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +490 -0
  135. package/templates/apps/api/src/routes/schedule/index.ts +249 -0
  136. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +385 -0
  137. package/templates/apps/api/src/routes/slack/actions.ts +177 -0
  138. package/templates/apps/api/src/routes/slack/index.ts +38 -0
  139. package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +16 -6
  140. package/templates/apps/api/src/routes/test/auth/session.ts +8 -6
  141. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +577 -0
  142. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +78 -0
  143. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +41 -0
  144. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +64 -0
  145. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +42 -0
  146. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +84 -0
  147. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +119 -0
  148. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +238 -0
  149. package/templates/apps/api/src/routes/waitlist-signups/index.ts +101 -0
  150. package/templates/apps/api/src/routes/webhooks/README.md +35 -12
  151. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +315 -0
  152. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +270 -0
  153. package/templates/apps/api/src/routes/webhooks/stripe.ts +248 -20
  154. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +356 -0
  155. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +671 -0
  156. package/templates/apps/api/src/routes/workspaces/config.ts +125 -0
  157. package/templates/apps/api/src/routes/workspaces/index.ts +194 -0
  158. package/templates/apps/api/src/routes/workspaces/sessions.ts +218 -0
  159. package/templates/apps/api/src/server.ts +307 -7
  160. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +489 -0
  161. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +475 -0
  162. package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +132 -0
  163. package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
  164. package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
  165. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +523 -0
  166. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +359 -0
  167. package/templates/apps/api/src/services/__tests__/entitlements.test.ts +142 -0
  168. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +425 -0
  169. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +2840 -0
  170. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +393 -0
  171. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +8 -8
  172. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +560 -0
  173. package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +684 -0
  174. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +406 -0
  175. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +290 -0
  176. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +736 -0
  177. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +475 -0
  178. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +340 -0
  179. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +983 -0
  180. package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +278 -0
  181. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +71 -0
  182. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +379 -0
  183. package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +941 -0
  184. package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +588 -0
  185. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +768 -0
  186. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +1314 -0
  187. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +341 -0
  188. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +567 -0
  189. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +474 -0
  190. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +354 -0
  191. package/templates/apps/api/src/services/abandon-email.ts +307 -0
  192. package/templates/apps/api/src/services/aeo-score.ts +288 -0
  193. package/templates/apps/api/src/services/answer-distributions.ts +138 -0
  194. package/templates/apps/api/src/services/consent.ts +236 -0
  195. package/templates/apps/api/src/services/deliverable-fulfillment.ts +523 -0
  196. package/templates/apps/api/src/services/entitlements.ts +102 -0
  197. package/templates/apps/api/src/services/flow-aggregate.ts +232 -0
  198. package/templates/apps/api/src/services/flow-checkouts.ts +123 -0
  199. package/templates/apps/api/src/services/flow-engine.ts +1296 -0
  200. package/templates/apps/api/src/services/lead-promotion.ts +176 -0
  201. package/templates/apps/api/src/services/lead-routing.ts +3 -3
  202. package/templates/apps/api/src/services/listing-config.ts +102 -0
  203. package/templates/apps/api/src/services/listing-csv-sweep.ts +455 -0
  204. package/templates/apps/api/src/services/listing-promotion.ts +342 -0
  205. package/templates/apps/api/src/services/playbook-compile.ts +398 -0
  206. package/templates/apps/api/src/services/playbook-render.ts +263 -0
  207. package/templates/apps/api/src/services/project-listing-decision.ts +510 -0
  208. package/templates/apps/api/src/services/project-listing-flow.ts +426 -0
  209. package/templates/apps/api/src/services/project-listing-issue.ts +251 -0
  210. package/templates/apps/api/src/services/project-listings.ts +1472 -0
  211. package/templates/apps/api/src/services/recommender-capture.ts +835 -0
  212. package/templates/apps/api/src/services/scheduling/cal-provider.ts +392 -0
  213. package/templates/apps/api/src/services/scheduling/index.ts +63 -0
  214. package/templates/apps/api/src/services/scheduling/types.ts +88 -0
  215. package/templates/apps/api/src/services/slack-notify.ts +240 -0
  216. package/templates/apps/api/src/services/tenant-context.ts +451 -0
  217. package/templates/apps/api/src/services/usage-aggregation.ts +516 -0
  218. package/templates/apps/api/src/services/usage-credits.ts +340 -0
  219. package/templates/apps/api/src/services/usage-metering.ts +699 -0
  220. package/templates/apps/api/src/services/waitlist-dashboard.ts +947 -0
  221. package/templates/apps/api/src/services/waitlist-experiments.ts +213 -0
  222. package/templates/apps/api/src/services/waitlist-followup.ts +486 -0
  223. package/templates/apps/api/src/services/waitlist-scoring.ts +166 -0
  224. package/templates/apps/api/src/services/waitlist-signups.ts +165 -0
  225. package/templates/apps/api/src/vercel-handler.ts +165 -0
  226. package/templates/apps/api/tsconfig.json +0 -12
  227. package/templates/apps/mcp/CLAUDE.md +19 -9
  228. package/templates/apps/mcp/LICENSE +201 -0
  229. package/templates/apps/mcp/__tests__/create_page.test.ts +1 -0
  230. package/templates/apps/mcp/__tests__/delete_page.test.ts +1 -0
  231. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +105 -0
  232. package/templates/apps/mcp/__tests__/parity.test.ts +70 -6
  233. package/templates/apps/mcp/__tests__/request_callback.test.ts +4 -4
  234. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +242 -0
  235. package/templates/apps/mcp/__tests__/submit_lead.test.ts +4 -4
  236. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +4 -4
  237. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +47 -0
  238. package/templates/apps/mcp/__tests__/update_page.test.ts +1 -0
  239. package/templates/apps/mcp/package.json +6 -4
  240. package/templates/apps/mcp/src/config/modules.ts +37 -0
  241. package/templates/apps/mcp/src/server.ts +109 -18
  242. package/templates/apps/mcp/src/tools/capture_lead.ts +1 -1
  243. package/templates/apps/mcp/src/tools/create_booking.ts +58 -0
  244. package/templates/apps/mcp/src/tools/create_page.ts +1 -0
  245. package/templates/apps/mcp/src/tools/delete_page.ts +1 -0
  246. package/templates/apps/mcp/src/tools/get_event_meta.ts +55 -0
  247. package/templates/apps/mcp/src/tools/get_flow.ts +61 -0
  248. package/templates/apps/mcp/src/tools/index.ts +25 -0
  249. package/templates/apps/mcp/src/tools/list_availability.ts +67 -0
  250. package/templates/apps/mcp/src/tools/purchase.ts +3 -0
  251. package/templates/apps/mcp/src/tools/submit_flow_step.ts +83 -0
  252. package/templates/apps/mcp/src/tools/update_page.ts +1 -0
  253. package/templates/apps/mcp/tsconfig.json +1 -9
  254. package/templates/apps/web/.env.example +44 -0
  255. package/templates/apps/web/CLAUDE.md +28 -0
  256. package/templates/apps/web/LICENSE +201 -0
  257. package/templates/apps/web/__tests__/__mocks__/code-highlight.jest-stub.ts +24 -0
  258. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +33 -0
  259. package/templates/apps/web/__tests__/__mocks__/next-intl.ts +18 -0
  260. package/templates/apps/web/__tests__/__mocks__/server-only.ts +13 -0
  261. package/templates/apps/web/__tests__/__mocks__/style-mock.js +4 -0
  262. package/templates/apps/web/__tests__/__mocks__/vercel-analytics-server.ts +9 -0
  263. package/templates/apps/web/__tests__/jest-support/import-meta-transformer.cjs +90 -0
  264. package/templates/apps/web/__tests__/setup.ts +22 -32
  265. package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +6 -3
  266. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +8 -2
  267. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +260 -0
  268. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +263 -0
  269. package/templates/apps/web/app/[locale]/(authed)/account/sign-out-button.tsx +39 -0
  270. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +54 -0
  271. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +96 -0
  272. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +309 -0
  273. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +149 -0
  274. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +273 -0
  275. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/gate.test.tsx +172 -0
  276. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +171 -0
  277. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +179 -0
  278. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +116 -0
  279. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ForbiddenState.tsx +24 -0
  280. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +66 -0
  281. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +61 -0
  282. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +114 -0
  283. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +139 -0
  284. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +85 -0
  285. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +67 -0
  286. package/templates/apps/web/app/[locale]/(authed)/waitlist/format.ts +111 -0
  287. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +179 -0
  288. package/templates/apps/web/app/[locale]/about/page.tsx +10 -1
  289. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +6 -1
  290. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +9 -0
  291. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  292. package/templates/apps/web/app/[locale]/careers/page.tsx +9 -1
  293. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +9 -0
  294. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  295. package/templates/apps/web/app/[locale]/case-studies/page.tsx +13 -1
  296. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +11 -1
  297. package/templates/apps/web/app/[locale]/clients/page.tsx +9 -1
  298. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +21 -1
  299. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +188 -0
  300. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +242 -0
  301. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +29 -0
  302. package/templates/apps/web/app/[locale]/contact/__tests__/intake-analytics.test.ts +51 -0
  303. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +39 -15
  304. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +21 -3
  305. package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +49 -0
  306. package/templates/apps/web/app/[locale]/contact/intake-script.ts +66 -0
  307. package/templates/apps/web/app/[locale]/contact/page.tsx +14 -4
  308. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +13 -1
  309. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  310. package/templates/apps/web/app/[locale]/courses/page.tsx +9 -1
  311. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +1 -0
  312. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +11 -1
  313. package/templates/apps/web/app/[locale]/downloads/page.tsx +13 -1
  314. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +9 -0
  315. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +12 -2
  316. package/templates/apps/web/app/[locale]/events/page.tsx +9 -1
  317. package/templates/apps/web/app/[locale]/faq/page.tsx +10 -1
  318. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +265 -0
  319. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +445 -0
  320. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +341 -0
  321. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +287 -0
  322. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +302 -0
  323. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +128 -0
  324. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +254 -0
  325. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +144 -0
  326. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +108 -0
  327. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +532 -0
  328. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +121 -0
  329. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +457 -0
  330. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +396 -0
  331. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +114 -0
  332. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +52 -0
  333. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +13 -1
  334. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  335. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +9 -0
  336. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  337. package/templates/apps/web/app/[locale]/integrations/page.tsx +13 -1
  338. package/templates/apps/web/app/[locale]/launch-service/page.tsx +14 -1
  339. package/templates/apps/web/app/[locale]/layout.tsx +127 -16
  340. package/templates/apps/web/app/[locale]/page.tsx +10 -1
  341. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +9 -0
  342. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  343. package/templates/apps/web/app/[locale]/partners/page.tsx +9 -1
  344. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +9 -0
  345. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  346. package/templates/apps/web/app/[locale]/products/page.tsx +9 -1
  347. package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +13 -1
  348. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +10 -0
  349. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +110 -0
  350. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +96 -0
  351. package/templates/apps/web/app/[locale]/projects/page.tsx +13 -1
  352. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +9 -0
  353. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +12 -2
  354. package/templates/apps/web/app/[locale]/resources/page.tsx +13 -1
  355. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +117 -0
  356. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +96 -0
  357. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +92 -0
  358. package/templates/apps/web/app/[locale]/schedule/page.tsx +82 -0
  359. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +9 -0
  360. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  361. package/templates/apps/web/app/[locale]/services/page.tsx +9 -1
  362. package/templates/apps/web/app/[locale]/sign-in/__tests__/next-path.test.ts +31 -0
  363. package/templates/apps/web/app/[locale]/sign-in/__tests__/page.test.tsx +152 -0
  364. package/templates/apps/web/app/[locale]/sign-in/next-path.ts +0 -0
  365. package/templates/apps/web/app/[locale]/sign-in/page.tsx +83 -0
  366. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +123 -0
  367. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +13 -1
  368. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  369. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +13 -1
  370. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  371. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +11 -1
  372. package/templates/apps/web/app/[locale]/testimonials/page.tsx +13 -1
  373. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +93 -0
  374. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +48 -0
  375. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +11 -1
  376. package/templates/apps/web/app/[locale]/use-cases/page.tsx +13 -1
  377. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +13 -1
  378. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  379. package/templates/apps/web/app/globals.css +35 -0
  380. package/templates/apps/web/app/llms.txt/route.ts +2 -1
  381. package/templates/apps/web/app/og/route.tsx +10 -2
  382. package/templates/apps/web/auth.ts +8 -0
  383. package/templates/apps/web/components/sign-out-trigger.tsx +42 -0
  384. package/templates/apps/web/content/en/blog/README.txt +25 -0
  385. package/templates/apps/web/content/en/docs/index.mdx +28 -0
  386. package/templates/apps/web/jest.config.cjs +90 -27
  387. package/templates/apps/web/lib/__tests__/site-brand.test.ts +119 -0
  388. package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
  389. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +499 -0
  390. package/templates/apps/web/lib/account-config.ts +13 -0
  391. package/templates/apps/web/lib/server-api.ts +85 -0
  392. package/templates/apps/web/lib/site-brand.tsx +121 -0
  393. package/templates/apps/web/lib/site-theme.ts +74 -0
  394. package/templates/apps/web/messages/en.json +275 -7
  395. package/templates/apps/web/middleware.ts +11 -3
  396. package/templates/apps/web/next.config.mjs +8 -2
  397. package/templates/apps/web/package.json +17 -7
  398. package/templates/apps/web/postcss.config.mjs +23 -0
  399. package/templates/apps/web/public/.well-known/security.txt +3 -3
  400. package/templates/apps/web/src/lib/routes.ts +15 -2
  401. package/templates/apps/web/tsconfig.json +1 -16
  402. package/templates/content/_site.mdx +36 -25
  403. package/templates/content/en/AboutPage/about.mdx +24 -14
  404. package/templates/content/en/ContactPage/contact.mdx +23 -14
  405. package/templates/content/en/FAQPage/faq.mdx +10 -392
  406. package/templates/content/en/HomePage/home.mdx +14 -285
  407. package/templates/database/CHANGELOG.md +443 -0
  408. package/templates/database/LICENSE +201 -0
  409. package/templates/database/README.md +3 -1
  410. package/templates/database/__tests__/flows-listing-diagnostic.test.ts +392 -0
  411. package/templates/database/__tests__/flows-presets.test.ts +173 -0
  412. package/templates/database/__tests__/prefixed-ids.db.test.ts +146 -0
  413. package/templates/database/__tests__/seed.test.ts +88 -0
  414. package/templates/database/__tests__/user-cascade.test.ts +19 -4
  415. package/templates/database/auth/schema.prisma +521 -0
  416. package/templates/database/config/schema.prisma +234 -0
  417. package/templates/database/content/schema.prisma +263 -0
  418. package/templates/database/flows/listing-diagnostic.ts +182 -0
  419. package/templates/database/flows/presets.ts +315 -0
  420. package/templates/database/inbox/__tests__/schema.test.ts +4 -0
  421. package/templates/database/inbox/schema.prisma +574 -3
  422. package/templates/database/jest.config.cjs +8 -0
  423. package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +201 -0
  424. package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +67 -0
  425. package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +46 -0
  426. package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +25 -0
  427. package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +49 -0
  428. package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +197 -0
  429. package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +32 -0
  430. package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +22 -0
  431. package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +151 -0
  432. package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +223 -0
  433. package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +22 -0
  434. package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +32 -0
  435. package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +40 -0
  436. package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +4 -0
  437. package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +2 -0
  438. package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +2 -0
  439. package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +29 -0
  440. package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +6 -0
  441. package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +39 -0
  442. package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +24 -0
  443. package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +3 -0
  444. package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +33 -0
  445. package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +30 -0
  446. package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +47 -0
  447. package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +16 -0
  448. package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +47 -0
  449. package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +10 -0
  450. package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +26 -0
  451. package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +90 -0
  452. package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +174 -0
  453. package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +14 -0
  454. package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +25 -0
  455. package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
  456. package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
  457. package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
  458. package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
  459. package/templates/database/ops/schema.prisma +27 -0
  460. package/templates/database/package.json +42 -4
  461. package/templates/database/prisma.config.ts +1 -1
  462. package/templates/database/schema.prisma +19 -435
  463. package/templates/database/seed-test.ts +14 -4
  464. package/templates/database/seed.ts +148 -0
  465. package/templates/database/tenancy.ts +45 -0
  466. package/templates/database/tsconfig.flows.json +15 -0
  467. package/templates/database/tsconfig.json +7 -1
  468. package/templates/database/tsconfig.tenancy.json +14 -0
  469. package/templates/package.json +30 -55
  470. package/templates/pnpm-workspace.yaml +0 -3
  471. package/templates/project.yaml +1 -1
  472. package/templates/tsconfig.json +1 -37
  473. package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +0 -94
  474. package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +0 -75
  475. package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +0 -82
  476. package/templates/apps/web/__tests__/auth/env-helpers.test.ts +0 -168
  477. package/templates/apps/web/__tests__/authority-list-routes.test.tsx +0 -208
  478. package/templates/apps/web/__tests__/better-stack.test.ts +0 -66
  479. package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +0 -87
  480. package/templates/apps/web/__tests__/blog-source-populated.test.ts +0 -130
  481. package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +0 -60
  482. package/templates/apps/web/__tests__/google-analytics.test.tsx +0 -104
  483. package/templates/apps/web/__tests__/i18n/middleware.test.ts +0 -119
  484. package/templates/apps/web/__tests__/i18n/routing.test.ts +0 -114
  485. package/templates/apps/web/__tests__/i18n/translations.test.ts +0 -191
  486. package/templates/apps/web/__tests__/json-ld.test.ts +0 -98
  487. package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +0 -580
  488. package/templates/apps/web/__tests__/layout-stealth.test.ts +0 -39
  489. package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +0 -103
  490. package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +0 -114
  491. package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +0 -94
  492. package/templates/apps/web/__tests__/page.test.tsx +0 -230
  493. package/templates/apps/web/__tests__/products-services-phase1.test.tsx +0 -321
  494. package/templates/apps/web/__tests__/projects-routes.test.tsx +0 -392
  495. package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +0 -475
  496. package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +0 -53
  497. package/templates/apps/web/__tests__/routes/blog-route.test.tsx +0 -191
  498. package/templates/apps/web/__tests__/routes/llms-txt.test.ts +0 -123
  499. package/templates/apps/web/__tests__/routes/robots-txt.test.ts +0 -52
  500. package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +0 -78
  501. package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +0 -203
  502. package/templates/apps/web/__tests__/section-routes.test.tsx +0 -693
  503. package/templates/apps/web/__tests__/security-headers.test.ts +0 -201
  504. package/templates/apps/web/__tests__/sentry-config.test.ts +0 -116
  505. package/templates/apps/web/__tests__/sitemap-locales.test.ts +0 -415
  506. package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +0 -844
  507. package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +0 -120
  508. package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +0 -202
  509. package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +0 -152
  510. package/templates/apps/web/content/en/blog/index.mdx +0 -15
  511. package/templates/apps/web/content/en/blog/launching-ailk.mdx +0 -20
  512. package/templates/apps/web/content/en/docs/getting-started.mdx +0 -30
  513. package/templates/content/.gitkeep +0 -0
  514. package/templates/content/CLAUDE.md +0 -85
  515. package/templates/content/ar/HomePage/home.mdx +0 -123
  516. package/templates/content/ar/_site.mdx +0 -35
  517. package/templates/content/de/_site.mdx +0 -29
  518. package/templates/content/en/.gitkeep +0 -0
  519. package/templates/content/en/AboutPage/about.schema.json +0 -82
  520. package/templates/content/en/BookCall/book-call.mdx +0 -19
  521. package/templates/content/en/BookCall/book-call.schema.json +0 -121
  522. package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +0 -20
  523. package/templates/content/en/CareersList/careers.mdx +0 -24
  524. package/templates/content/en/CaseStudiesList/case-studies.mdx +0 -22
  525. package/templates/content/en/CaseStudiesList/showcase.mdx +0 -8
  526. package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +0 -32
  527. package/templates/content/en/CaseStudy/schema-builder-migration.mdx +0 -41
  528. package/templates/content/en/ClientsList/clients.mdx +0 -38
  529. package/templates/content/en/ContactPage/contact.schema.json +0 -100
  530. package/templates/content/en/ContactPoint/primary.mdx +0 -14
  531. package/templates/content/en/Course/aeo-fundamentals.mdx +0 -37
  532. package/templates/content/en/Course/schema-builder-workshop.mdx +0 -62
  533. package/templates/content/en/CoursesList/courses.mdx +0 -22
  534. package/templates/content/en/DownloadsList/downloads.mdx +0 -22
  535. package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +0 -39
  536. package/templates/content/en/Event/ailk-launch-day-2026.mdx +0 -35
  537. package/templates/content/en/EventsList/events.mdx +0 -26
  538. package/templates/content/en/FAQPage/faq.schema.json +0 -231
  539. package/templates/content/en/GlossaryList/glossary.mdx +0 -19
  540. package/templates/content/en/GlossaryList/glossary.schema.json +0 -300
  541. package/templates/content/en/HomePage/home.schema.json +0 -88
  542. package/templates/content/en/HomePage/launch-service.mdx +0 -475
  543. package/templates/content/en/HomePage/launch-service.schema.json +0 -138
  544. package/templates/content/en/HowTo/add-mdx-content.mdx +0 -38
  545. package/templates/content/en/HowTo/configure-aeo-scoring.mdx +0 -35
  546. package/templates/content/en/HowTo/set-up-json-ld.mdx +0 -36
  547. package/templates/content/en/Integration/github-actions.mdx +0 -31
  548. package/templates/content/en/Integration/openai-api.mdx +0 -26
  549. package/templates/content/en/Integration/vercel.mdx +0 -32
  550. package/templates/content/en/IntegrationsList/integrations.mdx +0 -22
  551. package/templates/content/en/JobPosting/content-strategist.mdx +0 -46
  552. package/templates/content/en/JobPosting/developer-advocate.mdx +0 -47
  553. package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +0 -57
  554. package/templates/content/en/LandingPage/aeo-audit.mdx +0 -19
  555. package/templates/content/en/LandingPage/aeo-audit.schema.json +0 -92
  556. package/templates/content/en/LandingPage/agency-waitlist.mdx +0 -340
  557. package/templates/content/en/LandingPage/agency-waitlist.schema.json +0 -152
  558. package/templates/content/en/LandingPage/developers.mdx +0 -19
  559. package/templates/content/en/LandingPage/developers.schema.json +0 -109
  560. package/templates/content/en/LandingPage/for-agencies.mdx +0 -390
  561. package/templates/content/en/LandingPage/for-agencies.schema.json +0 -107
  562. package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +0 -417
  563. package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +0 -105
  564. package/templates/content/en/LandingPage/for-businesses.mdx +0 -19
  565. package/templates/content/en/LandingPage/for-businesses.schema.json +0 -96
  566. package/templates/content/en/LandingPage/platform.mdx +0 -369
  567. package/templates/content/en/LandingPage/platform.schema.json +0 -166
  568. package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +0 -19
  569. package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +0 -102
  570. package/templates/content/en/LandingPage/vs-wordpress.mdx +0 -19
  571. package/templates/content/en/LandingPage/vs-wordpress.schema.json +0 -53
  572. package/templates/content/en/NewsletterSignup/newsletter.mdx +0 -236
  573. package/templates/content/en/NewsletterSignup/newsletter.schema.json +0 -91
  574. package/templates/content/en/Organization/site-owner.mdx +0 -16
  575. package/templates/content/en/Partner/prisma.mdx +0 -31
  576. package/templates/content/en/Partner/supabase.mdx +0 -30
  577. package/templates/content/en/Partner/vercel.mdx +0 -28
  578. package/templates/content/en/PartnersList/partners.mdx +0 -22
  579. package/templates/content/en/Person/founder.mdx +0 -18
  580. package/templates/content/en/Pricing/pricing.mdx +0 -538
  581. package/templates/content/en/Pricing/pricing.schema.json +0 -209
  582. package/templates/content/en/PrivacyPage/privacy.mdx +0 -165
  583. package/templates/content/en/PrivacyPage/privacy.schema.json +0 -64
  584. package/templates/content/en/Product/ailk.mdx +0 -19
  585. package/templates/content/en/Product/ailk.schema.json +0 -94
  586. package/templates/content/en/Product/api-launch-kit.mdx +0 -26
  587. package/templates/content/en/Product/launch-kit.mdx +0 -28
  588. package/templates/content/en/ProductsList/products.mdx +0 -26
  589. package/templates/content/en/Project/ailk-phase-2.mdx +0 -54
  590. package/templates/content/en/ProjectsList/projects.mdx +0 -25
  591. package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +0 -27
  592. package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +0 -28
  593. package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +0 -39
  594. package/templates/content/en/ResourcesHub/resources.mdx +0 -22
  595. package/templates/content/en/Service/consulting.mdx +0 -22
  596. package/templates/content/en/Service/implementation.mdx +0 -30
  597. package/templates/content/en/Service/launch-service.mdx +0 -232
  598. package/templates/content/en/Service/launch.mdx +0 -228
  599. package/templates/content/en/ServicesList/services.mdx +0 -84
  600. package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +0 -44
  601. package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +0 -47
  602. package/templates/content/en/SoftwareProduct/ailk-schema.mdx +0 -49
  603. package/templates/content/en/TeamList/team.mdx +0 -63
  604. package/templates/content/en/TeamMember/founder.mdx +0 -72
  605. package/templates/content/en/TermsPage/terms.mdx +0 -19
  606. package/templates/content/en/TermsPage/terms.schema.json +0 -99
  607. package/templates/content/en/TestimonialsList/testimonials.mdx +0 -44
  608. package/templates/content/en/UseCasesList/use-cases.mdx +0 -30
  609. package/templates/content/en/Whitepaper/ax-first-product-development.mdx +0 -27
  610. package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +0 -27
  611. package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +0 -31
  612. package/templates/content/en-XA/AboutPage/about.mdx +0 -8
  613. package/templates/content/en-XA/FAQPage/faq.mdx +0 -8
  614. package/templates/content/en-XA/HomePage/home.mdx +0 -8
  615. package/templates/content/es/_site.mdx +0 -30
  616. package/templates/content/fr/_site.mdx +0 -30
  617. package/templates/content/ja/_site.mdx +0 -30
  618. package/templates/content/pt-BR/AboutPage/about.mdx +0 -8
  619. package/templates/content/pt-BR/FAQPage/faq.mdx +0 -8
  620. package/templates/content/pt-BR/HomePage/home.mdx +0 -23
@@ -0,0 +1,398 @@
1
+ /**
2
+ * @file playbook-compile.ts
3
+ * @description FEAT-074 U4a (#4715) — compiles a scored `targeting-maturity`
4
+ * signup into a person-scoped Playbook and persists it append-only.
5
+ *
6
+ * `compilePlaybook` is a PURE function (`rules/execution-substrate.md` — the
7
+ * compile is a payload, never a per-respondent model call): given a scored
8
+ * `WaitlistScore` row's facts (`{id, tier, ruleTrace}`), the play library,
9
+ * and the lead id, it returns `{source, markdown}` — no Prisma, no network,
10
+ * no clock, no filesystem read of its own. `source` validates against
11
+ * `vendor/playbook-method/playbook-source.schema.json` (U1) and passes
12
+ * `vendor/playbook-method/check-playbook.py` — see this file's own test
13
+ * suite, which runs the vendored checker against every golden.
14
+ *
15
+ * `compileAndStorePlaybook` is the thin, fail-soft Prisma-touching wrapper —
16
+ * the sibling to A2's `waitlist-scoring.ts` glue. It loads the vendored
17
+ * play library LAZILY, memoized, on first use inside its own try block (a
18
+ * `readFileSync`, not a static `import … from "*.json"` — `apps/api`'s
19
+ * `tsconfig.json` sets `rootDir: "./src"` + `composite: true`, and the
20
+ * vendored library lives at the repo root, outside that boundary;
21
+ * `apps-paid/design-reference-gallery/lib/corpus.ts` is this repo's own
22
+ * precedent for reading vendored repo-root content via `readFileSync`
23
+ * rather than a cross-boundary static import). Deliberately NOT at module
24
+ * scope (security-review finding M1, PR #4727): a missing/corrupt vendored
25
+ * file must degrade like any other compile failure (D4), never throw at
26
+ * import time where no catch in this file can reach it. `compilePlaybook`
27
+ * itself never touches the filesystem.
28
+ *
29
+ * The path is resolved via `process.cwd()`-relative candidate probing
30
+ * (`apps/api/src/lib/locales.ts`'s own `resolveProjectYamlPath` pattern),
31
+ * NOT `import.meta.url` — Jest's ts-jest preset here compiles to CommonJS
32
+ * (`jest.config.js`), which cannot execute `import.meta` (Node throws
33
+ * `SyntaxError: Cannot use 'import.meta' outside a module`); this repo's own
34
+ * `moduleNameMapper` comment on `@working-theory/database` names the exact
35
+ * same v7 prisma-client `import.meta.url` incompatibility and mocks around
36
+ * it. `process.cwd()` has no such split — it resolves correctly whether the
37
+ * process was started from `apps/api/` (dev/build) or from this package's
38
+ * own test run.
39
+ *
40
+ * The unlock map (`unlocks_next`) is LIBRARY-DECLARED, resolved from
41
+ * `levels[level].unlocks_next` — NEVER computed as `level + 1` (the
42
+ * method's map is not monotonic: Level 5 unlocks Level 7, Level 8 unlocks
43
+ * Level 7 — spec §2.1, U3 Clarifications).
44
+ */
45
+
46
+ import { readFileSync } from "node:fs";
47
+ import { join } from "node:path";
48
+
49
+ import type { Prisma } from "@working-theory/database";
50
+ import { logger } from "@working-theory/observability";
51
+
52
+ import { prisma } from "../lib/prisma.js";
53
+
54
+ // ─── Play-library types (the vendored targeting-maturity.plays.json shape) ──
55
+
56
+ export interface PlaybookPlay {
57
+ id: string;
58
+ title: string;
59
+ text: string;
60
+ }
61
+
62
+ export interface PlaybookLevelEntry {
63
+ level: number;
64
+ name?: string;
65
+ use_if?: string;
66
+ cautions?: string[];
67
+ plays: PlaybookPlay[];
68
+ unlocks_next: number | null;
69
+ }
70
+
71
+ export interface PlayLibrary {
72
+ version: string;
73
+ method: string;
74
+ /** A bare pointer object at level 0 in the authored shape (D4) — the Gate's
75
+ * own plays live in `levels[]` at `level: 0`; consulted only as a fallback
76
+ * when `levels[]` carries no level-0 entry (mirrors
77
+ * `check-playbook.py`'s `_library_entry_for_level`). */
78
+ gate?: { level: number; plays?: PlaybookPlay[]; unlocks_next?: number | null };
79
+ levels: PlaybookLevelEntry[];
80
+ }
81
+
82
+ // ─── The vendored play library — lazy-loaded, memoized (see header) ─────────
83
+
84
+ const VENDORED_LIBRARY_RELATIVE = "vendor/playbook-method/targeting-maturity.plays.json";
85
+
86
+ /** Probes `[cwd, cwd/.., cwd/../..]` for the vendored library — apps run
87
+ * from their own dir in dev/build (`apps/api/`) but this package's own
88
+ * tests may run from either `apps/api/` or the repo root (mirrors
89
+ * `apps/api/src/lib/locales.ts`'s `resolveProjectYamlPath`). Falls back to
90
+ * the cwd-relative path so a genuine miss's error names the expected
91
+ * location. */
92
+ export function resolveVendoredLibraryPath(cwd = process.cwd()): string {
93
+ const candidates = [
94
+ join(cwd, VENDORED_LIBRARY_RELATIVE),
95
+ join(cwd, "..", VENDORED_LIBRARY_RELATIVE),
96
+ join(cwd, "..", "..", VENDORED_LIBRARY_RELATIVE),
97
+ ];
98
+ for (const candidate of candidates) {
99
+ try {
100
+ readFileSync(candidate, "utf8");
101
+ return candidate;
102
+ } catch {
103
+ // continue
104
+ }
105
+ }
106
+ return candidates[0]!;
107
+ }
108
+
109
+ export function loadVendoredPlayLibrary(): PlayLibrary {
110
+ return JSON.parse(readFileSync(resolveVendoredLibraryPath(), "utf8")) as PlayLibrary;
111
+ }
112
+
113
+ // LAZY + memoized (security-review finding M1, PR #4727): loading the
114
+ // library at MODULE SCOPE would let a missing/corrupt vendored file throw
115
+ // at import time — outside every try/catch this file adds, and outside the
116
+ // fail-soft contract this whole unit exists to uphold. On a bundler that
117
+ // cannot statically resolve the `process.cwd()`-relative repo-root path
118
+ // (e.g. a serverless target with no `functions.includeFiles` entry for
119
+ // `vendor/**`), that import-time throw would 500 EVERY request through this
120
+ // module's static import chain (`waitlist-scoring.ts` → `flow-engine.ts` →
121
+ // the route layer → the server entrypoint) — not merely fail to compile a
122
+ // Playbook. Deferring the read to first use inside `compileAndStorePlaybook`'s
123
+ // own try block (below) means a missing/corrupt file degrades exactly like
124
+ // any other compile failure (D4: logged, the score is unaffected) instead of
125
+ // taking the process down. Memoized so the read still happens at most once.
126
+ let vendoredPlayLibraryCache: PlayLibrary | undefined;
127
+ function vendoredPlayLibrary(): PlayLibrary {
128
+ if (!vendoredPlayLibraryCache) {
129
+ vendoredPlayLibraryCache = loadVendoredPlayLibrary();
130
+ }
131
+ return vendoredPlayLibraryCache;
132
+ }
133
+
134
+ /** The one experiment this compile hooks (D4) — every other experiment's
135
+ * `scoreCompletedSignup` completion never reaches this module. */
136
+ export const TARGETING_MATURITY_EXPERIMENT_KEY = "targeting-maturity";
137
+
138
+ // ─── compilePlaybook — the pure payload (D1) ────────────────────────────────
139
+
140
+ export interface PlaybookSource {
141
+ artifact_type: "playbook";
142
+ scope: "person";
143
+ scope_ref: string;
144
+ method: string;
145
+ level: number;
146
+ plays: string[];
147
+ unlocks_next?: string[];
148
+ compiled_from: string;
149
+ }
150
+
151
+ /** The WaitlistScore facts `compilePlaybook` reads — a subset of the Prisma
152
+ * row, never the row itself (the pure function takes no Prisma type). */
153
+ export interface CompilePlaybookScoreInput {
154
+ /** `WaitlistScore.id` — becomes `source.compiled_from` (D2). */
155
+ id: string;
156
+ /** `WaitlistScore.tier` — a `level_N` string. */
157
+ tier: string;
158
+ /** `WaitlistScore.ruleTrace` — opaque at this boundary, mirroring
159
+ * `packages/validation`'s own `ruleTrace: z.unknown()` convention; parsed
160
+ * defensively for the Level-1 loop-back framing only (see
161
+ * `decidingRefNames`). */
162
+ ruleTrace: unknown;
163
+ }
164
+
165
+ export interface CompilePlaybookInput {
166
+ score: CompilePlaybookScoreInput;
167
+ plays: PlayLibrary;
168
+ leadId: string;
169
+ }
170
+
171
+ export interface CompilePlaybookResult {
172
+ source: PlaybookSource;
173
+ markdown: string;
174
+ }
175
+
176
+ /** A `compilePlaybook` input that cannot be compiled — the library declares
177
+ * no Level for the score's tier, or that Level has no plays (spec §5.3 — the
178
+ * play-library owner's defect, never a compile branch). */
179
+ export class PlaybookCompileError extends Error {}
180
+
181
+ const TIER_LEVEL_RE = /^level_(\d+)$/;
182
+
183
+ /** The library entry for `level` — `levels[]` first, the `gate` pointer as a
184
+ * level-0 fallback only (mirrors `check-playbook.py`'s `_library_entry_for_level`,
185
+ * the #8496 amendment). */
186
+ function levelEntryFor(library: PlayLibrary, level: number): PlaybookLevelEntry | undefined {
187
+ const fromLevels = library.levels.find((entry) => entry.level === level);
188
+ if (fromLevels) return fromLevels;
189
+ if (level === 0 && library.gate?.plays && library.gate.plays.length > 0) {
190
+ return {
191
+ level: 0,
192
+ plays: library.gate.plays,
193
+ unlocks_next: library.gate.unlocks_next ?? null,
194
+ };
195
+ }
196
+ return undefined;
197
+ }
198
+
199
+ /**
200
+ * The `ref(<name>)` def names that DECIDED the matched tier — every
201
+ * `kind: "predicate"` trace entry whose `tier` equals the matched tier AND
202
+ * whose `result` is `true`, with every `ref(...)` segment in its `path`
203
+ * extracted (`RuleTraceEntry.path` embeds each traversed def as
204
+ * `ref(<name>)`, per `evaluatePredicateNode`'s own path-building — there is
205
+ * no separate `def` field to read instead). Duck-typed, not
206
+ * `ruleTraceEntrySchema`-parsed: `ruleTrace` is `unknown` at every boundary
207
+ * this function is reached from (Prisma `Json`, the dashboard's own
208
+ * `z.unknown()`), and this compile never needs the full shape — only
209
+ * whether a specific ref fired true for this tier.
210
+ */
211
+ function decidingRefNames(ruleTrace: unknown, tier: string): Set<string> {
212
+ const names = new Set<string>();
213
+ if (!Array.isArray(ruleTrace)) return names;
214
+ for (const entry of ruleTrace) {
215
+ if (typeof entry !== "object" || entry === null) continue;
216
+ const e = entry as Record<string, unknown>;
217
+ if (e["kind"] !== "predicate" || e["tier"] !== tier || e["result"] !== true) continue;
218
+ const path = e["path"];
219
+ if (typeof path !== "string") continue;
220
+ for (const m of path.matchAll(/ref\(([^)]+)\)/g)) {
221
+ names.add(m[1]!);
222
+ }
223
+ }
224
+ return names;
225
+ }
226
+
227
+ function renderPlaybookMarkdown(args: {
228
+ source: PlaybookSource;
229
+ entry: PlaybookLevelEntry;
230
+ unlockEntry: PlaybookLevelEntry | undefined;
231
+ isLoopBackCohort: boolean;
232
+ }): string {
233
+ const { source, entry, unlockEntry, isLoopBackCohort } = args;
234
+ const heading = entry.name ? `Level ${source.level} — ${entry.name}` : `Level ${source.level}`;
235
+ const lines: string[] = [];
236
+
237
+ lines.push(`# Playbook — ${heading}`, "");
238
+
239
+ if (isLoopBackCohort) {
240
+ lines.push(
241
+ "> You reached Level 1 via the Level-6 loop-back — a confirmed-Fit " +
242
+ "cohort (not paying customers) put you here. Once this Level has " +
243
+ "given you a validated ICP filter, revisit at Level 7 or 8.",
244
+ "",
245
+ );
246
+ }
247
+
248
+ lines.push(
249
+ "## §1. Identity",
250
+ "",
251
+ `**artifact_type:** \`${source.artifact_type}\``,
252
+ `**scope:** \`${source.scope}\``,
253
+ `**scope_ref:** \`${source.scope_ref}\``,
254
+ `**method:** \`${source.method}\``,
255
+ `**level:** \`${source.level}\``,
256
+ `**compiled_from:** \`${source.compiled_from}\``,
257
+ "",
258
+ "<!-- machine-region -->",
259
+ `<!-- playbook-source:\n${JSON.stringify(source)}\n-->`,
260
+ "<!-- /machine-region -->",
261
+ "",
262
+ "---",
263
+ "",
264
+ "## §2. Plays",
265
+ "",
266
+ `### Level ${source.level}`,
267
+ "",
268
+ );
269
+ for (const play of entry.plays) {
270
+ lines.push(`- \`${play.id}\` — ${play.title}`);
271
+ }
272
+
273
+ if (unlockEntry && source.unlocks_next) {
274
+ lines.push(
275
+ "",
276
+ "---",
277
+ "",
278
+ "## §3. Unlocks next",
279
+ "",
280
+ `### Level ${unlockEntry.level}`,
281
+ "",
282
+ );
283
+ for (const play of unlockEntry.plays) {
284
+ lines.push(`- \`${play.id}\` — ${play.title}`);
285
+ }
286
+ }
287
+
288
+ return lines.join("\n") + "\n";
289
+ }
290
+
291
+ /**
292
+ * Compile a scored `targeting-maturity` signup into a person-scoped
293
+ * Playbook — pure, deterministic, no I/O (D1). Throws `PlaybookCompileError`
294
+ * when the library declares no Level for the score's tier, or that Level
295
+ * has no plays — the caller (`compileAndStorePlaybook`) is fail-soft over
296
+ * this throw (D4, spec §5.3).
297
+ */
298
+ export function compilePlaybook(input: CompilePlaybookInput): CompilePlaybookResult {
299
+ const { score, plays, leadId } = input;
300
+
301
+ const match = TIER_LEVEL_RE.exec(score.tier);
302
+ if (!match) {
303
+ throw new PlaybookCompileError(
304
+ `cannot compile a Playbook for tier "${score.tier}" — not a level_N tier`,
305
+ );
306
+ }
307
+ const level = Number(match[1]);
308
+
309
+ const entry = levelEntryFor(plays, level);
310
+ if (!entry || entry.plays.length === 0) {
311
+ throw new PlaybookCompileError(
312
+ `the play library declares no plays for Level ${level} — a library defect, not a compile branch (spec §5.3)`,
313
+ );
314
+ }
315
+
316
+ let unlockEntry: PlaybookLevelEntry | undefined;
317
+ let unlockIds: string[] | undefined;
318
+ if (entry.unlocks_next !== null && entry.unlocks_next !== undefined) {
319
+ unlockEntry = levelEntryFor(plays, entry.unlocks_next);
320
+ if (unlockEntry && unlockEntry.plays.length > 0) {
321
+ unlockIds = unlockEntry.plays.map((play) => play.id);
322
+ }
323
+ }
324
+
325
+ const source: PlaybookSource = {
326
+ artifact_type: "playbook",
327
+ scope: "person",
328
+ scope_ref: leadId,
329
+ method: plays.method,
330
+ level,
331
+ plays: entry.plays.map((play) => play.id),
332
+ ...(unlockIds ? { unlocks_next: unlockIds } : {}),
333
+ compiled_from: score.id,
334
+ };
335
+
336
+ const isLoopBackCohort =
337
+ level === 1 && decidingRefNames(score.ruleTrace, score.tier).has("has_confirmed_fit_cohort");
338
+
339
+ const markdown = renderPlaybookMarkdown({ source, entry, unlockEntry, isLoopBackCohort });
340
+
341
+ return { source, markdown };
342
+ }
343
+
344
+ // ─── compileAndStorePlaybook — the fail-soft Prisma-touching wrapper (D4) ───
345
+
346
+ export interface CompileAndStorePlaybookInput {
347
+ siteId: string;
348
+ leadId: string;
349
+ experimentKey: string;
350
+ scoreId: string;
351
+ tier: string;
352
+ ruleTrace: unknown;
353
+ }
354
+
355
+ /**
356
+ * Compile + append a `PlaybookInstance` row. Fail-soft over BOTH the pure
357
+ * compile (`PlaybookCompileError` or any other throw) and the Prisma write —
358
+ * a compile/store failure is logged and never fails the signup or the score
359
+ * (D4). The caller (`waitlist-scoring.ts`) awaits this unconditionally; it
360
+ * never throws.
361
+ */
362
+ export async function compileAndStorePlaybook(
363
+ input: CompileAndStorePlaybookInput,
364
+ ): Promise<void> {
365
+ const { siteId, leadId, experimentKey, scoreId, tier, ruleTrace } = input;
366
+ try {
367
+ const { source, markdown } = compilePlaybook({
368
+ score: { id: scoreId, tier, ruleTrace },
369
+ plays: vendoredPlayLibrary(),
370
+ leadId,
371
+ });
372
+
373
+ await prisma.playbookInstance.create({
374
+ data: {
375
+ siteId,
376
+ leadId,
377
+ experimentKey,
378
+ scoreId,
379
+ tier,
380
+ level: source.level,
381
+ ruleTrace: (ruleTrace ?? null) as unknown as Prisma.InputJsonValue,
382
+ source: source as unknown as Prisma.InputJsonValue,
383
+ markdown,
384
+ },
385
+ });
386
+ } catch (err) {
387
+ logger.error(
388
+ {
389
+ siteId,
390
+ experimentKey,
391
+ leadId,
392
+ kind: "playbook_compile_failed",
393
+ errKind: err instanceof Error ? err.constructor.name : typeof err,
394
+ },
395
+ "[playbook-compile] compile/store failed — the score is unaffected",
396
+ );
397
+ }
398
+ }
@@ -0,0 +1,263 @@
1
+ /**
2
+ * @file playbook-render.ts
3
+ * @description FEAT-074 U4b (#4718) — renders a stored `PlaybookInstance` to
4
+ * a customer-facing PDF, on request.
5
+ *
6
+ * `renderPlaybookPdf({source, markdown, locale})` is a pure-TypeScript
7
+ * renderer (D2, spec §3): a small typed template lays out the Level/Gate
8
+ * heading, the Level's bound plays (title + body, resolved from `source`'s
9
+ * `plays`/`unlocks_next` id lists against the LIVE vendored play library —
10
+ * D1's "a template or renderer fix improves every already-issued Playbook"
11
+ * intent, so this deliberately reads the library fresh at render time
12
+ * rather than trusting anything frozen into the stored `markdown` snapshot),
13
+ * and the unlocks-next block. `markdown` is accepted per the spec's literal
14
+ * `{source, markdown, locale}` signature (matching `compilePlaybook`'s own
15
+ * `{source, markdown}` output pair, the exact shape `PlaybookInstance`
16
+ * persists) but is NOT parsed for content in this unit — `source`'s id
17
+ * lists plus a live library lookup already carry everything §2.1 requires
18
+ * (the Level label, the bound plays, the unlocks-next block); reserved for
19
+ * a future direct-render use.
20
+ *
21
+ * Renderer choice (D2, recorded per the dispatch's own requirement): PDF.js
22
+ * (mozilla's) sibling project `pdf-lib` (v1.17.1, MIT) — pure TypeScript,
23
+ * zero native dependencies (its own runtime deps —
24
+ * `@pdf-lib/standard-fonts`, `@pdf-lib/upng`, `pako`, `tslib` — are all pure
25
+ * JS/TS too), no headless browser, no postinstall download. It embeds the
26
+ * 14 PDF standard fonts (Helvetica here) with no external font files, and
27
+ * exposes `font.widthOfTextAtSize` for the manual word-wrap this file
28
+ * implements (pdf-lib has no built-in text-flow/wrap primitive — a small
29
+ * typed template has no need for one).
30
+ *
31
+ * A play id from `source.plays`/`source.unlocks_next` that the CURRENT
32
+ * vendored library no longer declares is a library defect, never a render
33
+ * branch — mirrors `playbook-compile.ts`'s own `PlaybookCompileError`
34
+ * posture ("a library defect, not a compile branch") applied symmetrically
35
+ * to render.
36
+ *
37
+ * Every CHROME label (the Level/Gate heading text, the "Plays"/"Unlocks
38
+ * next" section titles) is a `LocalizedString` resolved via
39
+ * `resolveLocalizedString` (`@working-theory/validation`, the ONE shared
40
+ * resolution rule — flow-i18n.ts's own header) against the request
41
+ * `locale`. Play TITLES/BODIES are read straight from the vendored library,
42
+ * which carries no `LocalizedString` shape of its own (English-only at the
43
+ * source, the same convention `compilePlaybook`'s stored `markdown` already
44
+ * uses) — the i18n requirement (spec §2.1) scopes to the renderer's own
45
+ * chrome, not to library content. `renderPlaybookPdf`'s fixed
46
+ * `{source, markdown, locale}` signature carries no separate flow
47
+ * `defaultLocale` field, so this file's label table declares `en` as its
48
+ * sole `defaultLocale` (a module constant, `LABEL_DEFAULT_LOCALE` below) —
49
+ * every label below currently declares only `en`, so this is presently a
50
+ * no-op fallback, kept for structural correctness against a future
51
+ * multi-locale label table.
52
+ *
53
+ * The Gate (Level 0) is labelled "Gate," never "Level 0" (spec §2.1's
54
+ * never-say-Level-0 rule — mirrors the operator dashboard's own
55
+ * `waitlistDashboard.level.gate` key, U4a D5, and the AC-1 Gate filename
56
+ * convention `playbook-gate.pdf`).
57
+ */
58
+
59
+ import { resolveLocalizedString } from "@working-theory/validation";
60
+ import { PDFDocument, PageSizes, StandardFonts, type PDFFont, type PDFPage } from "pdf-lib";
61
+
62
+ import { loadVendoredPlayLibrary, type PlaybookPlay, type PlaybookSource, type PlayLibrary } from "./playbook-compile.js";
63
+
64
+ // ─── Chrome labels — the CUSTOMER-FACING structural text (spec §2.1) ──────
65
+
66
+ const LABEL_DEFAULT_LOCALE = "en";
67
+
68
+ const LABELS = {
69
+ documentTitle: { en: "Your Playbook" },
70
+ gate: { en: "Gate" },
71
+ /** `{n}` is a literal placeholder — substituted after resolution. */
72
+ levelLabel: { en: "Level {n}" },
73
+ plays: { en: "Plays" },
74
+ unlocksNext: { en: "Unlocks next" },
75
+ } as const satisfies Record<string, Record<string, string>>;
76
+
77
+ function label(key: keyof typeof LABELS, locale: string): string {
78
+ return resolveLocalizedString(LABELS[key], locale, LABEL_DEFAULT_LOCALE);
79
+ }
80
+
81
+ /** Level 0 is "Gate," never "Level 0" (spec §2.1 — the never-say-Level-0 rule). */
82
+ function levelHeading(level: number, locale: string): string {
83
+ if (level === 0) return label("gate", locale);
84
+ return label("levelLabel", locale).replace("{n}", String(level));
85
+ }
86
+
87
+ // ─── The vendored play library — a per-play id index ───────────────────────
88
+
89
+ interface IndexedPlay {
90
+ play: PlaybookPlay;
91
+ level: number;
92
+ levelName: string | undefined;
93
+ }
94
+
95
+ /** A `source.plays`/`source.unlocks_next` id the CURRENT vendored library no
96
+ * longer declares — a library defect, never a render branch (mirrors
97
+ * `PlaybookCompileError`'s identical posture in `playbook-compile.ts`). */
98
+ export class PlaybookRenderError extends Error {}
99
+
100
+ /** Flattens every Level's `plays[]` into one id-keyed index, carrying each
101
+ * play's owning Level number + name — so an unlocks-next play id resolves
102
+ * both its own title/text AND which Level it belongs to, with no need to
103
+ * parse a `tm-<level>.<n>` id-format convention the schema does not
104
+ * guarantee. */
105
+ function buildPlayIndex(library: PlayLibrary): Map<string, IndexedPlay> {
106
+ const index = new Map<string, IndexedPlay>();
107
+ for (const entry of library.levels) {
108
+ for (const play of entry.plays) {
109
+ index.set(play.id, { play, level: entry.level, levelName: entry.name });
110
+ }
111
+ }
112
+ return index;
113
+ }
114
+
115
+ function resolvePlays(index: Map<string, IndexedPlay>, ids: string[]): IndexedPlay[] {
116
+ return ids.map((id) => {
117
+ const entry = index.get(id);
118
+ if (!entry) {
119
+ throw new PlaybookRenderError(
120
+ `the vendored play library declares no play "${id}" — a library defect, never a render branch (mirrors playbook-compile.ts's PlaybookCompileError posture)`,
121
+ );
122
+ }
123
+ return entry;
124
+ });
125
+ }
126
+
127
+ // ─── Word wrap + pagination (pdf-lib has no built-in text-flow primitive) ──
128
+
129
+ const PAGE_SIZE = PageSizes.Letter; // [612, 792]
130
+ const MARGIN = 54; // 0.75in
131
+ const CONTENT_WIDTH = PAGE_SIZE[0] - MARGIN * 2;
132
+ const TOP_Y = PAGE_SIZE[1] - MARGIN;
133
+ const BOTTOM_Y = MARGIN;
134
+
135
+ /** Greedy word-wrap against a font's measured width — pdf-lib exposes
136
+ * `font.widthOfTextAtSize` but no wrap primitive of its own. */
137
+ function wrapLine(text: string, font: PDFFont, size: number, maxWidth: number): string[] {
138
+ const words = text.split(/\s+/).filter((w) => w.length > 0);
139
+ const lines: string[] = [];
140
+ let current = "";
141
+ for (const word of words) {
142
+ const candidate = current.length > 0 ? `${current} ${word}` : word;
143
+ if (font.widthOfTextAtSize(candidate, size) <= maxWidth) {
144
+ current = candidate;
145
+ } else {
146
+ if (current.length > 0) lines.push(current);
147
+ current = word;
148
+ }
149
+ }
150
+ if (current.length > 0) lines.push(current);
151
+ return lines;
152
+ }
153
+
154
+ interface PdfWriter {
155
+ heading(text: string, size: number): void;
156
+ subheading(text: string, size: number): void;
157
+ paragraph(text: string, size: number): void;
158
+ spacer(height: number): void;
159
+ save(): Promise<Uint8Array>;
160
+ }
161
+
162
+ /** A closure-scoped writer (`@working-theory/design-system/no-undisciplined-class`
163
+ * forbids `this`-bearing stateful classes) — the mutable page/y cursor lives
164
+ * in this function's own local variables, never on an instance. */
165
+ async function createPdfWriter(): Promise<PdfWriter> {
166
+ const doc = await PDFDocument.create();
167
+ const regular = await doc.embedFont(StandardFonts.Helvetica);
168
+ const bold = await doc.embedFont(StandardFonts.HelveticaBold);
169
+
170
+ let page: PDFPage = doc.addPage(PAGE_SIZE);
171
+ let y = TOP_Y;
172
+
173
+ function ensureSpace(height: number): void {
174
+ if (y - height < BOTTOM_Y) {
175
+ page = doc.addPage(PAGE_SIZE);
176
+ y = TOP_Y;
177
+ }
178
+ }
179
+
180
+ function drawLines(lines: string[], font: PDFFont, size: number, lineHeight: number): void {
181
+ for (const line of lines) {
182
+ ensureSpace(lineHeight);
183
+ page.drawText(line, { x: MARGIN, y: y - size, size, font });
184
+ y -= lineHeight;
185
+ }
186
+ }
187
+
188
+ return {
189
+ heading(text, size) {
190
+ ensureSpace(size * 1.6 + 8);
191
+ drawLines(wrapLine(text, bold, size, CONTENT_WIDTH), bold, size, size * 1.4);
192
+ y -= 8;
193
+ },
194
+ subheading(text, size) {
195
+ ensureSpace(size * 1.6 + 6);
196
+ drawLines(wrapLine(text, bold, size, CONTENT_WIDTH), bold, size, size * 1.35);
197
+ y -= 6;
198
+ },
199
+ paragraph(text, size) {
200
+ drawLines(wrapLine(text, regular, size, CONTENT_WIDTH), regular, size, size * 1.4);
201
+ y -= 6;
202
+ },
203
+ spacer(height) {
204
+ y -= height;
205
+ },
206
+ async save() {
207
+ return doc.save();
208
+ },
209
+ };
210
+ }
211
+
212
+ // ─── renderPlaybookPdf ───────────────────────────────────────────────────────
213
+
214
+ export interface RenderPlaybookPdfInput {
215
+ source: PlaybookSource;
216
+ /** Accepted per the spec's literal signature; not parsed for content in
217
+ * this unit — see the file header. */
218
+ markdown: string;
219
+ locale: string;
220
+ }
221
+
222
+ /**
223
+ * Renders a stored `PlaybookInstance` to a PDF: the Level/Gate heading, the
224
+ * Level's bound plays (title + body from the LIVE vendored library), and
225
+ * the unlocks-next block (spec §2.1). Throws `PlaybookRenderError` when the
226
+ * current library no longer declares a play `source` names — a library
227
+ * defect, never a render branch.
228
+ */
229
+ export async function renderPlaybookPdf(input: RenderPlaybookPdfInput): Promise<Uint8Array> {
230
+ const { source, locale } = input;
231
+
232
+ const library = loadVendoredPlayLibrary();
233
+ const index = buildPlayIndex(library);
234
+
235
+ const currentPlays = resolvePlays(index, source.plays);
236
+ const unlockPlays = source.unlocks_next ? resolvePlays(index, source.unlocks_next) : [];
237
+
238
+ const writer = await createPdfWriter();
239
+
240
+ writer.heading(label("documentTitle", locale), 20);
241
+ writer.subheading(levelHeading(source.level, locale), 15);
242
+ writer.spacer(4);
243
+
244
+ writer.subheading(label("plays", locale), 13);
245
+ renderPlays(writer, currentPlays);
246
+
247
+ if (unlockPlays.length > 0) {
248
+ writer.spacer(10);
249
+ const unlockLevel = unlockPlays[0]!.level;
250
+ writer.subheading(`${label("unlocksNext", locale)} — ${levelHeading(unlockLevel, locale)}`, 13);
251
+ renderPlays(writer, unlockPlays);
252
+ }
253
+
254
+ return writer.save();
255
+ }
256
+
257
+ function renderPlays(writer: PdfWriter, plays: IndexedPlay[]): void {
258
+ for (const { play } of plays) {
259
+ writer.paragraph(`• ${play.title}`, 11);
260
+ writer.paragraph(play.text, 10);
261
+ writer.spacer(4);
262
+ }
263
+ }