create-ailk 0.2.0 → 0.4.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 (292) hide show
  1. package/component-catalog.md +50 -53
  2. package/dist/cli.js +19 -9
  3. package/dist/lib/apply-module-patches.d.ts +47 -0
  4. package/dist/lib/apply-module-patches.js +370 -0
  5. package/dist/lib/extract-module-bundle.d.ts +51 -3
  6. package/dist/lib/extract-module-bundle.js +327 -28
  7. package/dist/lib/fetch-module.d.ts +3 -38
  8. package/dist/lib/fetch-module.js +26 -8
  9. package/dist/lib/fetch-template.d.ts +20 -0
  10. package/dist/lib/fetch-template.js +129 -0
  11. package/dist/lib/module-license-gate.d.ts +10 -0
  12. package/dist/lib/module-license-gate.js +13 -4
  13. package/dist/lib/paid-paths.d.ts +16 -0
  14. package/dist/lib/paid-paths.js +90 -0
  15. package/dist/module-architecture.d.ts +197 -23
  16. package/dist/module-architecture.js +820 -25
  17. package/dist/parse-args.d.ts +9 -2
  18. package/dist/parse-args.js +46 -1
  19. package/dist/programmatic.d.ts +28 -2
  20. package/dist/programmatic.js +115 -8
  21. package/dist/surfaces.d.ts +12 -36
  22. package/dist/surfaces.js +77 -5
  23. package/dist/sync-routes.js +31 -1
  24. package/dist/template-manifest.d.ts +92 -0
  25. package/dist/template-manifest.js +168 -0
  26. package/dist/template-names.d.ts +41 -0
  27. package/dist/template-names.js +44 -0
  28. package/package.json +3 -2
  29. package/site-templates/marketing-site-free/ailk-template.json +8 -0
  30. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +103 -0
  31. package/site-templates/marketing-site-free/content/en/PrivacyPage/privacy.mdx +79 -0
  32. package/site-templates/marketing-site-free/content/en/TermsPage/terms.mdx +80 -0
  33. package/site-templates/marketing-site-free/provenance.json +4 -0
  34. package/templates/.claude/agents/web.md +2 -3
  35. package/templates/.claude/rules/architecture.md +5 -5
  36. package/templates/.claude/skills/README.md +2 -1
  37. package/templates/.env.example +21 -9
  38. package/templates/CONVENTIONS.md +6 -7
  39. package/templates/apps/api/.env.example +9 -0
  40. package/templates/apps/api/CLAUDE.md +33 -5
  41. package/templates/apps/api/package.json +0 -1
  42. package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
  43. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
  44. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
  45. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +166 -0
  46. package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
  47. package/templates/apps/api/src/config/index.ts +19 -0
  48. package/templates/apps/api/src/config/modules.ts +12 -14
  49. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  50. package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
  51. package/templates/apps/api/src/lib/slice-load.ts +90 -0
  52. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
  53. package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
  54. package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
  55. package/templates/apps/api/src/openapi/index.ts +9 -3
  56. package/templates/apps/api/src/openapi/spec.ts +505 -65
  57. package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
  58. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
  59. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
  60. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
  61. package/templates/apps/api/src/routes/content/index.ts +38 -11
  62. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +17 -0
  63. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +17 -0
  64. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
  65. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +10 -0
  66. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
  67. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
  68. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +10 -0
  69. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +24 -0
  70. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +17 -0
  71. package/templates/apps/api/src/routes/project-listings/index.ts +37 -10
  72. package/templates/apps/api/src/routes/project-listings/me.ts +10 -0
  73. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
  74. package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
  75. package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
  76. package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
  77. package/templates/apps/api/src/routes/project-listings/start.ts +30 -0
  78. package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
  79. package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
  80. package/templates/apps/api/src/server.ts +504 -137
  81. package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
  82. package/templates/apps/api/src/services/listing-config.ts +58 -0
  83. package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
  84. package/templates/apps/api/src/services/project-listings.ts +229 -4
  85. package/templates/apps/api/src/vercel-handler.ts +60 -26
  86. package/templates/apps/mcp/.env.example +8 -0
  87. package/templates/apps/mcp/CLAUDE.md +2 -2
  88. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
  89. package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
  90. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
  91. package/templates/apps/mcp/src/config/modules.ts +14 -13
  92. package/templates/apps/mcp/src/server.ts +8 -4
  93. package/templates/apps/mcp/src/tools/index.ts +7 -22
  94. package/templates/apps/web/.env.example +15 -0
  95. package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
  96. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
  97. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
  98. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
  99. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
  100. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
  101. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
  102. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
  103. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
  104. package/templates/apps/web/app/[locale]/pricing/page.tsx +40 -0
  105. package/templates/apps/web/app/[locale]/privacy/page.tsx +40 -0
  106. package/templates/apps/web/app/[locale]/terms/page.tsx +40 -0
  107. package/templates/apps/web/app/llms.txt/route.ts +12 -9
  108. package/templates/apps/web/jest.config.cjs +19 -13
  109. package/templates/apps/web/lib/__tests__/server-api.test.ts +61 -0
  110. package/templates/apps/web/lib/server-api.ts +28 -12
  111. package/templates/apps/web/messages/en.json +58 -0
  112. package/templates/apps/web/next.config.mjs +1 -3
  113. package/templates/apps/web/package.json +0 -6
  114. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  115. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  116. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  117. package/templates/apps/web/public/favicon-16x16.png +0 -0
  118. package/templates/apps/web/public/favicon-32x32.png +0 -0
  119. package/templates/apps/web/public/favicon.ico +0 -0
  120. package/templates/apps/web/public/favicon.svg +6 -3
  121. package/templates/apps/web/public/lockup-horizontal.svg +4 -0
  122. package/templates/apps/web/public/logomark.svg +4 -0
  123. package/templates/apps/web/public/site.webmanifest +2 -2
  124. package/templates/apps/web/public/wordmark.svg +4 -0
  125. package/templates/database/CHANGELOG.md +47 -0
  126. package/templates/database/inbox/schema.prisma +67 -0
  127. package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
  128. package/templates/database/package.json +1 -1
  129. package/templates/package.json +1 -1
  130. package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
  131. package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
  132. package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
  133. package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
  134. package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
  135. package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
  136. package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
  137. package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
  138. package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
  139. package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
  140. package/templates/apps/api/src/__tests__/server.test.ts +0 -253
  141. package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
  142. package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
  143. package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
  144. package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
  145. package/templates/apps/api/src/bin/seed-presets.ts +0 -58
  146. package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
  147. package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
  148. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
  149. package/templates/apps/api/src/lib/stripe.ts +0 -52
  150. package/templates/apps/api/src/lib/tenant-db.ts +0 -225
  151. package/templates/apps/api/src/lib/ws-token.ts +0 -91
  152. package/templates/apps/api/src/middleware/tenant.ts +0 -99
  153. package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
  154. package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
  155. package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
  156. package/templates/apps/api/src/routes/billing/index.ts +0 -36
  157. package/templates/apps/api/src/routes/billing/portal.ts +0 -182
  158. package/templates/apps/api/src/routes/billing/read.ts +0 -75
  159. package/templates/apps/api/src/routes/billing/usage.ts +0 -153
  160. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
  161. package/templates/apps/api/src/routes/checkout/index.ts +0 -13
  162. package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
  163. package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
  164. package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
  165. package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
  166. package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
  167. package/templates/apps/api/src/routes/flows/README.md +0 -147
  168. package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
  169. package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
  170. package/templates/apps/api/src/routes/flows/index.ts +0 -202
  171. package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
  172. package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
  173. package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +0 -221
  174. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
  175. package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
  176. package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
  177. package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
  178. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
  179. package/templates/apps/api/src/routes/schedule/index.ts +0 -249
  180. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
  181. package/templates/apps/api/src/routes/slack/actions.ts +0 -177
  182. package/templates/apps/api/src/routes/slack/index.ts +0 -38
  183. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
  184. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
  185. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
  186. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
  187. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
  188. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
  189. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
  190. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
  191. package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
  192. package/templates/apps/api/src/routes/webhooks/README.md +0 -80
  193. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
  194. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
  195. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
  196. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
  197. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
  198. package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
  199. package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
  200. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
  201. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
  202. package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
  203. package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
  204. package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
  205. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
  206. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
  207. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
  208. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
  209. package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +0 -684
  210. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
  211. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
  212. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
  213. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
  214. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
  215. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
  216. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
  217. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
  218. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
  219. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
  220. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
  221. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
  222. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
  223. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
  224. package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
  225. package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
  226. package/templates/apps/api/src/services/flow-engine.ts +0 -1296
  227. package/templates/apps/api/src/services/lead-promotion.ts +0 -176
  228. package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
  229. package/templates/apps/api/src/services/listing-promotion.ts +0 -342
  230. package/templates/apps/api/src/services/playbook-compile.ts +0 -398
  231. package/templates/apps/api/src/services/playbook-render.ts +0 -263
  232. package/templates/apps/api/src/services/project-listing-decision.ts +0 -510
  233. package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
  234. package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
  235. package/templates/apps/api/src/services/recommender-capture.ts +0 -835
  236. package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
  237. package/templates/apps/api/src/services/scheduling/index.ts +0 -63
  238. package/templates/apps/api/src/services/scheduling/types.ts +0 -88
  239. package/templates/apps/api/src/services/tenant-context.ts +0 -451
  240. package/templates/apps/api/src/services/usage-metering.ts +0 -699
  241. package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
  242. package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
  243. package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
  244. package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
  245. package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
  246. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
  247. package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
  248. package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
  249. package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
  250. package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
  251. package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
  252. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
  253. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
  254. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
  255. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
  256. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
  257. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
  258. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
  259. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
  260. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
  261. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
  262. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
  263. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
  264. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
  265. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
  266. package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
  267. package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
  268. package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
  269. package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
  270. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
  271. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
  272. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
  273. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
  274. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
  275. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
  276. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
  277. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
  278. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
  279. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
  280. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
  281. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
  282. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
  283. package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
  284. package/templates/apps/web/content/en/blog/README.txt +0 -25
  285. package/templates/apps/web/content/en/docs/index.mdx +0 -28
  286. package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
  287. package/templates/apps/web/lib/blog-list-data.ts +0 -95
  288. package/templates/apps/web/lib/blog-post-data.ts +0 -79
  289. package/templates/apps/web/lib/source.ts +0 -34
  290. package/templates/apps/web/public/logomark-dark.svg +0 -4
  291. package/templates/apps/web/public/logomark-light.svg +0 -4
  292. package/templates/apps/web/source.config.ts +0 -78
