create-ailk 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/README.md +55 -8
  2. package/component-catalog.json +336 -27
  3. package/component-catalog.md +25 -12
  4. package/dist/lib/api-boot-assertion.d.ts +46 -0
  5. package/dist/lib/api-boot-assertion.js +70 -0
  6. package/dist/lib/import-graph.d.ts +29 -0
  7. package/dist/lib/import-graph.js +140 -0
  8. package/dist/module-architecture.d.ts +1 -0
  9. package/dist/module-architecture.js +253 -127
  10. package/dist/surfaces.js +105 -7
  11. package/dist/template-manifest.d.ts +33 -0
  12. package/dist/template-manifest.js +99 -16
  13. package/dist/template-names.d.ts +1 -1
  14. package/dist/template-names.js +1 -0
  15. package/package.json +3 -2
  16. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
  17. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
  18. package/site-templates/marketing-site/ailk-template.json +1 -1
  19. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
  20. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
  21. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
  22. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
  23. package/site-templates/minimalism-site/ailk-template.json +2 -1
  24. package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
  25. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
  26. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
  27. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
  28. package/site-templates/simple-service/ailk-template.json +10 -0
  29. package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
  30. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
  31. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
  32. package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
  33. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
  34. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
  35. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
  36. package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
  37. package/site-templates/simple-service/provenance.json +4 -0
  38. package/templates/.env.example +72 -7
  39. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
  40. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
  41. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
  42. package/templates/apps/api/.env.example +13 -0
  43. package/templates/apps/api/CLAUDE.md +26 -2
  44. package/templates/apps/api/jest.config.cjs +3 -0
  45. package/templates/apps/api/package.json +1 -0
  46. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
  47. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
  48. package/templates/apps/api/src/__tests__/server.test.ts +471 -0
  49. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
  50. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
  51. package/templates/apps/api/src/config/index.ts +62 -0
  52. package/templates/apps/api/src/config/modules.ts +7 -3
  53. package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
  54. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
  55. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
  56. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
  57. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
  58. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
  59. package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
  60. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
  61. package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
  62. package/templates/apps/api/src/lib/content-adapter.ts +57 -31
  63. package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
  64. package/templates/apps/api/src/lib/error-serializer.ts +23 -3
  65. package/templates/apps/api/src/lib/outbound-http.ts +7 -2
  66. package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
  67. package/templates/apps/api/src/lib/site.ts +208 -9
  68. package/templates/apps/api/src/lib/stripe.ts +66 -0
  69. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
  70. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
  71. package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
  72. package/templates/apps/api/src/openapi/spec.ts +543 -2
  73. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
  74. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
  75. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
  76. package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
  77. package/templates/apps/api/src/routes/auth/user.ts +61 -0
  78. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
  79. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
  80. package/templates/apps/api/src/routes/checkout/index.ts +34 -0
  81. package/templates/apps/api/src/routes/checkout/session.ts +139 -0
  82. package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
  83. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
  84. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
  85. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
  86. package/templates/apps/api/src/routes/content/get-page.ts +94 -12
  87. package/templates/apps/api/src/routes/content/index.ts +13 -73
  88. package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
  89. package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
  90. package/templates/apps/api/src/routes/content/search.ts +129 -0
  91. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
  92. package/templates/apps/api/src/routes/leads/index.ts +100 -13
  93. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
  94. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
  95. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
  96. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
  97. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
  98. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
  99. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
  100. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
  101. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
  102. package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
  103. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
  104. package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
  105. package/templates/apps/api/src/routes/requests/index.ts +54 -0
  106. package/templates/apps/api/src/routes/webhooks/README.md +265 -0
  107. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
  108. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
  109. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
  110. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
  111. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
  112. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
  113. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
  114. package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
  115. package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
  116. package/templates/apps/api/src/server.ts +439 -49
  117. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
  118. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
  119. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
  120. package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
  121. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
  122. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
  123. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
  124. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
  125. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
  126. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
  127. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
  128. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
  129. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
  130. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
  131. package/templates/apps/api/src/services/abandon-email.ts +8 -2
  132. package/templates/apps/api/src/services/aeo-score.ts +49 -12
  133. package/templates/apps/api/src/services/audit-log.ts +668 -0
  134. package/templates/apps/api/src/services/consent.ts +18 -0
  135. package/templates/apps/api/src/services/default-tenant.ts +70 -31
  136. package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
  137. package/templates/apps/api/src/services/dunning-policy.ts +294 -0
  138. package/templates/apps/api/src/services/entitlements.ts +80 -0
  139. package/templates/apps/api/src/services/lead-promotion.ts +13 -14
  140. package/templates/apps/api/src/services/lead-routing.ts +161 -2
  141. package/templates/apps/api/src/services/leads-inbox.ts +23 -2
  142. package/templates/apps/api/src/services/listing-config.ts +27 -9
  143. package/templates/apps/api/src/services/project-listings.ts +201 -6
  144. package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
  145. package/templates/apps/api/src/services/request-confirm.ts +646 -0
  146. package/templates/apps/api/src/services/revalidation.ts +173 -0
  147. package/templates/apps/api/src/services/verified-email.ts +71 -0
  148. package/templates/apps/api/src/vercel-handler.ts +75 -35
  149. package/templates/apps/mcp/.env.example +17 -0
  150. package/templates/apps/mcp/CLAUDE.md +57 -24
  151. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
  152. package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
  153. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
  154. package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
  155. package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
  156. package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
  157. package/templates/apps/mcp/__tests__/index.test.ts +39 -0
  158. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
  159. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
  160. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
  161. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
  162. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
  163. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
  164. package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
  165. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
  166. package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
  167. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
  168. package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
  169. package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
  170. package/templates/apps/mcp/__tests__/server.test.ts +67 -14
  171. package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
  172. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
  173. package/templates/apps/mcp/jest.config.cjs +13 -0
  174. package/templates/apps/mcp/package.json +2 -0
  175. package/templates/apps/mcp/src/abuse/index.ts +159 -24
  176. package/templates/apps/mcp/src/config/modules.ts +10 -5
  177. package/templates/apps/mcp/src/http-transport.ts +102 -29
  178. package/templates/apps/mcp/src/index.ts +73 -11
  179. package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
  180. package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
  181. package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
  182. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
  183. package/templates/apps/mcp/src/lib/prisma.ts +39 -0
  184. package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
  185. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
  186. package/templates/apps/mcp/src/server.ts +59 -35
  187. package/templates/apps/mcp/src/tools/audit.ts +26 -2
  188. package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
  189. package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
  190. package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
  191. package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
  192. package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
  193. package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
  194. package/templates/apps/mcp/src/tools/index.ts +6 -4
  195. package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
  196. package/templates/apps/mcp/src/tools/purchase.ts +128 -45
  197. package/templates/apps/mcp/src/tools/read_content.ts +20 -6
  198. package/templates/apps/mcp/src/tools/search_content.ts +11 -4
  199. package/templates/apps/web/.env.example +71 -5
  200. package/templates/apps/web/CLAUDE.md +44 -0
  201. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
  202. package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
  203. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
  204. package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
  205. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
  206. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
  207. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
  208. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
  209. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
  210. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
  211. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
  212. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
  213. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
  214. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
  215. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
  216. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
  217. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
  218. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
  219. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
  220. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
  221. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
  222. package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
  223. package/templates/apps/web/app/[locale]/layout.tsx +133 -87
  224. package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
  225. package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
  226. package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
  227. package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
  228. package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
  229. package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
  230. package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
  231. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
  232. package/templates/apps/web/app/api/revalidate/route.ts +194 -0
  233. package/templates/apps/web/auth.ts +407 -8
  234. package/templates/apps/web/jest.config.cjs +8 -4
  235. package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
  236. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
  237. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
  238. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
  239. package/templates/apps/web/lib/auth-email.ts +318 -0
  240. package/templates/apps/web/lib/auth-env.ts +85 -0
  241. package/templates/apps/web/lib/auth-routes.ts +55 -0
  242. package/templates/apps/web/lib/meeting-slugs.ts +53 -0
  243. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  244. package/templates/apps/web/messages/en.json +187 -0
  245. package/templates/apps/web/package.json +2 -1
  246. package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
  247. package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
  248. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
  249. package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
  250. package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
  251. package/templates/database/CHANGELOG.md +142 -0
  252. package/templates/database/README.md +10 -3
  253. package/templates/database/__tests__/appointment.db.test.ts +106 -0
  254. package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
  255. package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
  256. package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
  257. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
  258. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
  259. package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
  260. package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
  261. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
  262. package/templates/database/__tests__/tenancy.test.ts +141 -0
  263. package/templates/database/config/schema.prisma +79 -2
  264. package/templates/database/content/schema.prisma +87 -11
  265. package/templates/database/inbox/schema.prisma +452 -9
  266. package/templates/database/jest.config.cjs +5 -3
  267. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
  268. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
  269. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
  270. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
  271. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
  272. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
  273. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
  274. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
  275. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
  276. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
  277. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
  278. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
  279. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
  280. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
  281. package/templates/database/package.json +1 -1
  282. package/templates/database/tenancy.ts +86 -1
  283. package/templates/database/tsconfig.json +1 -0
  284. package/versions.json +34 -34
  285. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
  286. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
  287. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
  288. package/templates/apps/api/src/routes/content/create.ts +0 -115
  289. package/templates/apps/api/src/routes/content/delete.ts +0 -82
  290. package/templates/apps/api/src/routes/content/update.ts +0 -112
  291. package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
  292. package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
  293. package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
  294. package/templates/apps/mcp/src/tools/create_page.ts +0 -96
  295. package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
  296. package/templates/apps/mcp/src/tools/update_page.ts +0 -92
  297. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
  298. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
  299. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
@@ -15,13 +15,13 @@ The component inventory of `@working-theory/ui` and the shells of `@working-theo
15
15
  3. Check the Primitives, Panels and Shells tables too — a low-level control, a collapsible layout container, or a route-level frame lives there, not under Sections or Blocks.
16
16
  4. Only then build. If you do build, you are declaring the capability is genuinely absent.
17
17
 
18
- Inventory: **286 component files** across 16 tiers — 54 sections, 86 blocks, 17 chrome, 79 primitives, 6 panels, 3 shells, 5 docs-blog-blocks, 1 docs-blog-chrome, 2 docs-blog-shells, 2 scheduling-sections, 5 scheduling-blocks, 3 signal-collectors-sections, 8 signal-collectors-blocks, 2 commerce-sections, 12 commerce-blocks, 1 commerce-panels. 283 distinct names: `SchedulerEmbed`, `Separator`, `Tabs` exist in more than one tier as genuinely different components, listed once per tier with its own source path.
18
+ Inventory: **298 component files** across 16 tiers — 54 sections, 95 blocks, 18 chrome, 79 primitives, 6 panels, 5 shells, 5 docs-blog-blocks, 1 docs-blog-chrome, 2 docs-blog-shells, 2 scheduling-sections, 5 scheduling-blocks, 3 signal-collectors-sections, 8 signal-collectors-blocks, 2 commerce-sections, 12 commerce-blocks, 1 commerce-panels. 295 distinct names: `SchedulerEmbed`, `Separator`, `Tabs` exist in more than one tier as genuinely different components, listed once per tier with its own source path.
19
19
 
20
- Description coverage: **282/286** components carry a capability description derived from source — 43 from the section registry's hand-authored when-to-use text, 213 from the component's own doc-comment header, 26 from its tier barrel's roster line. **4** have none; they are listed under [Undescribed components](#undescribed-components) rather than given a sentence invented from their name.
20
+ Description coverage: **294/298** components carry a capability description derived from source — 43 from the section registry's hand-authored when-to-use text, 220 from the component's own doc-comment header, 31 from its tier barrel's roster line. **4** have none; they are listed under [Undescribed components](#undescribed-components) rather than given a sentence invented from their name.
21
21
 
22
- Of those, **253** are *strong* — a capability sentence you could match a requirement against — and **29** are *weak*: either one short clause, or a long one that spends itself on wiring (client boundaries, class names, which primitives it composes) instead of on what the thing is for. Weak rows are marked `(weak)` and listed in full under [Weak descriptions](#weak-descriptions). Coverage is 99%, but **findable-without-luck coverage is 88%**.
22
+ Of those, **264** are *strong* — a capability sentence you could match a requirement against — and **30** are *weak*: either one short clause, or a long one that spends itself on wiring (client boundaries, class names, which primitives it composes) instead of on what the thing is for. Weak rows are marked `(weak)` and listed in full under [Weak descriptions](#weak-descriptions). Coverage is 99%, but **findable-without-luck coverage is 89%**.
23
23
 
24
- Per tier, strong out of total: **Sections** 53/54 · **Blocks** 81/86 · **Chrome** 10/17 · **Primitives** 64/79 (4 undescribed) · **Panels** 5/6 · **Shells** 2/3 · **Docs + Blog Blocks** 5/5 · **Docs + Blog Chrome** 1/1 · **Docs + Blog Shells** 0/2 · **Scheduling Sections** 2/2 · **Scheduling Blocks** 5/5 · **Signal-Collectors Sections** 3/3 · **Signal-Collectors Blocks** 7/8 · **Commerce Sections** 2/2 · **Commerce Blocks** 12/12 · **Commerce Panels** 1/1. A tier low on this line is a tier where a capability search is most likely to come up empty on something that exists.
24
+ Per tier, strong out of total: **Sections** 53/54 · **Blocks** 90/95 · **Chrome** 10/18 · **Primitives** 64/79 (4 undescribed) · **Panels** 5/6 · **Shells** 4/5 · **Docs + Blog Blocks** 5/5 · **Docs + Blog Chrome** 1/1 · **Docs + Blog Shells** 0/2 · **Scheduling Sections** 2/2 · **Scheduling Blocks** 5/5 · **Signal-Collectors Sections** 3/3 · **Signal-Collectors Blocks** 7/8 · **Commerce Sections** 2/2 · **Commerce Blocks** 12/12 · **Commerce Panels** 1/1. A tier low on this line is a tier where a capability search is most likely to come up empty on something that exists.
25
25
 
26
26
  That grade is **mechanical**. It counts what it can measure — how much a description adds beyond the component's own name, how much of it is spent on wiring, whether the extraction came out damaged — and it cannot tell a fluent paragraph about the wrong subject from a useful one. A person reading these tables will find more rows unusable than the weak count admits. Treat it as a floor on the problem, not a measurement of it, and read the source before concluding a capability is absent.
27
27
 
@@ -86,7 +86,7 @@ Import: `import { X } from "@working-theory/ui/sections";` (or from the `@workin
86
86
  | `FormLightbox` | The VideoLightbox dialog shell hosting a StepFlow instead of a video: a trigger (e.g. a "Let's Start" Button — any focusable element the caller supplies) opens the SAME StepFlow inside a Radix Dialog on the dimmed/blurred backdrop shared with VideoLightbox/SpotlightPanel. | `import { FormLightbox } from "@working-theory/ui/sections"` |