@@ -124,27 +124,12 @@ export {
124
124
  type ScaffoldOutput,
125
125
  } from "./scaffold.js";
126
126
 
127
- export {
128
- getFlowInputSchema,
129
- getFlowOutputSchema,
130
- ailkGetFlow,
131
- type GetFlowInput,
132
- type AilkGetFlowOutput,
133
- } from "./get_flow.js";
134
-
135
- export {
136
- submitFlowStepInputSchemaForTool,
137
- submitFlowStepOutputSchema,
138
- ailkSubmitFlowStep,
139
- type SubmitFlowStepInput,
140
- type AilkSubmitFlowStepOutput,
141
- } from "./submit_flow_step.js";
142
-
143
- // The scheduling tools (list_availability, get_event_meta, create_booking) are
144
- // deliberately NOT re-exported here, and neither are the content-write tools
145
- // (create_page, update_page, delete_page). Both groups are module-conditional
146
- // slice files (#5182): a re-export from this barrel is a static path into a
147
- // file the OSS strip removes, which reds the public tree's type-check. They are
148
- // reached only through `createMcpServer`'s per-module loader in ../server.ts.
127
+ // The scheduling tools (list_availability, get_event_meta, create_booking),
128
+ // the content-write tools (create_page, update_page, delete_page), and the
129
+ // signal-collectors flow tools (get_flow, submit_flow_step) are deliberately
130
+ // NOT re-exported here. All three groups are module-conditional slice files
131
+ // (#5182): a re-export from this barrel is a static path into a file the OSS
132
+ // strip removes, which reds the public tree's type-check. They are reached
133
+ // only through `createMcpServer`'s per-module loader in ../server.ts.
149
134
  // The rule this barrel now follows: it re-exports exactly the tools no module
150
135
  // gate can remove.
@@ -26,6 +26,21 @@ CONTENT_BACKEND=mdx
26
26
  # Same convention as apps/workspace/lib/client-api.ts's identically-named var.
27
27
  # Should be apps/api's origin with NO trailing slash (a single trailing
28
28
  # slash is stripped before joining, but keep this clean regardless).
29
+ #
30
+ # THIS IS THE ONE BROWSER-SIDE NAME (#5397). All three browser readers of the
31
+ # apps/api origin resolve it from this variable: resolveSubmitUrl
32
+ # (packages/ui, the form blocks above), apps/workspace's client-api, and
33
+ # @working-theory/api-client's getApiBase() — which runs in the browser
34
+ # wherever a "use client" component binds it, e.g. the founder application's
35
+ # ListingApplicationClient. getApiBase() previously read NEXT_PUBLIC_API_URL,
36
+ # a name this file never documented, so a site configured exactly per these
37
+ # docs sent its browser traffic to http://localhost:3001. That name is still
38
+ # honoured as a deprecated fallback; do not set it in a new deployment.
39
+ #
40
+ # The server-side counterpart is AILK_API_URL (see the repo-root .env.example)
41
+ # — deliberately NOT NEXT_PUBLIC_, and it still wins over this one when both
42
+ # are set, so an RSC/Server-Action call can target an internal origin the
43
+ # browser never sees.
29
44
  # NEXT_PUBLIC_AILK_API_URL=http://localhost:3002
30
45
 
31
46
  # =============================================================================