87
87
  | `Gallery` | An image gallery — use to showcase product screenshots or visual work. | MDX `type: gallery` · `import { Gallery } from "@working-theory/ui/sections"` |
88
88
  | `GlossaryIndex` | An A-Z glossary index — use for a browsable list of defined terms. | MDX `type: glossary-index` · `import { GlossaryIndex } from "@working-theory/ui/sections"` |
89
- | `Hero` | Top-of-page introduction — headline, optional subhead and CTAs; use once per page as the first section. | MDX `type: hero` · `import { Hero } from "@working-theory/ui/sections"` |
89
+ | `Hero` | Top-of-page introduction — headline, optional subhead and CTAs; use once per page as the first section. On `variant: 'centered'` the optional `lockup` image renders in the headline's place (the headline string becomes its accessible name), centered over an optional full-bleed `backdrop` at every width — the landing-slide treatment. | MDX `type: hero` · `import { Hero } from "@working-theory/ui/sections"` |
90
90
  | `HeroFormSlot` | The panel hero's right-column gate card: a `FormCard` wrapping either the consumer's conversational intake (when eligible and a `HeroIntakeProvider` is mounted) or the embedded multi-step `StepFlow`, which is also where `controls.showForm` hands the surface over. /. | `import { HeroFormSlot } from "@working-theory/ui/sections"` |
91
91
  | `HeroIntakeContext` | The bounded seam through which a consumer page mounts its own conversational intake as the FIRST surface INSIDE one of Hero's existing gate surfaces (the `lightboxCta` FormLightbox, or the panel variant's form-slot card), with the existing multi-step StepFlow as the in-gate fallback (correcting 's whole-page swap). | `import { HeroIntakeContext } from "@working-theory/ui/sections"` |
92
92
  | `HeroPromptCapture` | The Hero `promptCapture` opt-in's wiring. Composes the two new blocks — `PromptCaptureCard` (the chat-style card) and `SuggestionChipCarousel` (the one-expanded-slot chip row) — and owns the ONE piece of state they share: the typed text. A chip click writes into it; the card renders and edits it; submit carries it onward. | `import { HeroPromptCapture } from "@working-theory/ui/sections"` |
@@ -118,7 +118,7 @@ Import: `import { X } from "@working-theory/ui/sections";` (or from the `@workin
118
118
 
119
119
  ---
120
120
 
121
- ## Blocks (86)
121
+ ## Blocks (95)
122
122
 
123
123
  Intermediate composition tiles — the pieces sections are built from. Compose primitives and panels; never import a section.
124
124
 
@@ -133,7 +133,8 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
133
133
  | `ArticlePager` | The footer prev/next pager. Given the previous and next destinations — docs pages in tree order, or blog posts in date order — renders two link tiles (a directional label + the destination title). Either side may be absent (first / last item) — the present side still anchors to its edge. Renders nothing when both sides are absent. | `import { ArticlePager } from "@working-theory/ui/blocks"` |
134
134
  | `AskAiCommandGroup` | The ⌘K palette's composable "Ask AI" group; one CommandItem per provider seeding "About <entity>: <query>" (or the entity promptText when the query is empty); composed by the consumer's own CommandDialog. | `import { AskAiCommandGroup } from "@working-theory/ui/blocks"` |
135
135
  | `AskAiProviderRow` | The ONE shared provider-link row every ask-AI placement composes — the AskAi section (band + ticker variant) and the Hero `askAi` opt-in all render THIS block, never a per-placement fork. Renders a bounded set of five measured providers, each a deep link seeded with an AUTHORED `promptText` — the prompt is always consumer-supplied data, never derived from page context (headline, entity name, etc.). | `import { AskAiProviderRow } from "@working-theory/ui/blocks"` |
136
- | `AuthCard` | Logo (top-center, inside). Axes: intent: "sign-in" \| "sign-up" — drives heading / subhead / footer copy. method: "password" \| "magic-link" \| "sso" — drives the body + primary action. | `import { AuthCard } from "@working-theory/ui/blocks"` |
136
+ | `AssetLightbox` | The asset detail lightbox: the image with zoom on the stage, a Details rail with in-place pencil-edit Title and Description, then Filename/Type/Size/Dimensions/Created, then Tags (from the seeded set the caller passes), then Copy URL/Replace/Delete. Renders inside a ResponsiveSheet — the SAME field sequence in both its dialog and sheet forms (: one field array, walked once, shared by both forms). | `import { AssetLightbox } from "@working-theory/ui/blocks"` |
137
+ | `AuthCard` | Multi-flow auth card: sign-in, sign-up, invitation, forgot, reset and sent, from one skeleton. One self-contained card renders every auth surface a site needs: signing in, signing up, a closed sign-up that requires an invitation, requesting a password reset, choosing a new password, and the check-your-email state that follows a magic link or a reset request. | `import { AuthCard } from "@working-theory/ui/blocks"` |
137
138
  | `AwardBadgePlaceholder` | The labeled placeholder an Awards badge slot renders when no image has been supplied — mirrors MediaPlaceholder's role but in an award-chevron/ laurel badge silhouette rather than a generic photo frame, since an unfilled award slot should read as "an award badge goes here," not "a photo goes here." The shape (a laurel-flanked medallion over a chevron ribbon) is a hand-drawn SVG outline — never a copied G2/Capterra/Apollo asset. | internal — compose from `packages/ui/src/blocks/AwardBadgePlaceholder.tsx` |
138
139
  | `BackgroundPattern` | Decorative SVG dot-grid / line pattern for ContactForm section. Decorative block (generalized beyond its original single consumer). Renders an absolutely-positioned decoration behind a section's content. Originally the pattern anchored behind ContactForm's form column; SpotlightPanel composes it as a full-bleed section backdrop (dimmed + blurred, behind a centered card) — same block, a second placement. | `import { BackgroundPattern } from "@working-theory/ui/blocks"` |
139
140
  | `BadgeRow` | Horizontal cluster of static badge labels used by Hero and Feature section families for `data.badge` and tag-list patterns. | `import { BadgeRow } from "@working-theory/ui/blocks"` |
@@ -151,7 +152,7 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
151
152
  | `ContactFormFields` | Renders the form field grid (Input / Textarea / Select primitives) + submit Button + optional consent text footer. | `import { ContactFormFields } from "@working-theory/ui/blocks"` |
152
153
  | `ContactInfoAside` | For ContactForm 'split-with-pattern' variant. Renders address / phone / email contact info rows. Each row: sr-only <dt> label + visible <dd> with Icon + Text. Phone and email render as tel:/mailto: links (click-to-call / click-to-email). | `import { ContactInfoAside } from "@working-theory/ui/blocks"` |
153
154
  | `ConversationalIntake` | The UI half of the conversational-intake feature: renders the transcript, closed-question answer pills, a free-text form, a thinking indicator, and Skip — driven entirely through the `NextTurn` interface exported by `./engine` (merged). | `import { ConversationalIntake } from "@working-theory/ui/blocks"` |
154
- | `DescriptionList` | Semantic <dl> of aligned term/definition rows (rows + inline layouts). A semantic <dl> rendering label/value rows from an `items` prop. Each item is a <dt> (muted, min-width-aligned term) paired with a <dd> (value). The value is a ReactNode, so callers compose richer values (spans, lists, mono text) inside it. | `import { DescriptionList } from "@working-theory/ui/blocks"` |
155
+ | `DescriptionList` | Semantic <dl> of aligned term/definition rows (rows + inline layouts). A semantic <dl> rendering label/value rows from an `items` prop. Each item is a <dt> (muted term) paired with a <dd> (value). The value is a ReactNode, so callers compose richer values (spans, lists, mono text) inside it. | `import { DescriptionList } from "@working-theory/ui/blocks"` |
155
156
  | `DetailCard` | The collapsible console / data-panel card: a composable card with a header (title + optional actions + collapse toggle), an optional tab bar, a body slot, and an optional footer. The "deferred SidePanel DetailCard" from, built as a general content card any console / detail view can compose without inventing its own card structure. | `import { DetailCard } from "@working-theory/ui/blocks"` |
156
157
  | `DetailHeader` | The entity-bearing sibling of PageHeader: a Breadcrumb wayfinding trail above an EntityHeader identity row, plus a forwarded actions slot. | `import { DetailHeader } from "@working-theory/ui/blocks"` |
157
158
  | `DistributionList` | Per-option answer-share list over the Progress primitive. One question's answer distribution: a title, an "answered" count line, and one row per declared option (a label, a pre-formatted share `display` string, and a `Progress` bar). `display` is pre-formatted by the caller (Rule 10 — this block never formats numbers itself); `share` drives the `Progress` fill, scaled to the primitive's 0–100 range. | `import { DistributionList } from "@working-theory/ui/blocks"` |
@@ -167,12 +168,14 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
167
168
  | `KeyPointsPanel` | ONE shared block composed by two consumers: Hero's `panel` variant (text-left / panel-right) and Pricing's `band-hero` arrangement (header-left / panel-right, tiers below). | internal — compose from `packages/ui/src/blocks/KeyPointsPanel.tsx` |
168
169
  | `LinkCard` | A whole-tile link card: the entire tile IS the anchor (Claude-docs link-card anatomy). Optional leading icon, a title heading, and an optional description paragraph. Hover fill + chrome are intrinsic to the block — never props. | `import { LinkCard } from "@working-theory/ui/blocks"` |
169
170
  | `LinkListItem` | Hub-page spoke list row: prose Link + optional description, enlarged to the 44px touch-target floor. Fix: the enlarged-hit-area pattern (same idiom as ShortcutTrigger, SegmentedControl's touchTargetFloor) — `min-h-(control-height-big)` clamps the anchor's clickable box to 44px while `inline-flex items-center` keeps the visible text at its normal prose size. | `import { LinkListItem } from "@working-theory/ui/blocks"` |
171
+ | `ListRow` | The PHONE form of one admin list item. The reference is `apps-paid/admin/design/demos/first-steps/demo.html:475` (`rowDoc`), which lays the row out as: a leading type glyph, a two-line text column with the MUTED META LINE FIRST and the item's name second (every phone screen in the demo agrees), an optional trailing control, and a chevron. | `import { ListRow } from "@working-theory/ui/blocks"` |
172
+ | `ListTable` | The desktop form: a fixed-layout list table that never wraps, with the shared Tags and Live cells. The reference is `apps-paid/admin/design/demos/first-steps/demo.html`'s `.tbl`: `table-layout:fixed` on the table and `white-space:nowrap;overflow:hidden;text-overflow:ellipsis` on every cell. BOTH are load-bearing — `table-fixed` alone lets the cell wrap, `truncate` alone lets the browser widen the column to fit. | `import { ListTable } from "@working-theory/ui/blocks"` |
170
173
  | `LocaleSwitcher` | Compact globe + locale dropdown. Extracts the "🌐 English ▾" affordance that lived as an inline node in the chrome Footer story into a reusable block. Composes the `Select` primitive (NOT a raw <select>): a leading globe icon, the current locale label (via SelectValue), and the trigger's built-in chevron. | `import { LocaleSwitcher } from "@working-theory/ui/blocks"` |
171
174
  | `LocaleSwitcherNav` | Composes the controlled `LocaleSwitcher` block and owns the navigation side-effect that block deliberately leaves to its consumer: on a locale change it writes the `NEXT_LOCALE` cookie and navigates to the as-needed prefixed path (default locale unprefixed, every other locale `/<code>` + path — mirroring `apps/web/middleware.ts`'s `localePrefix: "as-needed"`). | `import { LocaleSwitcherNav } from "@working-theory/ui/blocks"` |
172
175
  | `LogoRow` | The ONE logo-strip implementation; bounded layout/treatment/logoSize plus an opt-in motion-safe marquee; composed by the LogoCloud section and the Hero logoStrip opt-in. | `import { LogoRow } from "@working-theory/ui/blocks"` |
173
176
  | `MarkdownViewToggle` | 2-state rendered ↔ markdown view toggle for content pages. The human complement to the agent-facing /llms.txt surface. Mirrors the Claude-docs "view/copy as markdown" affordance. Ships at Tier 3 (Block) because it is an interactive view-toggle control composing the SegmentedControl primitive with the CodeBlock block — not a page-level layout shell. | `import { MarkdownViewToggle } from "@working-theory/ui/blocks"` |
174
177
  | `MediaFigure` | The reusable *frame* for the visual half of a split section: an `Image` constrained by an optional `AspectRatio`, with consistent rounding, an optional caption, an optional decorative frame, and an optional overlay slot + scrim for text/badges/play-buttons over media. | `import { MediaFigure } from "@working-theory/ui/blocks"` |
175
- | `MediaPlaceholder` | The single shared labeled placeholder a declared IMAGERY SLOT renders when no asset has been supplied yet — a chassis never silently ships an empty slot, and never renders an accidental image. Composed by `Hero`/`Feature`/`Gallery` (sections tier) whenever their `imagerySlot` data flag is `true` and the corresponding asset is absent. | internal — compose from `packages/ui/src/blocks/MediaPlaceholder.tsx` |
178
+ | `MediaPlaceholder` | The single shared labeled placeholder a declared IMAGERY SLOT renders when no asset has been supplied yet — a chassis never silently ships an empty slot, and never renders an accidental image. Composed by `Hero`/`Feature`/`Gallery` (sections tier) whenever their `imagerySlot` data flag is `true` and the corresponding asset is absent. | `import { MediaPlaceholder } from "@working-theory/ui/blocks"` |
176
179
  | `MemberList` | Settings "People" member rows — reimplements the ploy.ai Team Members list capture (avatar + email + "(you)" + role Badge). Purely presentational and props-driven: no session/permission logic; the caller supplies the member list, role labels, and an optional per-row actions slot (e.g. a "Remove" menu). | `import { MemberList } from "@working-theory/ui/blocks"` |
177
180
  | `MetricComparisonTable` | A numeric comparison table over the Table primitive. The operator-dashboard comparison view's core surface: one column per waitlist (plus a "total" column) and one row per metric (raw, qualified, qualification rate, one row per tier). | `import { MetricComparisonTable } from "@working-theory/ui/blocks"` |
178
181
  | `NavActionList` | CTA action cluster block (one shared button chrome; variant = paint, size = box). Extracts the action-cluster variant routing that was duplicated verbatim between Nav (ActionCluster, Nav.tsx:240–342) and NavMobileMenu (action map, NavMobileMenu.tsx:344–435). This block is the vehicle for the later Nav + NavMobileMenu refactors — both will compose it. | `import { NavActionList } from "@working-theory/ui/blocks"` |
@@ -193,18 +196,24 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
193
196
  | `PromptCaptureCard` | The chat-style prompt card: a multiline field carrying the authored placeholder, with a circular arrow submit control pinned bottom-right that stays muted (disabled) until the visitor has typed something. Enter submits; Shift+Enter inserts a newline — the chat-composer convention. | `import { PromptCaptureCard } from "@working-theory/ui/blocks"` |
194
197
  | `Prose` | The single typography wrapper both the docs route and blog render their compiled-MDX body inside. It styles every RAW markdown element a compiled document emits — h2–h6, p, ul/ol/li, blockquote, table parts, hr, a, inline code, pre, img, kbd, strong/em — via `@working-theory/theme` semantic tokens and type roles. No raw color or spacing literals; dark mode rides the semantic tokens (no `dark:` variants needed). | `import { Prose } from "@working-theory/ui/blocks"` |
195
198
  | `RecommendationCard` | "For you" proactive-recommendation tile: status + title + description + "Why this?" disclosure + context chip + action + dismiss (ploy-capture recomposition). The "For you" proactive-recommendation card captured off ploy.ai's Overview page (behavior spec: -2, — `overview-page` "For you" feed / rebuild-triage row "For you" recommendation card"). | `import { RecommendationCard } from "@working-theory/ui/blocks"` |
199
+ | `ResponsiveSheet` | ONE component, TWO forms, ONE open pair. Renders its `children` as a Radix Dialog at `md` (768px, --breakpoint-md) and up, and as a full-screen Radix Sheet below it — never both at once. | `import { ResponsiveSheet } from "@working-theory/ui/blocks"` |
200
+ | `RowGroup` | The bordered box that groups ListRows with hairline dividers. The reference is `apps-paid/admin/design/demos/first-steps/demo.html`'s `.box` (`background:var(--bg);border:1px solid var(--border); border-radius:6px;overflow:hidden`) with `.row{border-top:…}` and `.row:first-child{border-top:0}` — i.e. hairlines BETWEEN rows, none above the first. | `import { RowGroup } from "@working-theory/ui/blocks"` |
196
201
  | `SchedulerEmbed` | A declarative third-party booking iframe. Renders a full-width, border-0, lazy-loaded <iframe> at `src` with a graceful loading skeleton (shown until the iframe's own `load` event fires) and a timeout-gated plain-link fallback beneath it — for environments that block third-party frames (ad blockers, a strict CSP `frame-src`) where the iframe may load blank or never load at all. | `import { SchedulerEmbed } from "@working-theory/ui/blocks"` · `packages/ui/src/blocks/SchedulerEmbed.tsx` |
202
+ | `SectionActionRow` | The bottom-anchored, centered action row a section places BENEATH its own content, at the 44×44px target-size floor. It is the block form of the placement established for `stair-steps`: a bounded 1-2 action list rendered after the section body rather than inside the section header. | `import { SectionActionRow } from "@working-theory/ui/blocks"` |
197
203
  | `SectionHeader` | Alignment-parameterized section header (eyebrow/subtitle/description/supportLink). The same component serves two ranks (headingLevel="section" \| "subsection") and three alignments (align="start" \| "center" \| "end"). It knows nothing about columns/splits/carousels — it is width-flexible and fills whatever container the section places it in. | `import { SectionHeader } from "@working-theory/ui/blocks"` |
198
204
  | `ShareBar` | The reader-facing counterpart to DocsActionBar. Both surfaces share the same SplitButton *affordance + slot* (outline variant, right-justified on the post title row) but fill it with different actions: DocsActionBar carries the AI-ingestion set (Copy page + Open in …), ShareBar carries the social-share set (X · LinkedIn · Bluesky · Copy link). One primitive, two configs. | `import { ShareBar } from "@working-theory/ui/blocks"` |
199
205
  | `SinglePriceCard` | 2-column split card for the Pricing 'single-price-with-details' variant. *(weak)* | `import { SinglePriceCard } from "@working-theory/ui/blocks"` |
200
206
  | `SocialIcons` | Horizontal row of social icon links. Renders brand icons (via Icon primitive + simple-icons paths) inside accessible Link primitives. Each platform link gets an aria-label. Shared by company-mission, simple-centered, and newsletter-below footer variants, and composed by TeamMemberCard for member social links. *(weak)* | `import { SocialIcons } from "@working-theory/ui/blocks"` |
207
+ | `SortableList` | The one @dnd-kit/sortable reorder block: mouse, touch and keyboard drag behind a six-dot grip, announced, controlled. `@dnd-kit/sortable` wrapped ONCE behind our six-dot grip. The ONLY file in the monorepo that imports `@dnd-kit/*` — enforced by tests/architecture/no-dnd-kit-outside-sortablelist.test.ts (SD-2). | `import { SortableList } from "@working-theory/ui/blocks"` |
201
208
  | `StatTicker` | Static, horizontally overflow-scrollable workspace stat strip with an optional trailing CTA; no auto-scroll marquee (ploy-capture recomposition). The Overview stat strip captured off ploy.ai (behavior spec: -2, — `overview-ticker` "-viewport"/"-track"/"-segment"/"-value"/"-cta"). | `import { StatTicker } from "@working-theory/ui/blocks"` |
202
209
  | `Steps` | The numbered install/guide walkthrough: an ordered list of `Step` items, each a zero-padded number marker + an optional per-step heading + body, joined top-to-bottom by a connecting vertical rule. The classic docs "follow these steps" surface, rendered from `@working-theory/ui` so docs pages stay design-system citizens. | `import { Steps } from "@working-theory/ui/blocks"` |
203
210
  | `StreamingMessage` | One chat turn's message rendering, per the ploy.ai capture contract. | `import { StreamingMessage } from "@working-theory/ui/blocks"` |
211
+ | `SubscriptionStatusBanner` | The account page's update-your-card banner: tells a subscriber whose payment needs attention, with one call to action to open the Stripe billing portal and fix it. Presentational only — the block owns no app wiring (the FormCard `ReactNode` action-slot convention); the page decides whether to render it and supplies pre-translated copy. | `import { SubscriptionStatusBanner } from "@working-theory/ui/blocks"` |
204
212
  | `SuggestionChipCarousel` | A 3-6 icon-chip row with exactly one chip expanded (muted category + bold prompt), auto-rotating motion-safe with hover/focus pause; a click hands the chip's prompt to the host. A row of 3-6 icon chips in which exactly ONE is expanded at a time. | `import { SuggestionChipCarousel } from "@working-theory/ui/blocks"` |
205
213
  | `TableOfContents` | The shared "On this page" rail consumed by the docs shell and the blog post layout (do not couple it to docs-only context). Renders a flat list of heading anchors and scroll-spies the heading currently in view, highlighting the matching item. | `import { TableOfContents } from "@working-theory/ui/blocks"` |
206
214
  | `TeamMemberCard` | Renders one team member in either of two layout modes: 'inline' — small circular avatar left + name/role right (with-small-images variant) 'stacked' — large circular avatar top + name/role/social-links below (with-large-images variant). | `import { TeamMemberCard } from "@working-theory/ui/blocks"` |
207
215
  | `TestimonialAside` | For ContactForm 'with-testimonial' variant. Rating (optional 0–5): rendered via Rating primitive with accessible label. avatar (optional): composed via Avatar + AvatarFallback + AvatarImage (initials fallback). | `import { TestimonialAside } from "@working-theory/ui/blocks"` |
216
+ | `UpgradeDialog` | A plan-comparison dialog a consumer opens when an entitlement gates an action — controlled by default (open/onOpenChange are the primary interface; `trigger` is optional, since both known consumers open it from a menu row rather than an inline trigger). | `import { UpgradeDialog } from "@working-theory/ui/blocks"` |
208
217
  | `UserBlock` | Position-agnostic identity/account trigger block: avatar + primary label + optional secondary line, with density/affordance/collapsed/truncation variants. A single focusable button-rooted element that forwardRef's + spreads props so DropdownMenuTrigger asChild (AccountMenu) can clone it as its trigger anchor. | `import { UserBlock } from "@working-theory/ui/blocks"` |
209
218
  | `VerifyCodeField` | Presentational code-entry unit for StepFlow's `verify` step type: a status line ("We sent a code to…"), a 6-digit numeric code Field, and a resend link. Composes existing Field/Input/Button/Text primitives — no bespoke markup, no new primitive. | `import { VerifyCodeField } from "@working-theory/ui/blocks"` |
210
219
  | `VersionHistory` | Day-grouped auto-saved version list with a "Current" marker — reimplements the ploy.ai artifact-editor Versions capture ("10 auto-saved versions", grouped by day, `Current` on one entry). Purely presentational and props-driven: renders the grouped list only. Designed to be embedded inside a popover by a higher tier — this block does NOT render the popover/trigger itself. | `import { VersionHistory } from "@working-theory/ui/blocks"` |
@@ -215,7 +224,7 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
215
224
 
216
225
  ---
217
226
 
218
- ## Chrome (17)
227
+ ## Chrome (18)
219
228
 
220
229
  Persistent framing UI that wraps page content — navigation, footers, sidebars, banners. Not MDX-driven.
221
230
 
@@ -228,6 +237,7 @@ Import: `import { X } from "@working-theory/ui/chrome";` (or from the `@working-
228
237
  | `Banner` | Site-wide announcement and consent banner chrome. *(weak)* | `import { Banner } from "@working-theory/ui/chrome"` |
229
238
  | `BannerConsentIsland` | Accept / Reject action cluster for the consent Banner. Isolated to "use client" so Banner stays RSC. The onAccept / onReject callbacks must be Server Actions when used inside an RSC tree; they may be regular functions in client contexts (Storybook, testing). *(weak)* | internal — compose from `packages/ui/src/chrome/BannerConsentIsland.tsx` |
230
239
  | `BannerDismissIsland` | Isolated to "use client" so Banner (Server Component) stays RSC. Mirrors the Nav / NavMobileMenu split: interactivity is contained here. Rendering: wraps its children in a relative-positioned div and absolutely places the × dismiss button at the end (RTL-safe: `end-4`) of the bar. *(weak)* | internal — compose from `packages/ui/src/chrome/BannerDismissIsland.tsx` |
240
+ | `BottomTabBar` | Fixed, safe-area-aware phone bottom tab bar chrome. Server Component (RSC) — presentational, no interaction state of its own; the leading avatar/account trigger is supplied as a node by the consumer (AppFrame composes AccountMenu around it). *(weak)* | `import { BottomTabBar } from "@working-theory/ui/chrome"` |
231
241
  | `FacetNav` | Chrome-tier faceted secondary navigation (ploy-capture recomposition). One reusable component servicing THREE captured consumers: Settings' grouped Org/Workspace nav, Assets' All + tag-facet counts, and Docs' All + folder tree + tag facets. | `import { FacetNav } from "@working-theory/ui/chrome"` |
232
242
  | `FloatingCta` | Persistent fixed corner action pill. *(weak)* | `import { FloatingCta } from "@working-theory/ui/chrome"` |
233
243
  | `Footer` | Site footer server component with secondary nav links. Label resolution order for navItems labels. Resolved labels are passed into FooterNav as pre-resolved strings — no duplicated resolution logic across variants. | `import { Footer } from "@working-theory/ui/chrome"` |
@@ -350,7 +360,7 @@ Import: `import { X } from "@working-theory/ui/panels";` (or from the `@working-
350
360
 
351
361
  ---
352
362
 
353
- ## Shells (3)
363
+ ## Shells (5)
354
364
 
355
365
  Route-level slot-frames — the outermost layout a page is placed into. They supply geometry and slots (nav / sidebar / main / footer), not content: you inject the real Nav, Footer and sidebar. A different package from every tier above: `@working-theory/templates`.
356
366
 
@@ -358,7 +368,9 @@ Import: `import { X } from "@working-theory/templates/shells";` (or from the `@w
358
368
 
359
369
  | Component | What it does | How to use it |
360
370
  | --- | --- | --- |
371
+ | `AccountShell` | Route-level layout for a signed-in person's own Account area. Composed exactly as the deleted shell was — a thin AppShell + SidebarNav. Auth is not wired here; the consuming layout guards the route. | `import { AccountShell } from "@working-theory/templates/shells"` |
361
372
  | `AppShell` | Shared authed-app base frame. Layout ownership: AppShell is the SINGLE owner of the authed sidebar frame layout. The `sidebar` prop is CONTENT (e.g. a <SidebarNav>), never a layout component. Do NOT pass SidebarShell or FixedPanel as the sidebar — that nests two incompatible layout systems (a fixed-width shell vs a variable-width panel). | `import { AppShell } from "@working-theory/templates/shells"` |
373
+ | `AuthShell` | The frame the five auth pages (/sign-in, /sign-up, /forgot-password, /reset-password, /verify-email) share: a brand mark top-start, one centered content column, and a slim footer. No nav — an auth page has nothing to navigate to, and the marketing Nav is exactly what this shell exists to replace on those routes. | `import { AuthShell } from "@working-theory/templates/shells"` |
362
374
  | `DeckHeroObserver` | Internal client island for MarketingShell's `deckHero` option. | internal — compose from `packages/templates/src/shells/DeckHeroObserver.tsx` |
363
375
  | `MarketingShell` | Public-facing marketing site **slot-frame**. Server Component (RSC default). A shell is a slot-defining frame: it carves out *where chrome goes* (top + bottom) and *where sections go* (the body), owning **geometry only**. Chrome (Nav/Footer) and sections **fill** the slots — nothing is baked into the component. See (first instance). *(weak)* | `import { MarketingShell } from "@working-theory/templates/shells"` |
364
376
 
@@ -517,7 +529,7 @@ Import: `import { X } from "@working-theory/ui-commerce";` (or from the `@workin
517
529
 
518
530
  ## Weak descriptions
519
531
 
520
- These 29 components have a description that is accurate but does not do the job well: too short to match against, or long but spent on implementation. A capability search reaches them only if the searcher's word happens to be in it, so they are the most likely place for this catalog to repeat the failure it was built to fix. Read the source before concluding the capability is absent — and if you own one of these components, its header is the fix.
532
+ These 30 components have a description that is accurate but does not do the job well: too short to match against, or long but spent on implementation. A capability search reaches them only if the searcher's word happens to be in it, so they are the most likely place for this catalog to repeat the failure it was built to fix. Read the source before concluding the capability is absent — and if you own one of these components, its header is the fix.
521
533
 
522
534
  | Component | Tier | What the source gives us |
523
535
  | --- | --- | --- |
@@ -530,6 +542,7 @@ These 29 components have a description that is accurate but does not do the job
530
542
  | `Banner` | chrome | Site-wide announcement and consent banner chrome. |
531
543
  | `BannerConsentIsland` | chrome | Accept / Reject action cluster for the consent Banner. Isolated to "use client" so Banner stays RSC. The onAccept / onReject callbacks must be Server Actions when used inside an RSC tree; they may be regular functions in client contexts (Storybook, testing). |
532
544
  | `BannerDismissIsland` | chrome | Isolated to "use client" so Banner (Server Component) stays RSC. Mirrors the Nav / NavMobileMenu split: interactivity is contained here. Rendering: wraps its children in a relative-positioned div and absolutely places the × dismiss button at the end (RTL-safe: `end-4`) of the bar. |
545
+ | `BottomTabBar` | chrome | Fixed, safe-area-aware phone bottom tab bar chrome. Server Component (RSC) — presentational, no interaction state of its own; the leading avatar/account trigger is supplied as a node by the consumer (AppFrame composes AccountMenu around it). |
533
546
  | `FloatingCta` | chrome | Persistent fixed corner action pill. |
534
547
  | `NavMobileMenu` | chrome | Client subcomponent for mobile hamburger state. Isolates "use client" to this narrow component so Nav (Server Component) stays clean. Renders a hamburger button + collapsible link list. Closes the menu automatically when the pathname changes (route navigation). |
535
548
  | `SidebarNav` | chrome | Application sidebar rail (nav + teams + user) for dashboard shells. Server Component (RSC). Renders the vertical sidebar rail for application-shell layouts. Used as the `sidebar` prop of AdminShell. Wrap in a FixedPanel (panels tier) to get the expanded ↔ rail ↔ hidden state machinery. Independently reusable in future shells (settings, analytics, etc.). |
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The verdict on a scaffold `apps/api` boot attempt (#5824).
3
+ *
4
+ * `scripts/verify-scaffold.mjs` builds and boots the scaffold's `apps/api`
5
+ * because the #5799 measurement run found a keyless scaffold's api dying at
6
+ * boot on `AILK slice module './lib/stripe.js' could not be resolved at
7
+ * runtime` — a base-tree regression (#5823) that no gate in this repo could
8
+ * see, because the driver built `apps/web` and `database` and never touched
9
+ * `apps/api`.
10
+ *
11
+ * The verdict lives here, apart from the driver, for one reason: it is the
12
+ * assertion the whole gate exists for, and proving it still catches the #5823
13
+ * class must not cost a templates build, an install and a `next build`. It is
14
+ * a pure function of what the child process printed plus how it ended, so
15
+ * `__tests__/verify-scaffold-api-boot.test.ts` drives it directly. The driver
16
+ * loads it from `dist/` exactly as it already loads `paid-paths.js`.
17
+ */
18
+ /** The line `apps/api` logs once `server.listen()` has resolved. */
19
+ export declare const API_LISTENING_MARKER = "AI Launch Kit API listening";
20
+ /** What the driver observed of the boot attempt. */
21
+ export interface ApiBootObservation {
22
+ /** The child's stdout and stderr, interleaved as captured. */
23
+ output: string;
24
+ /** Whether the child exited before the driver stopped waiting. */
25
+ exited: boolean;
26
+ /** Its exit code, when it exited. */
27
+ exitCode: number | null;
28
+ /** Whether the wait ended on the driver's own deadline instead. */
29
+ timedOut: boolean;
30
+ /** That deadline, in milliseconds, for the failure message. */
31
+ timeoutMs: number;
32
+ }
33
+ export interface ApiBootVerdict {
34
+ ok: boolean;
35
+ reason: string;
36
+ }
37
+ /**
38
+ * Decide whether a scaffold `apps/api` booted.
39
+ *
40
+ * An unresolved slice module is checked BEFORE the listening line: that
41
+ * ordering is the #5823 assertion itself — a `could not be resolved` line is a
42
+ * failure that names the module, even in the hypothetical transcript that also
43
+ * reached a listen. Every other way of not reaching the listening line — an
44
+ * early exit, or a deadline with neither signal — fails with what was seen.
45
+ */
46
+ export declare function assessApiBoot(observation: ApiBootObservation): ApiBootVerdict;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The verdict on a scaffold `apps/api` boot attempt (#5824).
3
+ *
4
+ * `scripts/verify-scaffold.mjs` builds and boots the scaffold's `apps/api`
5
+ * because the #5799 measurement run found a keyless scaffold's api dying at
6
+ * boot on `AILK slice module './lib/stripe.js' could not be resolved at
7
+ * runtime` — a base-tree regression (#5823) that no gate in this repo could
8
+ * see, because the driver built `apps/web` and `database` and never touched
9
+ * `apps/api`.
10
+ *
11
+ * The verdict lives here, apart from the driver, for one reason: it is the
12
+ * assertion the whole gate exists for, and proving it still catches the #5823
13
+ * class must not cost a templates build, an install and a `next build`. It is
14
+ * a pure function of what the child process printed plus how it ended, so
15
+ * `__tests__/verify-scaffold-api-boot.test.ts` drives it directly. The driver
16
+ * loads it from `dist/` exactly as it already loads `paid-paths.js`.
17
+ */
18
+ /** The line `apps/api` logs once `server.listen()` has resolved. */
19
+ export const API_LISTENING_MARKER = 'AI Launch Kit API listening';
20
+ /**
21
+ * A slice module the api could not resolve at runtime — the #5823 shape. The
22
+ * module specifier is captured so the failure names it rather than leaving the
23
+ * next reader to re-derive it from a bare non-zero exit.
24
+ */
25
+ const UNRESOLVED_SLICE_MODULE = /AILK slice module ['"`]?([^'"`\s]+?)['"`]? could not be resolved/;
26
+ /**
27
+ * Decide whether a scaffold `apps/api` booted.
28
+ *
29
+ * An unresolved slice module is checked BEFORE the listening line: that
30
+ * ordering is the #5823 assertion itself — a `could not be resolved` line is a
31
+ * failure that names the module, even in the hypothetical transcript that also
32
+ * reached a listen. Every other way of not reaching the listening line — an
33
+ * early exit, or a deadline with neither signal — fails with what was seen.
34
+ */
35
+ export function assessApiBoot(observation) {
36
+ const { output, exited, exitCode, timedOut, timeoutMs } = observation;
37
+ const unresolved = UNRESOLVED_SLICE_MODULE.exec(output);
38
+ if (unresolved) {
39
+ return {
40
+ ok: false,
41
+ reason: `the scaffold's apps/api could not resolve the slice module ${unresolved[1]} ` +
42
+ `at boot — the #5823 class, invisible to every other gate in this repo` +
43
+ (exited ? ` (exit ${String(exitCode)})` : ''),
44
+ };
45
+ }
46
+ if (output.includes(API_LISTENING_MARKER)) {
47
+ return {
48
+ ok: true,
49
+ reason: `the scaffold's apps/api logged \`${API_LISTENING_MARKER}\``,
50
+ };
51
+ }
52
+ if (exited) {
53
+ return {
54
+ ok: false,
55
+ reason: `the scaffold's apps/api exited with code ${String(exitCode)} before logging ` +
56
+ `\`${API_LISTENING_MARKER}\``,
57
+ };
58
+ }
59
+ if (timedOut) {
60
+ return {
61
+ ok: false,
62
+ reason: `the scaffold's apps/api neither logged \`${API_LISTENING_MARKER}\` nor exited ` +
63
+ `within ${timeoutMs}ms`,
64
+ };
65
+ }
66
+ return {
67
+ ok: false,
68
+ reason: `the scaffold's apps/api boot ended with no observable outcome`,
69
+ };
70
+ }
@@ -0,0 +1,29 @@
1
+ export declare const SOURCE_EXTS: string[];
2
+ /** Every source file under the given repo-relative roots, as "/"-joined paths. */
3
+ export declare function listFilesUnder(repoRoot: string, roots: string[]): Set<string>;
4
+ /** The relative import specifiers a source file names (static imports + re-exports). */
5
+ export declare function relativeImportsOf(absPath: string): string[];
6
+ /**
7
+ * Resolve a relative specifier against the candidate set. TypeScript ESM writes
8
+ * `./x.js` for `./x.ts`, so the `.js` → `.ts`/`.tsx` rewrite is the common case.
9
+ */
10
+ export declare function resolveRelativeImport(fromFile: string, spec: string, candidates: Set<string>): string | undefined;
11
+ /**
12
+ * The companion closure of `seeds` under `roots`: the seeds themselves plus
13
+ * every source file under `roots` that reaches one transitively through a
14
+ * relative import, to a fixed point.
15
+ *
16
+ * This is the shape both consumers need, and the reason the walk is shared
17
+ * rather than copied. `paidModuleTemplateExclusions()` seeds it with every
18
+ * paid module's `ownedFiles` to get the files a keyless starter must not
19
+ * carry; `tools/lint-slice-registration.ts` seeds it with the same set to
20
+ * derive, by subtraction, the files a scaffold ships UNCONDITIONALLY.
21
+ *
22
+ * Never a filename-stem rule (#5182 D7), which misses eleven files and
23
+ * over-marks three.
24
+ *
25
+ * @param repoRoot absolute path to the tree being walked.
26
+ * @param roots repo-relative roots the walk is confined to.
27
+ * @param seeds repo-relative, "/"-separated files the closure starts from.
28
+ */
29
+ export declare function importClosure(repoRoot: string, roots: readonly string[], seeds: Iterable<string>): Set<string>;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * The relative-import graph walker over a repo-relative source tree.
3
+ *
4
+ * Extracted from `module-architecture.ts` (#5701) so the two consumers that
5
+ * need this resolution share ONE implementation rather than two that can
6
+ * disagree:
7
+ *
8
+ * - `paidModuleTemplateExclusions()` (module-architecture.ts) walks it to take
9
+ * the companion closure of every paid module's `ownedFiles` — the files that
10
+ * would ship dangling into a keyless starter.
11
+ * - `tools/lint-slice-registration.ts` walks it to close over the specifiers an
12
+ * `isModuleEnabled("X")` branch in `apps/api/src/server.ts` loads, and to
13
+ * check every reached file against what module X's slice delivers.
14
+ *
15
+ * Both questions are "which files does this one reach through relative
16
+ * imports", and the answer must be the same function in both mouths: a lint
17
+ * that resolved specifiers differently from the exclusion set it checks
18
+ * against would pass on a tree the scaffold cannot boot.
19
+ *
20
+ * The resolution is deliberately TEXTUAL — a regex over import/export
21
+ * specifiers plus a candidate-set lookup, no type-checker and no bundler — so
22
+ * it runs inside `pnpm preflight`'s memory budget.
23
+ */
24
+ import { readFileSync, readdirSync } from "node:fs";
25
+ import { dirname, join, sep } from "node:path";
26
+ export const SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
27
+ /** Every source file under the given repo-relative roots, as "/"-joined paths. */
28
+ export function listFilesUnder(repoRoot, roots) {
29
+ const out = new Set();
30
+ const skip = new Set(["node_modules", ".next", "dist", ".turbo", ".source"]);
31
+ const stack = roots.map((r) => r);
32
+ while (stack.length > 0) {
33
+ const rel = stack.pop();
34
+ const abs = join(repoRoot, rel);
35
+ let entries;
36
+ try {
37
+ entries = readdirSync(abs, { withFileTypes: true });
38
+ }
39
+ catch {
40
+ continue;
41
+ }
42
+ for (const entry of entries) {
43
+ if (skip.has(entry.name))
44
+ continue;
45
+ const childRel = `${rel}/${entry.name}`;
46
+ if (entry.isDirectory())
47
+ stack.push(childRel);
48
+ else if (SOURCE_EXTS.some((e) => entry.name.endsWith(e)))
49
+ out.add(childRel);
50
+ }
51
+ }
52
+ return out;
53
+ }
54
+ /** The relative import specifiers a source file names (static imports + re-exports). */
55
+ export function relativeImportsOf(absPath) {
56
+ let src;
57
+ try {
58
+ src = readFileSync(absPath, "utf8");
59
+ }
60
+ catch {
61
+ return [];
62
+ }
63
+ const out = [];
64
+ for (const m of src.matchAll(/(?:^|\n)\s*(?:import|export)[\s\S]{0,400}?from\s*["'](\.[^"']*)["']/g))
65
+ out.push(m[1]);
66
+ for (const m of src.matchAll(/\bimport\s*\(\s*["'](\.[^"']*)["']\s*\)/g))
67
+ out.push(m[1]);
68
+ return out;
69
+ }
70
+ /**
71
+ * Resolve a relative specifier against the candidate set. TypeScript ESM writes
72
+ * `./x.js` for `./x.ts`, so the `.js` → `.ts`/`.tsx` rewrite is the common case.
73
+ */
74
+ export function resolveRelativeImport(fromFile, spec, candidates) {
75
+ const base = posixJoin(dirname(fromFile.split("/").join(sep)), spec);
76
+ const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
77
+ for (const cand of [
78
+ base,
79
+ ...SOURCE_EXTS.map((e) => stem + e),
80
+ ...SOURCE_EXTS.map((e) => `${stem}/index${e}`),
81
+ ])
82
+ if (candidates.has(cand))
83
+ return cand;
84
+ return undefined;
85
+ }
86
+ /** Join and normalize a "/"-separated path, resolving `.` and `..` segments. */
87
+ function posixJoin(dir, spec) {
88
+ const parts = `${dir.split(sep).join("/")}/${spec}`.split("/");
89
+ const out = [];
90
+ for (const part of parts) {
91
+ if (part === "" || part === ".")
92
+ continue;
93
+ if (part === "..")
94
+ out.pop();
95
+ else
96
+ out.push(part);
97
+ }
98
+ return out.join("/");
99
+ }
100
+ /**
101
+ * The companion closure of `seeds` under `roots`: the seeds themselves plus
102
+ * every source file under `roots` that reaches one transitively through a
103
+ * relative import, to a fixed point.
104
+ *
105
+ * This is the shape both consumers need, and the reason the walk is shared
106
+ * rather than copied. `paidModuleTemplateExclusions()` seeds it with every
107
+ * paid module's `ownedFiles` to get the files a keyless starter must not
108
+ * carry; `tools/lint-slice-registration.ts` seeds it with the same set to
109
+ * derive, by subtraction, the files a scaffold ships UNCONDITIONALLY.
110
+ *
111
+ * Never a filename-stem rule (#5182 D7), which misses eleven files and
112
+ * over-marks three.
113
+ *
114
+ * @param repoRoot absolute path to the tree being walked.
115
+ * @param roots repo-relative roots the walk is confined to.
116
+ * @param seeds repo-relative, "/"-separated files the closure starts from.
117
+ */
118
+ export function importClosure(repoRoot, roots, seeds) {
119
+ const closed = new Set(seeds);
120
+ const candidates = listFilesUnder(repoRoot, [...roots]);
121
+ for (;;) {
122
+ let grew = false;
123
+ for (const file of candidates) {
124
+ if (closed.has(file))
125
+ continue;
126
+ const imports = relativeImportsOf(join(repoRoot, file));
127
+ for (const spec of imports) {
128
+ const target = resolveRelativeImport(file, spec, candidates);
129
+ if (target !== undefined && closed.has(target)) {
130
+ closed.add(file);
131
+ grew = true;
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ if (!grew)
137
+ break;
138
+ }
139
+ return closed;
140
+ }
@@ -397,4 +397,5 @@ export declare function normalizeModules(modules: string[]): ModuleName[];
397
397
  * whole invariant.
398
398
  */
399
399
  export declare function coOwnedModuleDestinations(): ReadonlySet<string>;
400
+ export declare const EXCLUDE_TEMPLATE_DIRS: readonly string[];
400
401
  export declare function paidModuleTemplateExclusions(snapshotRoots: readonly string[], repoRoot: string): string[];