@@ -24,14 +24,14 @@ jest.mock("next/headers", () => ({
24
24
 
25
25
  const mockGetSession = jest.fn();
26
26
  // eslint-disable-next-line no-restricted-syntax -- #507: mock-internal rewrite pending; auth.ts's lazy Better-Auth singleton has no HTTP/DB boundary a unit test of the session-branch can mock without standing up Prisma.
27
- jest.mock("../../../../../auth", () => ({
27
+ jest.mock("../../../../auth", () => ({
28
28
  __esModule: true,
29
29
  auth: { api: { getSession: (...args: unknown[]) => mockGetSession(...args) } },
30
30
  }));
31
31
 
32
32
  const mockGetActiveProviderIds = jest.fn();
33
33
  // eslint-disable-next-line no-restricted-syntax -- #507: mock-internal rewrite pending; auth-env.ts is a pure env-introspection module with no external boundary to mock instead.
34
- jest.mock("../../../../../lib/auth-env", () => ({
34
+ jest.mock("../../../../lib/auth-env", () => ({
35
35
  __esModule: true,
36
36
  getActiveProviderIds: () => mockGetActiveProviderIds(),
37
37
  }));
@@ -48,8 +48,8 @@ jest.mock("better-auth/react", () => ({
48
48
  createAuthClient: () => ({ signIn: { social: mockSignInSocial } }),
49
49
  }));
50
50
 
51
- import AuthedLayout from "../../layout";
52
- import { SignInGate } from "../../sign-in-gate";
51
+ import AuthedLayout from "../layout";
52
+ import { SignInGate } from "../sign-in-gate";
53
53
 
54
54
  describe("AuthedLayout — the (authed) session guard", () => {
55
55
  beforeEach(() => {
@@ -1,3 +1,4 @@
1
+ // oss: false
1
2
  "use client";
2
3
 
3
4
  /**
@@ -53,7 +54,7 @@ import {
53
54
  type FlowStepperMessages,
54
55
  type FlowStepSubmitPayload,
55
56
  type FlowStepSubmitResult,
56
- } from "@working-theory/ui";
57
+ } from "@working-theory/ui/slices/signal-collectors";
57
58
  import type { AeoScoreResult, FlowConfig, FlowState } from "@working-theory/validation";
58
59
  import {
59
60
  deriveFlags,
@@ -1,3 +1,4 @@
1
+ // oss: false
1
2
  /**
2
3
  * Tests for FlowStepperClient's permalink completion redirect on a
3
4
  * `mode:"recommending"` flow (#4770 D1/D7/D12).
@@ -64,7 +65,7 @@ interface MockFlowStepperProps {
64
65
 
65
66
  let captured: MockFlowStepperProps | undefined;
66
67
 
67
- jest.mock("@working-theory/ui", () => ({
68
+ jest.mock("@working-theory/ui/slices/signal-collectors", () => ({
68
69
  FlowStepper: (props: MockFlowStepperProps) => {
69
70
  captured = props;
70
71
  return require("react").createElement("div", {
@@ -1,3 +1,4 @@
1
+ // oss: false
1
2
  /**
2
3
  * Tests for FlowStepperClient's segment-keyed completion redirect (#3960 D5).
3
4
  *
@@ -68,7 +69,7 @@ const PII_PAYLOAD = {
68
69
 
69
70
  let capturedOnCompleted: MockFlowStepperProps["onCompleted"];
70
71
 
71
- jest.mock("@working-theory/ui", () => ({
72
+ jest.mock("@working-theory/ui/slices/signal-collectors", () => ({
72
73
  FlowStepper: (props: MockFlowStepperProps) => {
73
74
  capturedOnCompleted = props.onCompleted;
74
75
  async function handleSubmit(): Promise<void> {
@@ -1,3 +1,4 @@
1
+ // oss: false
1
2
  /**
2
3
  * Tests for submitFlowStepAction Server Action (#3957 D8).
3
4
  *
@@ -1,3 +1,4 @@
1
+ // oss: false
1
2
  /**
2
3
  * #5053 — an interactive tool is two routes.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // oss: false
1
2
  "use server";
2
3
 
3
4
  /**
@@ -29,7 +30,7 @@
29
30
  */
30
31
 
31
32
  import { flows } from "@working-theory/api-client";
32
- import type { FlowStepSubmitResult } from "@working-theory/ui";
33
+ import type { FlowStepSubmitResult } from "@working-theory/ui/slices/signal-collectors";
33
34
  import {
34
35
  flowSlugSchema,
35
36
  submitFlowStepInputSchema,
@@ -1,3 +1,4 @@
1
+ // oss: false
1
2
  /**
2
3
  * /[locale]/flows/[slug] — the tool's LANDING route (#5053 D3/D5).
3
4
  *
@@ -1,3 +1,4 @@
1
+ // oss: false
1
2
  /**
2
3
  * /[locale]/flows/[slug]/start — the flow half of the two-route tool
3
4
  * (#5053 D3/D5). Moved here from `../page.tsx`, which is now the indexed
@@ -0,0 +1,40 @@
1
+ // Pricing page. Reads the Pricing MDX frontmatter, renders sections via
2
+ // SectionRenderer, and emits a JSON-LD <script>.
3
+ import type {
4
+ getMdxSource as defaultGetMdxSource,
5
+ getPageSections as defaultGetPageSections} from "@working-theory/page-renderer";
6
+ import {
7
+ renderPage,
8
+ renderPageMetadata
9
+ } from "@working-theory/page-renderer";
10
+ import type { Metadata } from "next";
11
+ import type { ReactElement } from "react";
12
+
13
+ interface Props {
14
+ params: Promise<{ locale: string }>;
15
+ getMdxSource?: typeof defaultGetMdxSource;
16
+ getPageSections?: typeof defaultGetPageSections;
17
+ }
18
+
19
+ export async function generateMetadata({
20
+ params,
21
+ }: Pick<Props, "params">): Promise<Metadata> {
22
+ const { locale } = await params;
23
+ return renderPageMetadata({ type: "Pricing", slug: "pricing", locale });
24
+ }
25
+
26
+ export default async function Page({
27
+ params,
28
+ getMdxSource,
29
+ getPageSections,
30
+ }: Props): Promise<ReactElement> {
31
+ const { locale } = await params;
32
+ return renderPage({
33
+ type: "Pricing",
34
+ slug: "pricing",
35
+ locale,
36
+ translationsNamespace: "pricing",
37
+ ...(getMdxSource !== undefined && { getMdxSource }),
38
+ ...(getPageSections !== undefined && { getPageSections }),
39
+ });
40
+ }
@@ -0,0 +1,40 @@
1
+ // Privacy page. Reads the PrivacyPage MDX frontmatter, renders sections via
2
+ // SectionRenderer, and emits a JSON-LD <script>.
3
+ import type {
4
+ getMdxSource as defaultGetMdxSource,
5
+ getPageSections as defaultGetPageSections} from "@working-theory/page-renderer";
6
+ import {
7
+ renderPage,
8
+ renderPageMetadata
9
+ } from "@working-theory/page-renderer";
10
+ import type { Metadata } from "next";
11
+ import type { ReactElement } from "react";
12
+
13
+ interface Props {
14
+ params: Promise<{ locale: string }>;
15
+ getMdxSource?: typeof defaultGetMdxSource;
16
+ getPageSections?: typeof defaultGetPageSections;
17
+ }
18
+
19
+ export async function generateMetadata({
20
+ params,
21
+ }: Pick<Props, "params">): Promise<Metadata> {
22
+ const { locale } = await params;
23
+ return renderPageMetadata({ type: "PrivacyPage", slug: "privacy", locale });
24
+ }
25
+
26
+ export default async function Page({
27
+ params,
28
+ getMdxSource,
29
+ getPageSections,
30
+ }: Props): Promise<ReactElement> {
31
+ const { locale } = await params;
32
+ return renderPage({
33
+ type: "PrivacyPage",
34
+ slug: "privacy",
35
+ locale,
36
+ translationsNamespace: "privacy",
37
+ ...(getMdxSource !== undefined && { getMdxSource }),
38
+ ...(getPageSections !== undefined && { getPageSections }),
39
+ });
40
+ }
@@ -0,0 +1,40 @@
1
+ // Terms page. Reads the TermsPage MDX frontmatter, renders sections via
2
+ // SectionRenderer, and emits a JSON-LD <script>.
3
+ import type {
4
+ getMdxSource as defaultGetMdxSource,
5
+ getPageSections as defaultGetPageSections} from "@working-theory/page-renderer";
6
+ import {
7
+ renderPage,
8
+ renderPageMetadata
9
+ } from "@working-theory/page-renderer";
10
+ import type { Metadata } from "next";
11
+ import type { ReactElement } from "react";
12
+
13
+ interface Props {
14
+ params: Promise<{ locale: string }>;
15
+ getMdxSource?: typeof defaultGetMdxSource;
16
+ getPageSections?: typeof defaultGetPageSections;
17
+ }
18
+
19
+ export async function generateMetadata({
20
+ params,
21
+ }: Pick<Props, "params">): Promise<Metadata> {
22
+ const { locale } = await params;
23
+ return renderPageMetadata({ type: "TermsPage", slug: "terms", locale });
24
+ }
25
+
26
+ export default async function Page({
27
+ params,
28
+ getMdxSource,
29
+ getPageSections,
30
+ }: Props): Promise<ReactElement> {
31
+ const { locale } = await params;
32
+ return renderPage({
33
+ type: "TermsPage",
34
+ slug: "terms",
35
+ locale,
36
+ translationsNamespace: "terms",
37
+ ...(getMdxSource !== undefined && { getMdxSource }),
38
+ ...(getPageSections !== undefined && { getPageSections }),
39
+ });
40
+ }
@@ -4,9 +4,11 @@
4
4
  * Returns a plain-text index of AILK's content surface for LLM consumption,
5
5
  * following the llms.txt convention (https://llmstxt.org).
6
6
  *
7
- * Aggregates two surfaces:
8
- * 1. Section-catalog (MDX seeds in content/) via @working-theory/content-adapters
9
- * 2. Fumadocs blog corpus via blogSource.getPages()
7
+ * Indexes the section catalog (MDX seeds in content/) via
8
+ * @working-theory/content-adapters. Nothing is gated: a Free site has no blog
9
+ * (D-46), so there is no blog corpus to omit — `getBlogPages` defaults to an
10
+ * empty list and the docs + blog module ships its own variant of this route
11
+ * that reads `blogSource` (#5365 D9).
10
12
  *
11
13
  * Format per entry:
12
14
  * - Title: <title>
@@ -14,8 +16,9 @@
14
16
  * Description: <description>
15
17
  *
16
18
  * DI note: `createLlmsHandler` accepts optional `getContentAdapter` and
17
- * `getBlogPages` deps for testing — tests inject stubs to avoid mocking
18
- * internal modules. The exported `GET` uses the real implementations.
19
+ * `getBlogPages` deps — tests inject stubs, and the module's variant of this
20
+ * route supplies the real `blogSource` reader. The exported `GET` is
21
+ * unchanged either way.
19
22
  */
20
23
 
21
24
  import { SCHEMA_ORG_TYPES } from "@working-theory/content-adapters";
@@ -23,7 +26,6 @@ import type { ContentAdapter, SchemaOrgType } from "@working-theory/content-adap
23
26
  import { getContentAdapter as defaultGetContentAdapter } from "@working-theory/page-renderer";
24
27
 
25
28
  import { SITE_NAME } from "../../lib/site-brand";
26
- import { blogSource as defaultBlogSource } from "../../lib/source";
27
29
  import { resolveBaseUrl, slugToPath } from "../../src/lib/routes";
28
30
 
29
31
  export interface LlmsHandlerDeps {
@@ -33,8 +35,9 @@ export interface LlmsHandlerDeps {
33
35
  */
34
36
  getContentAdapter?: () => ContentAdapter;
35
37
  /**
36
- * Blog pages getter. Defaults to the real blogSource.getPages().
37
- * Override in tests to inject stub pages without mocking lib/source.
38
+ * Blog pages getter. Defaults to none — Free has no blog (D-46, #5365 D9).
39
+ * The docs + blog module's own copy of this route reads `blogSource`.
40
+ * Override in tests to inject stub pages.
38
41
  */
39
42
  getBlogPages?: () => Array<{ url: string; data: { title: string; description?: string } }>;
40
43
  }
@@ -45,7 +48,7 @@ export interface LlmsHandlerDeps {
45
48
  */
46
49
  export function createLlmsHandler(deps: LlmsHandlerDeps = {}) {
47
50
  const resolveAdapter = deps.getContentAdapter ?? defaultGetContentAdapter;
48
- const resolveBlogPages = deps.getBlogPages ?? (() => defaultBlogSource.getPages() as Array<{ url: string; data: { title: string; description?: string } }>);
51
+ const resolveBlogPages = deps.getBlogPages ?? (() => []);
49
52
 
50
53
  return async function llmsGET(): Promise<Response> {
51
54
  const adapter = resolveAdapter();
@@ -11,11 +11,11 @@ module.exports = {
11
11
  ],
12
12
  testMatch: ["**/__tests__/**/*.test.ts", "**/__tests__/**/*.test.tsx"],
13
13
  testPathIgnorePatterns: ["/node_modules/", "/.next/", "/dist/"],
14
- // Ignore node_modules except fumadocs-* (ESM-only packages loaded via lib/source.ts → fumadocs-core/source).
14
+ // Ignore node_modules except @working-theory/* (their dist is ESM).
15
15
  // Mirrors the canonical jose carve-out used in apps/api, apps/mcp, and apps-paid/license-issuer.
16
16
  // Added: issue #1077
17
17
  transformIgnorePatterns: [
18
- "/node_modules/(?!(\\.pnpm/)?(fumadocs-[^/]*?@?[^/]*?|@working-theory(?:\\+[^/]*?@?[^/]*?|/[^/]+))(/|$))",
18
+ "/node_modules/(?!(\\.pnpm/)?@working-theory(?:\\+[^/]*?@?[^/]*?|/[^/]+)(/|$))",
19
19
  ],
20
20
  moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"],
21
21
  // Workspace packages publish ESM in dist/, but ts-jest runs in CommonJS.
@@ -27,12 +27,6 @@ module.exports = {
27
27
  // Map all *.css to an empty stub — layout.tsx imports ./globals.css (#4090 item 1)
28
28
  // and CSS is not parseable by ts-jest's CommonJS transform (apps-paid/marketing-web precedent, #1531).
29
29
  "\\.css$": "<rootDir>/__tests__/__mocks__/style-mock.js",
30
- // Fumadocs codegen output — produced by `pnpm --filter @working-theory/web build` into .source/
31
- // The server entry uses top-level await + ESM-only fumadocs-mdx/runtime/server;
32
- // map to a CJS stub so ts-jest can load modules that import lib/source.ts. (#1077)
33
- // Stub lives in lib/__mocks__/ (committed) not .source/__mocks__/ (gitignored).
34
- "^@/\\.source/server$": "<rootDir>/lib/__mocks__/source-server.js",
35
- "^@/\\.source$": "<rootDir>/.source/index.ts",
36
30
  "^@working-theory/page-renderer/testing$":
37
31
  "<rootDir>/node_modules/@working-theory/page-renderer/dist/testing.js",
38
32
  "^@working-theory/page-renderer$":
@@ -51,8 +45,6 @@ module.exports = {
51
45
  "^@working-theory/api-client$": "<rootDir>/node_modules/@working-theory/api-client/dist/index.js",
52
46
  "^@working-theory/content-adapters$":
53
47
  "<rootDir>/node_modules/@working-theory/content-adapters/dist/index.js",
54
- "^@working-theory/fumadocs-schema$":
55
- "<rootDir>/node_modules/@working-theory/fumadocs-schema/dist/index.js",
56
48
  "^@working-theory/license$": "<rootDir>/node_modules/@working-theory/license/dist/index.js",
57
49
  "^@working-theory/jsonld$": "<rootDir>/node_modules/@working-theory/jsonld/dist/index.js",
58
50
  "^@working-theory/metadata$": "<rootDir>/node_modules/@working-theory/metadata/dist/index.js",
@@ -66,6 +58,7 @@ module.exports = {
66
58
  "<rootDir>/node_modules/@working-theory/schema-validator/dist/index.js",
67
59
  "^@working-theory/sections-registry$":
68
60
  "<rootDir>/node_modules/@working-theory/sections-registry/dist/index.js",
61
+
69
62
  "^@working-theory/templates/shells$":
70
63
  "<rootDir>/node_modules/@working-theory/templates/dist/shells/index.js",
71
64
  "^@working-theory/templates$": "<rootDir>/node_modules/@working-theory/templates/dist/index.js",
@@ -82,6 +75,14 @@ module.exports = {
82
75
  // tree, where nothing imports it, so the unused mapping is inert there.
83
76
  "^@working-theory/ui/slices/scheduling$":
84
77
  "<rootDir>/node_modules/@working-theory/ui/dist/sections/slices/scheduling.js",
78
+ // #5366: the signal-collectors slice's re-export subpath. Same reasoning
79
+ // as the scheduling mapping above.
80
+ "^@working-theory/ui/slices/signal-collectors$":
81
+ "<rootDir>/node_modules/@working-theory/ui/dist/sections/slices/signal-collectors.js",
82
+ // #5364: the commerce slice's renderer-registration + CheckoutPriceCard
83
+ // re-export subpath. Same reasoning as the scheduling mapping above.
84
+ "^@working-theory/ui/slices/commerce$":
85
+ "<rootDir>/node_modules/@working-theory/ui/dist/sections/slices/commerce.js",
85
86
  "^@working-theory/theme$": "<rootDir>/node_modules/@working-theory/theme/dist/index.js",
86
87
  // site-config publishes ESM in dist/; map to source so ts-jest compiles it
87
88
  // through the same transformer as every sibling above. Only the
@@ -91,6 +92,11 @@ module.exports = {
91
92
  "<rootDir>/node_modules/@working-theory/site-config/dist/schema/index.js",
92
93
  "^@working-theory/validation$": "<rootDir>/node_modules/@working-theory/validation/dist/index.js",
93
94
  "^@working-theory/web-i18n$": "<rootDir>/node_modules/@working-theory/web-i18n/dist/index.js",
95
+ // The dependency-free tenancy-constants subpath (database/tenancy.ts —
96
+ // it imports only a type from @working-theory/ids). Mapped to source like
97
+ // every sibling above; `database/dist` is not built in this workspace
98
+ // pre-`pnpm build`.
99
+ "^@working-theory/database/tenancy$": "<rootDir>/node_modules/@working-theory/database/dist/tenancy.js",
94
100
  // #5025: pnpm keys next-intl's virtual-store path on its resolved peers,
95
101
  // so apps/web and packages/page-renderer can link two PHYSICALLY DISTINCT
96
102
  // copies of the same version — and a `jest.mock("next-intl/server")` in
@@ -126,9 +132,9 @@ module.exports = {
126
132
  },
127
133
  setupFilesAfterEnv: ["<rootDir>/__tests__/setup.ts"],
128
134
  transform: {
129
- // fumadocs-core (16.10.6+) ships `import.meta.env` in its dist JS
130
- // (e.g. dist/url-*.js), which the transformIgnorePatterns carve-out
131
- // above lets reach a transformer instead of being required raw.
135
+ // Some `@working-theory/*` dist JS ships `import.meta`, which the
136
+ // transformIgnorePatterns carve-out above lets reach a transformer
137
+ // instead of being required raw.
132
138
  // `import.meta` has no CJS form, so it must run through the shared
133
139
  // import.meta -> {} rewrite BEFORE ts-jest — ts-jest's own
134
140
  // astTransformers hook never fires for a node_modules JS file (see the
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Tests for `forwardedCookieInit` — the RSC cookie/origin-forwarding helper
3
+ * (#5371 shepherd fix-up).
4
+ *
5
+ * Contract: the returned `init.headers` always carries the forwarded
6
+ * `cookie`, and carries `origin` ONLY when the incoming request's `Origin`
7
+ * header is present — never a fabricated value. This is what lets a Server
8
+ * Action `PUT`/`PATCH` through `@working-theory/api-client`'s `workspace.*`
9
+ * namespace pass `apps/api`'s same-origin CSRF check (`isOriginAllowed`),
10
+ * which Node's server-side `fetch` cannot satisfy on its own since it never
11
+ * sets `Origin`.
12
+ */
13
+ import { forwardedCookieInit } from "../server-api";
14
+
15
+ const mockCookies = jest.fn();
16
+ const mockHeaders = jest.fn();
17
+
18
+ jest.mock("next/headers", () => ({
19
+ __esModule: true,
20
+ cookies: (...args: unknown[]) => mockCookies(...args),
21
+ headers: (...args: unknown[]) => mockHeaders(...args),
22
+ }));
23
+
24
+ describe("forwardedCookieInit", () => {
25
+ beforeEach(() => {
26
+ mockCookies.mockReset();
27
+ mockHeaders.mockReset();
28
+ });
29
+
30
+ it("forwards cookie + origin when the incoming request carries an Origin header", async () => {
31
+ mockCookies.mockResolvedValue({
32
+ toString: () => "better-auth.session_token=abc",
33
+ });
34
+ mockHeaders.mockResolvedValue(
35
+ new Headers({ origin: "https://app.example.com" }),
36
+ );
37
+
38
+ const init = await forwardedCookieInit();
39
+
40
+ expect(init).toEqual({
41
+ headers: {
42
+ cookie: "better-auth.session_token=abc",
43
+ origin: "https://app.example.com",
44
+ },
45
+ });
46
+ });
47
+
48
+ it("forwards cookie alone — no origin key at all — when Origin is absent", async () => {
49
+ mockCookies.mockResolvedValue({
50
+ toString: () => "better-auth.session_token=abc",
51
+ });
52
+ mockHeaders.mockResolvedValue(new Headers());
53
+
54
+ const init = await forwardedCookieInit();
55
+
56
+ expect(init).toEqual({
57
+ headers: { cookie: "better-auth.session_token=abc" },
58
+ });
59
+ expect(init.headers).not.toHaveProperty("origin");
60
+ });
61
+ });
@@ -3,22 +3,26 @@
3
3
  * (#4665 D9).
4
4
  *
5
5
  * Forwards the incoming request's cookies to `@working-theory/api-client`'s
6
- * `waitlistExperiments` namespace (B0, #4671) so `apps/api`'s cookie-session
7
- * auth preHandler resolves the SAME Better Auth session the browser holds —
8
- * both apps share the session store. GETs only, every page is an RSC; there
9
- * is no client fetch and no `NEXT_PUBLIC_` credential (D9).
6
+ * `waitlistExperiments` / `workspace` namespaces (B0, #4671; #5371) so
7
+ * `apps/api`'s cookie-session auth preHandler resolves the SAME Better Auth
8
+ * session the browser holds — both apps share the session store. Every page
9
+ * is an RSC and every write is a Server Action; there is no client fetch and
10
+ * no `NEXT_PUBLIC_` credential (D9). For a Server Action `PUT`/`PATCH`, the
11
+ * incoming request's `Origin` is forwarded alongside the cookie so
12
+ * `apps/api`'s same-origin CSRF check (`isOriginAllowed`) passes — Node's
13
+ * server-side `fetch` never sets `Origin` on its own.
10
14
  *
11
15
  * `apps/web` may not import `apps/api` directly (architecture.yaml) — every
12
- * read goes through `@working-theory/api-client`'s typed namespace, never a
16
+ * call goes through `@working-theory/api-client`'s typed namespace, never a
13
17
  * hand-rolled `fetch`. This module supplies exactly the two things that
14
- * namespace doesn't own: the cookie-forwarding `init` (B0's additive
18
+ * namespace doesn't own: the cookie/origin-forwarding `init` (B0's additive
15
19
  * `init?: { headers }` on `http.get`) and the envelope→`ApiError` unwrap
16
20
  * (mirrors `apps-paid/workspace/lib/api-error.ts`'s `unwrapEnvelope`, adapted to
17
21
  * the `@working-theory/api-client` `ApiResult<T>` shape B0 already returns
18
22
  * rather than re-parsing the wire envelope by hand).
19
23
  */
20
24
 
21
- import { cookies } from "next/headers";
25
+ import { cookies, headers } from "next/headers";
22
26
 
23
27
  /**
24
28
  * Structural mirror of `@working-theory/api-client`'s `ApiResult<T>`
@@ -57,15 +61,27 @@ interface ErrorBody {
57
61
  }
58
62
 
59
63
  /**
60
- * Forward the incoming request's cookies as the `init` every
61
- * `waitlistExperiments.*` call accepts. Call once per request — `cookies()`
62
- * reads the current RSC request context.
64
+ * Forward the incoming request's cookies — and, when present, its `Origin` —
65
+ * as the `init` every `waitlistExperiments.*` / `workspace.*` call accepts.
66
+ * Call once per request — `cookies()` / `headers()` read the current RSC
67
+ * request context.
68
+ *
69
+ * The `Origin` forward matters for every non-GET call: `apps/api`'s
70
+ * `authPreHandler` (`isOriginAllowed`) requires a matching `Origin` header on
71
+ * a cookie-authenticated mutating request, and Node's server-side `fetch`
72
+ * (unlike a browser XHR/fetch) never sets one on its own. A Server Action's
73
+ * incoming request DOES carry the browser's real `Origin`, so re-forwarding
74
+ * it here — rather than fabricating one — lets a genuine same-origin write
75
+ * pass the check exactly as a browser-originated one would. RSC `GET`s are
76
+ * unaffected: `isOriginAllowed` short-circuits `true` for safe methods before
77
+ * it ever inspects the header.
63
78
  */
64
79
  export async function forwardedCookieInit(): Promise<{
65
- headers: { cookie: string };
80
+ headers: { cookie: string; origin?: string };
66
81
  }> {
67
82
  const cookieHeader = (await cookies()).toString();
68
- return { headers: { cookie: cookieHeader } };
83
+ const origin = (await headers()).get("origin");
84
+ return { headers: { cookie: cookieHeader, ...(origin ? { origin } : {}) } };
69
85
  }
70
86
 
71
87
  /**
@@ -171,6 +171,14 @@
171
171
  "heading": "Partners",
172
172
  "emptyState": "Partners listing coming soon."
173
173
  },
174
+ "pricing": {
175
+ "heading": "Pricing",
176
+ "emptyState": "Pricing page content coming soon."
177
+ },
178
+ "privacy": {
179
+ "heading": "Privacy Policy",
180
+ "emptyState": "Privacy policy content coming soon."
181
+ },
174
182
  "products": {
175
183
  "heading": "Products",
176
184
  "emptyState": "Products listing coming soon."
@@ -215,6 +223,10 @@
215
223
  "description": "Deep dives into the architecture and internals of this site.",
216
224
  "emptyState": "Technical articles coming soon."
217
225
  },
226
+ "terms": {
227
+ "heading": "Terms of Service",
228
+ "emptyState": "Terms of service content coming soon."
229
+ },
218
230
  "testimonials": {
219
231
  "heading": "Testimonials",
220
232
  "emptyState": "Testimonials coming soon."
@@ -395,5 +407,51 @@
395
407
  "emptyHeadline": "No purchases yet",
396
408
  "emptyDescription": "Purchases you complete will appear here."
397
409
  }
410
+ },
411
+ "workspace": {
412
+ "title": "Workspace settings",
413
+ "description": "Your workspace name, site settings, and team.",
414
+ "nav": {
415
+ "account": "Account",
416
+ "general": "General",
417
+ "site": "Site",
418
+ "team": "Team"
419
+ },
420
+ "general": {
421
+ "heading": "General",
422
+ "nameLabel": "Workspace name",
423
+ "saveButton": "Save",
424
+ "saving": "Saving\u2026",
425
+ "saved": "Saved",
426
+ "saveError": "Could not save. Try again."
427
+ },
428
+ "site": {
429
+ "heading": "Site",
430
+ "identityHeading": "Identity",
431
+ "identityName": "Site name",
432
+ "identityUrl": "Site URL",
433
+ "identityDescription": "Description",
434
+ "seoHeading": "SEO",
435
+ "seoDescription": "Meta description",
436
+ "seoOgImage": "Social share image URL",
437
+ "themeHeading": "Theme",
438
+ "themeLightPrimary": "Primary colour (light)",
439
+ "themeLightAccent": "Accent colour (light)",
440
+ "themeDarkPrimary": "Primary colour (dark)",
441
+ "themeDarkAccent": "Accent colour (dark)",
442
+ "themeUnconfigured": "This site uses the default theme. Colours become editable once a theme is configured for the workspace.",
443
+ "saveButton": "Save",
444
+ "saving": "Saving\u2026",
445
+ "saved": "Saved",
446
+ "saveError": "Could not save. Try again."
447
+ },
448
+ "team": {
449
+ "heading": "Team",
450
+ "name": "Name",
451
+ "email": "Email",
452
+ "role": "Role",
453
+ "emptyHeadline": "No members yet",
454
+ "emptyDescription": "People with access to this workspace will appear here."
455
+ }
398
456
  }
399
457
  }