create-ailk 0.20.0 → 0.22.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 (222) hide show
  1. package/README.md +21 -1
  2. package/component-catalog.json +220 -8
  3. package/component-catalog.md +21 -9
  4. package/dist/add-module.js +9 -5
  5. package/dist/apply-design.d.ts +9 -0
  6. package/dist/apply-design.js +78 -31
  7. package/dist/derive-architecture.d.ts +8 -1
  8. package/dist/derive-architecture.js +38 -3
  9. package/dist/design-home.d.ts +54 -11
  10. package/dist/design-home.js +160 -22
  11. package/dist/design-package.js +7 -2
  12. package/dist/lib/design-checks.d.ts +6 -1
  13. package/dist/lib/design-checks.js +23 -2
  14. package/dist/lib/env-catalog.js +5 -1
  15. package/dist/module-architecture.js +4 -2
  16. package/dist/programmatic.js +63 -25
  17. package/dist/site-chrome.d.ts +8 -0
  18. package/dist/site-chrome.js +17 -1
  19. package/dist/site-roster.d.ts +4 -2
  20. package/dist/site-roster.js +7 -5
  21. package/dist/spoke-checkout.d.ts +39 -18
  22. package/dist/spoke-checkout.js +177 -236
  23. package/dist/starter-grid.js +16 -0
  24. package/dist/surfaces.js +30 -14
  25. package/dist/sync-routes.d.ts +28 -0
  26. package/dist/sync-routes.js +124 -1
  27. package/dist/template-record.d.ts +4 -3
  28. package/dist/template-record.js +6 -3
  29. package/dist/topology.js +25 -1
  30. package/dist/update.d.ts +6 -1
  31. package/dist/update.js +125 -18
  32. package/package.json +3 -3
  33. package/site-templates/_base/ailk-template.json +2 -2
  34. package/site-templates/_base/content/en/WorkingAgreementPage/working-agreement.mdx +78 -0
  35. package/site-templates/designs/minimalism/DESIGN.md +1 -1
  36. package/site-templates/designs/minimalism/contact/local-business-service.mdx +23 -0
  37. package/site-templates/designs/minimalism/contact/organization-product.mdx +23 -0
  38. package/site-templates/designs/minimalism/contact/organization-service.mdx +23 -0
  39. package/site-templates/designs/minimalism/contact/person-service.mdx +23 -0
  40. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +18 -1
  41. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +11 -0
  42. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +18 -1
  43. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +34 -23
  44. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +18 -1
  45. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +33 -23
  46. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +18 -1
  47. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +33 -23
  48. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +18 -1
  49. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +31 -21
  50. package/templates/README.md +26 -25
  51. package/templates/apps/api/CLAUDE.md +20 -7
  52. package/templates/apps/api/jest.config.cjs +1 -0
  53. package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
  54. package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
  55. package/templates/apps/api/src/lib/consent-store.ts +28 -0
  56. package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
  57. package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
  58. package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
  59. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
  60. package/templates/apps/api/src/middleware/auth.ts +13 -6
  61. package/templates/apps/api/src/openapi/spec.ts +30 -3
  62. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
  63. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
  64. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
  65. package/templates/apps/api/src/routes/consent/account.ts +266 -0
  66. package/templates/apps/api/src/routes/consent/index.ts +15 -7
  67. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
  68. package/templates/apps/api/src/routes/content/get-page.ts +4 -0
  69. package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
  70. package/templates/apps/api/src/routes/leads/index.ts +161 -7
  71. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +26 -0
  72. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
  73. package/templates/apps/api/src/routes/webhooks/stripe.ts +154 -19
  74. package/templates/apps/api/src/server.ts +28 -0
  75. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1038 -118
  76. package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
  77. package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
  78. package/templates/apps/api/src/services/consent.ts +95 -0
  79. package/templates/apps/api/src/services/lead-routing.ts +337 -8
  80. package/templates/apps/api/src/services/magnet-completion.ts +242 -0
  81. package/templates/apps/api/src/services/person.ts +40 -8
  82. package/templates/apps/api/src/services/plan-offerings.ts +59 -0
  83. package/templates/apps/api/src/services/project-listings.ts +6 -1
  84. package/templates/apps/api/src/services/public-site-resolver.ts +8 -1
  85. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +69 -61
  86. package/templates/apps/mcp/jest.config.cjs +1 -0
  87. package/templates/apps/web/.env.example +14 -0
  88. package/templates/apps/web/CLAUDE.md +139 -3
  89. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
  90. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
  91. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
  92. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
  93. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
  94. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
  95. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
  96. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
  97. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
  98. package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
  99. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
  100. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
  101. package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
  102. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
  103. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
  104. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
  105. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
  106. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
  107. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
  108. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
  109. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
  110. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
  111. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
  112. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
  113. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
  114. package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
  115. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
  116. package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
  117. package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
  118. package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
  119. package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
  120. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
  121. package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
  122. package/templates/apps/web/app/[locale]/about/[slug]/page.tsx +46 -0
  123. package/templates/apps/web/app/[locale]/about/__tests__/about-routes.test.tsx +350 -0
  124. package/templates/apps/web/app/[locale]/about/page.tsx +83 -1
  125. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +293 -0
  126. package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +115 -0
  127. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +146 -1
  128. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +310 -0
  129. package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +143 -0
  130. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +350 -0
  131. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +29 -3
  132. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  133. package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
  134. package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
  135. package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
  136. package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
  137. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
  138. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +24 -1
  139. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
  140. package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-magnet.test.ts +138 -0
  141. package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +17 -0
  142. package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +386 -0
  143. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +504 -24
  144. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +12 -3
  145. package/templates/apps/web/app/[locale]/contact/contact-form-magnet.ts +77 -0
  146. package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +2 -0
  147. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +184 -41
  148. package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
  149. package/templates/apps/web/app/[locale]/contact/page.tsx +120 -16
  150. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
  151. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +1 -1
  152. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
  153. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +106 -1
  154. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
  155. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
  156. package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
  157. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
  158. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  159. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
  160. package/templates/apps/web/app/[locale]/layout.tsx +23 -2
  161. package/templates/apps/web/app/[locale]/page.tsx +13 -1
  162. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  163. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
  164. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
  165. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
  166. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  167. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  168. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
  169. package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
  170. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
  171. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
  172. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
  173. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  174. package/templates/apps/web/app/[locale]/working-agreement/__tests__/page.test.tsx +63 -0
  175. package/templates/apps/web/app/[locale]/working-agreement/page.tsx +41 -0
  176. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
  177. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
  178. package/templates/apps/web/app/api/checkout/[offering]/route.ts +48 -0
  179. package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +120 -0
  180. package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +86 -0
  181. package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
  182. package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
  183. package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +92 -0
  184. package/templates/apps/web/app/api/webhooks/stripe/route.ts +31 -0
  185. package/templates/apps/web/auth.ts +28 -2
  186. package/templates/apps/web/jest.config.cjs +1 -0
  187. package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
  188. package/templates/apps/web/lib/__tests__/checkout.test.ts +243 -0
  189. package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
  190. package/templates/apps/web/lib/__tests__/deck-page.test.ts +249 -0
  191. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -19
  192. package/templates/apps/web/lib/account-invite.ts +37 -0
  193. package/templates/apps/web/lib/checkout-config.ts +27 -0
  194. package/templates/apps/web/lib/checkout.ts +93 -0
  195. package/templates/apps/web/lib/consent-policies.ts +22 -0
  196. package/templates/apps/web/lib/deck-page.ts +192 -0
  197. package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
  198. package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
  199. package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
  200. package/templates/apps/web/lib/sign-up-agreements.ts +447 -0
  201. package/templates/apps/web/lib/site-chrome.ts +2 -0
  202. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  203. package/templates/apps/web/messages/en.json +40 -12
  204. package/templates/apps/web/package.json +1 -0
  205. package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
  206. package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
  207. package/templates/apps/web/src/lib/lead-write.ts +90 -0
  208. package/templates/apps/web/src/lib/rate-limit.ts +79 -0
  209. package/templates/database/CHANGELOG.md +47 -0
  210. package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
  211. package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
  212. package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
  213. package/templates/database/__tests__/seed.test.ts +72 -2
  214. package/templates/database/inbox/schema.prisma +17 -2
  215. package/templates/database/jest.config.cjs +1 -0
  216. package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
  217. package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
  218. package/templates/database/package.json +1 -1
  219. package/templates/database/seed.ts +60 -0
  220. package/templates/tools/jest/pin-timezone.cjs +34 -0
  221. package/versions.json +34 -34
  222. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
package/README.md CHANGED
@@ -250,6 +250,14 @@ hand-authored translations, so `sync-routes` reports a missing namespace
250
250
  rather than attempting to merge JSON keys into it. Adding it is a manual
251
251
  step, same as reading any other reported gap.
252
252
 
253
+ The site's structured-data test (`apps/web/lib/__tests__/structured-data.test.tsx`)
254
+ fails any route that neither renders through `renderPage` nor is listed in its
255
+ `NO_STRUCTURED_DATA_ROUTES`. When `--apply` adds a route the template exempts
256
+ there and the site has edited that test (its bytes differ from the hash in
257
+ `.ailk-template-files.json`), the report prints the exact template entry to
258
+ add for each added route, under `Also required`. The test itself is never
259
+ written; an untouched copy is `create-ailk update`'s to bring forward.
260
+
253
261
  **The manifest.** `ROUTE_SINCE` in `src/surfaces.ts` maps every template route
254
262
  directory (relative to `apps/web/app/[locale]`) to the `@working-theory/*`
255
263
  fixed-group release line that first shipped it — the version a site pins, so
@@ -289,7 +297,7 @@ merge-file` against the recorded version's own copy as the base. A clean
289
297
  merge keeps your edit AND the fix, and is reported as **Merged**. A
290
298
  conflict leaves the file exactly as it was — never written with conflict
291
299
  markers — and is reported under **Needs review** with both versions
292
- named; nothing else in the run is held back by it.
300
+ named.
293
301
  - A recordable file the running version adds that the site lacks is
294
302
  **Added**, unless it sits under a route or directory the site deliberately
295
303
  does not carry (a gated-off surface, a spoke exclusion, or a directory you
@@ -301,6 +309,18 @@ recorded (a module bundle's own file, say) when its bytes differ from the
301
309
  running copy. Nothing the base scaffold did not write is ever touched. No
302
310
  template change at all means `Already up to date` and zero files written.
303
311
 
312
+ **Tests are held back with the file they test.** When a file is left under
313
+ **Needs review** or **Skipped**, every template test file that imports it by a
314
+ relative import (`relay.test.ts` importing `../relay`, say) stays exactly as
315
+ the site has it, and its record entry keeps its old hash and version — so a
316
+ run never leaves a test from the new version exercising code from the old
317
+ one. Each is reported under a **Held back with `<file>`** heading. Reconcile
318
+ the file, then run `update` again to bring its tests forward; every other
319
+ file in the run still updates. A held test-support file (`__tests__/fixtures.ts`,
320
+ say) holds the tests importing it in turn. A file skipped because you deleted
321
+ it keeps its tests held on every run: delete those tests too, or restore the
322
+ file.
323
+
304
324
  **A site scaffolded before this record existed** gets one built on its first
305
325
  `update` run, from the `patternVersion` `.ailk-site-manifest.json` already
306
326
  records — no separate command, no per-site setup. If that version predates
@@ -9,7 +9,7 @@
9
9
  "importPath": "@working-theory/ui/sections",
10
10
  "rootBarrel": "@working-theory/ui",
11
11
  "sourceRoot": "packages/ui/src/sections",
12
- "count": 55
12
+ "count": 56
13
13
  },
14
14
  {
15
15
  "name": "blocks",
@@ -17,7 +17,7 @@
17
17
  "importPath": "@working-theory/ui/blocks",
18
18
  "rootBarrel": "@working-theory/ui",
19
19
  "sourceRoot": "packages/ui/src/blocks",
20
- "count": 101
20
+ "count": 107
21
21
  },
22
22
  {
23
23
  "name": "chrome",
@@ -25,7 +25,7 @@
25
25
  "importPath": "@working-theory/ui/chrome",
26
26
  "rootBarrel": "@working-theory/ui",
27
27
  "sourceRoot": "packages/ui/src/chrome",
28
- "count": 18
28
+ "count": 19
29
29
  },
30
30
  {
31
31
  "name": "primitives",
@@ -89,7 +89,7 @@
89
89
  "importPath": "@working-theory/ui-scheduling",
90
90
  "rootBarrel": "@working-theory/ui-scheduling",
91
91
  "sourceRoot": "packages-paid/ui-scheduling/src/blocks",
92
- "count": 5
92
+ "count": 6
93
93
  },
94
94
  {
95
95
  "name": "signal-collectors-sections",
@@ -485,6 +485,17 @@
485
485
  "descriptionGrade": "strong",
486
486
  "sectionType": "items"
487
487
  },
488
+ {
489
+ "name": "ItemsTileLinkIsland",
490
+ "tier": "sections",
491
+ "sourcePath": "packages/ui/src/sections/ItemsTileLinkIsland.tsx",
492
+ "importPath": "@working-theory/ui/sections",
493
+ "exported": false,
494
+ "description": "Makes a whole linked tile card its link's click target.",
495
+ "descriptionSource": "doc-comment",
496
+ "descriptionGrade": "strong",
497
+ "sectionType": null
498
+ },
488
499
  {
489
500
  "name": "LogoCloud",
490
501
  "tier": "sections",
@@ -826,6 +837,17 @@
826
837
  "descriptionGrade": "strong",
827
838
  "sectionType": null
828
839
  },
840
+ {
841
+ "name": "AuthBackLink",
842
+ "tier": "blocks",
843
+ "sourcePath": "packages/ui/src/blocks/AuthBackLink.tsx",
844
+ "importPath": "@working-theory/ui/blocks",
845
+ "exported": true,
846
+ "description": "The auth shell's top-start \"← Back to <site>\". A hub's sign-in page renders it when the visitor arrived from a registered spoke, so the cross-domain handoff names where they came from and gives them the way back.",
847
+ "descriptionSource": "doc-comment",
848
+ "descriptionGrade": "strong",
849
+ "sectionType": null
850
+ },
829
851
  {
830
852
  "name": "AuthCard",
831
853
  "tier": "blocks",
@@ -892,6 +914,17 @@
892
914
  "descriptionGrade": "strong",
893
915
  "sectionType": null
894
916
  },
917
+ {
918
+ "name": "BlogSidebarBox",
919
+ "tier": "blocks",
920
+ "sourcePath": "packages/ui/src/blocks/BlogSidebarBox.tsx",
921
+ "importPath": "@working-theory/ui/blocks",
922
+ "exported": true,
923
+ "description": "Heading + text + one outlined button, authored on the blog index page's own content. Content is set on the blog index page's own frontmatter, never hard-coded.",
924
+ "descriptionSource": "barrel",
925
+ "descriptionGrade": "strong",
926
+ "sectionType": null
927
+ },
895
928
  {
896
929
  "name": "BrandLogo",
897
930
  "tier": "blocks",
@@ -1453,6 +1486,17 @@
1453
1486
  "descriptionGrade": "strong",
1454
1487
  "sectionType": null
1455
1488
  },
1489
+ {
1490
+ "name": "PostArchiveRow",
1491
+ "tier": "blocks",
1492
+ "sourcePath": "packages/ui/src/blocks/PostArchiveRow.tsx",
1493
+ "importPath": "@working-theory/ui/blocks",
1494
+ "exported": true,
1495
+ "description": "One \"Previous posts\" row: title link + meta, host-placed `Separator` between rows. Draws no divider of its own (border convention — the host places `Separator` between rows, as BlogList already does for PostRow).",
1496
+ "descriptionSource": "barrel",
1497
+ "descriptionGrade": "strong",
1498
+ "sectionType": null
1499
+ },
1456
1500
  {
1457
1501
  "name": "PostCard",
1458
1502
  "tier": "blocks",
@@ -1464,6 +1508,17 @@
1464
1508
  "descriptionGrade": "weak",
1465
1509
  "sectionType": null
1466
1510
  },
1511
+ {
1512
+ "name": "PostMetaLine",
1513
+ "tier": "blocks",
1514
+ "sourcePath": "packages/ui/src/blocks/PostMetaLine.tsx",
1515
+ "importPath": "@working-theory/ui/blocks",
1516
+ "exported": true,
1517
+ "description": "`<tag> · <date>` meta line, either half optional (C4). Shows the tag alone, the date alone, or both joined by a middot; renders nothing with neither.",
1518
+ "descriptionSource": "barrel",
1519
+ "descriptionGrade": "strong",
1520
+ "sectionType": null
1521
+ },
1467
1522
  {
1468
1523
  "name": "PostRow",
1469
1524
  "tier": "blocks",
@@ -1475,6 +1530,17 @@
1475
1530
  "descriptionGrade": "strong",
1476
1531
  "sectionType": null
1477
1532
  },
1533
+ {
1534
+ "name": "PostTeaser",
1535
+ "tier": "blocks",
1536
+ "sourcePath": "packages/ui/src/blocks/PostTeaser.tsx",
1537
+ "importPath": "@working-theory/ui/blocks",
1538
+ "exported": true,
1539
+ "description": "The featured post (`size=\"lg\"`) and \"Latest posts\" grid card (`size=\"md\"`); no cover renders no placeholder. Composes MediaFigure + PostMetaLine + primitives. No author byline (C7). RSC: no hooks or browser APIs.",
1540
+ "descriptionSource": "barrel",
1541
+ "descriptionGrade": "weak",
1542
+ "sectionType": null
1543
+ },
1478
1544
  {
1479
1545
  "name": "PricingExtraTierRow",
1480
1546
  "tier": "blocks",
@@ -1750,6 +1816,17 @@
1750
1816
  "descriptionGrade": "strong",
1751
1817
  "sectionType": null
1752
1818
  },
1819
+ {
1820
+ "name": "TopicChips",
1821
+ "tier": "blocks",
1822
+ "sourcePath": "packages/ui/src/blocks/TopicChips.tsx",
1823
+ "importPath": "@working-theory/ui/blocks",
1824
+ "exported": true,
1825
+ "description": "\"All\" plus one chip per tag, each a real server-rendered link — never a client-side filter — with the current page's chip marked `aria-current=\"page\"` / `data-current=\"true\"` in the served HTML. Composes Button (asChild) + Link; RSC, no client state.",
1826
+ "descriptionSource": "doc-comment",
1827
+ "descriptionGrade": "weak",
1828
+ "sectionType": null
1829
+ },
1753
1830
  {
1754
1831
  "name": "UpgradeDialog",
1755
1832
  "tier": "blocks",
@@ -1959,6 +2036,17 @@
1959
2036
  "descriptionGrade": "strong",
1960
2037
  "sectionType": null
1961
2038
  },
2039
+ {
2040
+ "name": "NavBackLink",
2041
+ "tier": "chrome",
2042
+ "sourcePath": "packages/ui/src/chrome/NavBackLink.tsx",
2043
+ "importPath": "@working-theory/ui/chrome",
2044
+ "exported": true,
2045
+ "description": "The bare header's back arrow, deck-return-aware.",
2046
+ "descriptionSource": "doc-comment",
2047
+ "descriptionGrade": "weak",
2048
+ "sectionType": null
2049
+ },
1962
2050
  {
1963
2051
  "name": "NavMobileMenu",
1964
2052
  "tier": "chrome",
@@ -3235,6 +3323,17 @@
3235
3323
  "descriptionGrade": "strong",
3236
3324
  "sectionType": null
3237
3325
  },
3326
+ {
3327
+ "name": "BookingProfileHeader",
3328
+ "tier": "scheduling-blocks",
3329
+ "sourcePath": "packages-paid/ui-scheduling/src/blocks/BookingProfileHeader.tsx",
3330
+ "importPath": "@working-theory/ui-scheduling",
3331
+ "exported": true,
3332
+ "description": "The /meet hub's opening header. The Cal.com / Calendly profile shape: whose calendar this is, then one line saying what to do here. A centered portrait over a single h1, and nothing beneath it — the booker cards below carry each event's own title, so a subtitle here would only repeat them.",
3333
+ "descriptionSource": "doc-comment",
3334
+ "descriptionGrade": "strong",
3335
+ "sectionType": null
3336
+ },
3238
3337
  {
3239
3338
  "name": "FlowStepper",
3240
3339
  "tier": "signal-collectors-sections",
@@ -5109,6 +5208,10 @@
5109
5208
  },
5110
5209
  "columns": {
5111
5210
  "anyOf": [
5211
+ {
5212
+ "type": "number",
5213
+ "const": 1
5214
+ },
5112
5215
  {
5113
5216
  "type": "number",
5114
5217
  "const": 2
@@ -5301,6 +5404,9 @@
5301
5404
  "headline": {
5302
5405
  "type": "string"
5303
5406
  },
5407
+ "subtitle": {
5408
+ "type": "string"
5409
+ },
5304
5410
  "body": {
5305
5411
  "type": "string"
5306
5412
  },
@@ -6660,6 +6766,20 @@
6660
6766
  },
6661
6767
  "annualBadge": {
6662
6768
  "type": "string"
6769
+ },
6770
+ "priceSubheadAccent": {
6771
+ "type": "string"
6772
+ },
6773
+ "priceSubheadAccentTone": {
6774
+ "type": "string",
6775
+ "enum": [
6776
+ "default",
6777
+ "muted",
6778
+ "brand",
6779
+ "success",
6780
+ "danger",
6781
+ "warning"
6782
+ ]
6663
6783
  }
6664
6784
  },
6665
6785
  "required": [
@@ -6878,6 +6998,20 @@
6878
6998
  },
6879
6999
  "annualBadge": {
6880
7000
  "type": "string"
7001
+ },
7002
+ "priceSubheadAccent": {
7003
+ "type": "string"
7004
+ },
7005
+ "priceSubheadAccentTone": {
7006
+ "type": "string",
7007
+ "enum": [
7008
+ "default",
7009
+ "muted",
7010
+ "brand",
7011
+ "success",
7012
+ "danger",
7013
+ "warning"
7014
+ ]
6881
7015
  }
6882
7016
  },
6883
7017
  "required": [
@@ -9045,7 +9179,8 @@
9045
9179
  "enum": [
9046
9180
  "with-testimonial",
9047
9181
  "split-with-pattern",
9048
- "card"
9182
+ "card",
9183
+ "column"
9049
9184
  ]
9050
9185
  },
9051
9186
  "pattern": {
@@ -9733,11 +9868,68 @@
9733
9868
  "baseUrl"
9734
9869
  ]
9735
9870
  },
9871
+ "topics": {
9872
+ "type": "array",
9873
+ "items": {
9874
+ "type": "object",
9875
+ "properties": {
9876
+ "label": {
9877
+ "type": "string"
9878
+ },
9879
+ "href": {
9880
+ "type": "string"
9881
+ },
9882
+ "current": {
9883
+ "type": "boolean"
9884
+ }
9885
+ },
9886
+ "required": [
9887
+ "label",
9888
+ "href"
9889
+ ]
9890
+ }
9891
+ },
9892
+ "sidebar": {
9893
+ "maxItems": 3,
9894
+ "type": "array",
9895
+ "items": {
9896
+ "type": "object",
9897
+ "properties": {
9898
+ "heading": {
9899
+ "type": "string"
9900
+ },
9901
+ "text": {
9902
+ "type": "string"
9903
+ },
9904
+ "cta": {
9905
+ "type": "object",
9906
+ "properties": {
9907
+ "label": {
9908
+ "type": "string"
9909
+ },
9910
+ "href": {
9911
+ "type": "string"
9912
+ }
9913
+ },
9914
+ "required": [
9915
+ "label",
9916
+ "href"
9917
+ ]
9918
+ }
9919
+ },
9920
+ "required": [
9921
+ "heading",
9922
+ "text",
9923
+ "cta"
9924
+ ]
9925
+ }
9926
+ },
9736
9927
  "variant": {
9737
9928
  "type": "string",
9738
9929
  "enum": [
9739
9930
  "list",
9740
- "grid"
9931
+ "grid",
9932
+ "index"
9741
9933
  ]
9742
9934
  }
9743
9935
  },
@@ -11999,7 +12191,7 @@
11999
12191
  "groups": [
12000
12192
  {
12001
12193
  "name": "colors",
12002
- "leafCount": 62,
12194
+ "leafCount": 64,
12003
12195
  "leaves": [
12004
12196
  {
12005
12197
  "path": "colors.light.bg",
@@ -12021,6 +12213,11 @@
12021
12213
  "type": "string",
12022
12214
  "value": "#475569"
12023
12215
  },
12216
+ {
12217
+ "path": "colors.light.link",
12218
+ "type": "string",
12219
+ "value": "#2563eb"
12220
+ },
12024
12221
  {
12025
12222
  "path": "colors.light.textMuted",
12026
12223
  "type": "string",
@@ -12176,6 +12373,11 @@
12176
12373
  "type": "string",
12177
12374
  "value": "#94a3b8"
12178
12375
  },
12376
+ {
12377
+ "path": "colors.dark.link",
12378
+ "type": "string",
12379
+ "value": "#60a5fa"
12380
+ },
12179
12381
  {
12180
12382
  "path": "colors.dark.textMuted",
12181
12383
  "type": "string",
@@ -13009,7 +13211,7 @@
13009
13211
  },
13010
13212
  {
13011
13213
  "name": "spacing",
13012
- "leafCount": 13,
13214
+ "leafCount": 15,
13013
13215
  "leaves": [
13014
13216
  {
13015
13217
  "path": "spacing.controlX",
@@ -13056,6 +13258,16 @@
13056
13258
  "type": "string",
13057
13259
  "value": "1rem"
13058
13260
  },
13261
+ {
13262
+ "path": "spacing.deckLabelLine",
13263
+ "type": "string",
13264
+ "value": "var(--spacing-section-lg)"
13265
+ },
13266
+ {
13267
+ "path": "spacing.deckActionLine",
13268
+ "type": "string",
13269
+ "value": "calc(2 * (var(--control-height-big) + var(--spacing-block-tight)))"
13270
+ },
13059
13271
  {
13060
13272
  "path": "spacing.paragraph",
13061
13273
  "type": "string",
@@ -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: **311 component files** across 16 tiers — 55 sections, 101 blocks, 18 chrome, 79 primitives, 6 panels, 8 shells, 5 docs-blog-blocks, 1 docs-blog-chrome, 2 docs-blog-shells, 2 scheduling-sections, 5 scheduling-blocks, 5 signal-collectors-sections, 9 signal-collectors-blocks, 2 commerce-sections, 12 commerce-blocks, 1 commerce-panels. 308 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: **320 component files** across 16 tiers — 56 sections, 107 blocks, 19 chrome, 79 primitives, 6 panels, 8 shells, 5 docs-blog-blocks, 1 docs-blog-chrome, 2 docs-blog-shells, 2 scheduling-sections, 6 scheduling-blocks, 5 signal-collectors-sections, 9 signal-collectors-blocks, 2 commerce-sections, 12 commerce-blocks, 1 commerce-panels. 317 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: **307/311** components carry a capability description derived from source — 44 from the section registry's hand-authored when-to-use text, 232 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.
20
+ Description coverage: **316/320** components carry a capability description derived from source — 44 from the section registry's hand-authored when-to-use text, 237 from the component's own doc-comment header, 35 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, **276** are *strong* — a capability sentence you could match a requirement against — and **31** 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%**.
22
+ Of those, **282** are *strong* — a capability sentence you could match a requirement against — and **34** 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%**.
23
23
 
24
- Per tier, strong out of total: **Sections** 54/55 · **Blocks** 95/101 · **Chrome** 10/18 · **Primitives** 64/79 (4 undescribed) · **Panels** 5/6 · **Shells** 7/8 · **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** 5/5 · **Signal-Collectors Blocks** 8/9 · **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** 55/56 · **Blocks** 99/107 · **Chrome** 10/19 · **Primitives** 64/79 (4 undescribed) · **Panels** 5/6 · **Shells** 7/8 · **Docs + Blog Blocks** 5/5 · **Docs + Blog Chrome** 1/1 · **Docs + Blog Shells** 0/2 · **Scheduling Sections** 2/2 · **Scheduling Blocks** 6/6 · **Signal-Collectors Sections** 5/5 · **Signal-Collectors Blocks** 8/9 · **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
 
@@ -53,7 +53,7 @@ The left column is search vocabulary — how a person phrases the need. It is ha
53
53
 
54
54
  ---
55
55
 
56
- ## Sections (55)
56
+ ## Sections (56)
57
57
 
58
58
  Page-level content bands. Most are MDX-driven (authored by `type` in page frontmatter and dispatched by `SectionRenderer`); a few are composed directly in a route.
59
59
 
@@ -93,6 +93,7 @@ Import: `import { X } from "@working-theory/ui/sections";` (or from the `@workin
93
93
  | `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"` |
94
94
  | `HeroQuickSearch` | Surfaces the existing ⌘K quick-search as an inline trigger inside the hero CTA cluster (Chassis 2). It does NOT re-roll a search input: it composes the SearchInputTrigger primitive (an input-style control at CTA height) plus the Command palette primitive, so there is ONE search mechanism. The nav's compact 24px ⌘K pill stays as-is — the hero reads as a search INPUT, matching the 44px "Get Started" CTA. | internal — compose from `packages/ui/src/sections/HeroQuickSearch.tsx` |
95
95
  | `Items` | A general-purpose card/items grid — use for services, use cases, or any homogeneous list of cards. | MDX `type: items` · `import { Items } from "@working-theory/ui/sections"` |
96
+ | `ItemsTileLinkIsland` | Makes a whole linked tile card its link's click target. | internal — compose from `packages/ui/src/sections/ItemsTileLinkIsland.tsx` |
96
97
  | `LogoCloud` | A customer/partner logo wall — use for trust-by-association social proof. | MDX `type: logoCloud` · `import { LogoCloud } from "@working-theory/ui/sections"` |
97
98
  | `MDXContent` | The keystone the docs route and blog both render their compiled MDX through. | `import { MDXContent } from "@working-theory/ui/sections"` |
98
99
  | `MediaBand` | A single full-width image band — use for the 1-up visual moment Gallery's 2-column minimum can't express (e.g. between pricing plan columns); renders a labeled placeholder until a real image is supplied. | MDX `type: media-band` · `import { MediaBand } from "@working-theory/ui/sections"` |
@@ -119,7 +120,7 @@ Import: `import { X } from "@working-theory/ui/sections";` (or from the `@workin
119
120
 
120
121
  ---
121
122
 
122
- ## Blocks (101)
123
+ ## Blocks (107)
123
124
 
124
125
  Intermediate composition tiles — the pieces sections are built from. Compose primitives and panels; never import a section.
125
126
 
@@ -135,12 +136,14 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
135
136
  | `AskAiCommandGroup` | The ⌘K palette's composable "Ask AI" group; one CommandItem per provider seeding "About &lt;entity>: &lt;query>" (or the entity promptText when the query is empty); composed by the consumer's own CommandDialog. | `import { AskAiCommandGroup } from "@working-theory/ui/blocks"` |
136
137
  | `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"` |
137
138
  | `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 link/Replace/Delete. Renders inside a ResponsiveSheet — the SAME field sequence in both layouts (: one field array, walked once, shared by both). | `import { AssetLightbox } from "@working-theory/ui/blocks"` |
139
+ | `AuthBackLink` | The auth shell's top-start "← Back to &lt;site>". A hub's sign-in page renders it when the visitor arrived from a registered spoke, so the cross-domain handoff names where they came from and gives them the way back. | `import { AuthBackLink } from "@working-theory/ui/blocks"` |
138
140
  | `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"` |
139
141
  | `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` |
140
142
  | `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"` |
141
143
  | `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"` |
142
144
  | `BillingToggle` | Monthly / Annual billing period toggle for the Pricing section. Purely presentational — click handlers come from the parent (Pricing section). No "use client" needed at the block level; the parent Pricing section is "use client" and passes the callbacks down. | `import { BillingToggle } from "@working-theory/ui/blocks"` |
143
145
  | `BlogPostHeader` | Layout: the better-auth sticky left rail — eyebrow ← back-link · decorative title · author byline · date/reading-time meta · tags. Per the title here is DECORATIVE (aria-hidden): the semantic `&lt;h1>` is rendered atop the right content column by `BlogPost` (bodyOnly), so the page keeps exactly one heading. | `import { BlogPostHeader } from "@working-theory/ui/blocks"` |
146
+ | `BlogSidebarBox` | Heading + text + one outlined button, authored on the blog index page's own content. Content is set on the blog index page's own frontmatter, never hard-coded. | `import { BlogSidebarBox } from "@working-theory/ui/blocks"` |
144
147
  | `BrandLogo` | Brand identity lockup (mark + wordmark + badge slot) as a home link. Brand identity lockup for Nav, Footer, and other surfaces. | `import { BrandLogo } from "@working-theory/ui/blocks"` |
145
148
  | `Card` | The base surface-chrome block the section tier composes instead of hand-rolling `rounded + hairline border + surface + p-card` (7× duplicated at time). A block owns its visual identity: it looks the same everywhere it is composed, and a higher tier SELECTS a look via `variant` — it never patches via className. | `import { Card } from "@working-theory/ui/blocks"` |
146
149
  | `ChoiceCard` | Onboarding "Is this you?" disambiguation card — reimplements the ploy.ai persona-disambiguation capture: a researched candidate option vs a "none of these are me" option, confirmed by the user ("a human-input seam the user answers to orient the agent"). Purely presentational and props-driven: renders options + an optional "none of these" option + a confirm action, disabled until a selection is made. | `import { ChoiceCard } from "@working-theory/ui/blocks"` |
@@ -193,8 +196,11 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
193
196
  | `PageHeaderSwitcher` | Header-owned context-switcher island (DropdownMenu-backed). Header-owned segment switcher rendered inside PageHeader's `switcher` slot. Composes DropdownMenu (Radix-backed — positioning, click-outside, Escape, focus management, and roving keyboard nav all come for free). | `import { PageHeaderSwitcher } from "@working-theory/ui/blocks"` |
194
197
  | `PairingCard` | Quiet card naming a matched WaitlistRecommendation pairing: both resolved offer names + the resolved reason, joined against a caller-supplied offers[] by key. | `import { PairingCard } from "@working-theory/ui/blocks"` |
195
198
  | `PhoneNumberField` | Single-box combo phone input for the "tel" field kind (supersedes the two-segment country-Select + number-Input layout). | `import { PhoneNumberField } from "@working-theory/ui/blocks"` |
199
+ | `PostArchiveRow` | One "Previous posts" row: title link + meta, host-placed `Separator` between rows. Draws no divider of its own (border convention — the host places `Separator` between rows, as BlogList already does for PostRow). | `import { PostArchiveRow } from "@working-theory/ui/blocks"` |
196
200
  | `PostCard` | Same props as PostRow, a vertical arrangement. A single linked blog-index card (cover · date · reading time · title · excerpt · "Read more"). Tier 3 block; composes MediaFigure + primitives. RSC default. *(weak)* | `import { PostCard } from "@working-theory/ui/blocks"` |
201
+ | `PostMetaLine` | `&lt;tag> · &lt;date>` meta line, either half optional (C4). Shows the tag alone, the date alone, or both joined by a middot; renders nothing with neither. | `import { PostMetaLine } from "@working-theory/ui/blocks"` |
197
202
  | `PostRow` | A single horizontal row in the two-column blog index: a landscape cover thumbnail (left) followed by the post's title · excerpt · author+date meta · tag list. The WHOLE row is one link (aria-labelled by the title, so screen readers announce the post title rather than the concatenated row content). | `import { PostRow } from "@working-theory/ui/blocks"` |
203
+ | `PostTeaser` | The featured post (`size="lg"`) and "Latest posts" grid card (`size="md"`); no cover renders no placeholder. Composes MediaFigure + PostMetaLine + primitives. No author byline (C7). RSC: no hooks or browser APIs. *(weak)* | `import { PostTeaser } from "@working-theory/ui/blocks"` |
198
204
  | `PricingExtraTierRow` | Compact horizontal extra-tier row for the Pricing 'two-tiers-with-extra-tier' variant. Renders plan name + description on the left and a CTA on the right. Structurally different from PricingTierCard (no price display in this variant). | `import { PricingExtraTierRow } from "@working-theory/ui/blocks"` |
199
205
  | `PricingTierCard` | Single pricing tier card (all 4 Pricing variants). Renders a single pricing tier: plan name + optional badge, description, price display, CTA button, and feature checklist. | `import { PricingTierCard } from "@working-theory/ui/blocks"` |
200
206
  | `PricingTierCardFeaturesDisclosure` | Phone-breakpoint (&lt;md, 768px — the same phone/tablet step CardGrid's own mobile-stacking ramp uses) presentation of a tier card's feature list: collapsed behind a disclosure button so price + CTA (which render ABOVE this row, see PricingTierCard's row-slot ordering) fit the first card view on a phone — the wisprflow.ai/pricing pattern. `md` and up renders the SAME feature list, always expanded, in a second, non-interactive tree — desktop/tablet is unaffected by the collapse. | `import { PricingTierCardFeaturesDisclosure } from "@working-theory/ui/blocks"` |
@@ -220,6 +226,7 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
220
226
  | `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"` |
221
227
  | `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"` |
222
228
  | `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"` |
229
+ | `TopicChips` | "All" plus one chip per tag, each a real server-rendered link — never a client-side filter — with the current page's chip marked `aria-current="page"` / `data-current="true"` in the served HTML. Composes Button (asChild) + Link; RSC, no client state. *(weak)* | `import { TopicChips } from "@working-theory/ui/blocks"` |
223
230
  | `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"` |
224
231
  | `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"` |
225
232
  | `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"` |
@@ -231,7 +238,7 @@ Import: `import { X } from "@working-theory/ui/blocks";` (or from the `@working-
231
238
 
232
239
  ---
233
240
 
234
- ## Chrome (18)
241
+ ## Chrome (19)
235
242
 
236
243
  Persistent framing UI that wraps page content — navigation, footers, sidebars, banners. Not MDX-driven.
237
244
 
@@ -249,6 +256,7 @@ Import: `import { X } from "@working-theory/ui/chrome";` (or from the `@working-
249
256
  | `FloatingCta` | Persistent fixed corner action pill. *(weak)* | `import { FloatingCta } from "@working-theory/ui/chrome"` |
250
257
  | `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"` |
251
258
  | `Nav` | Primary navigation server component with locale-aware links. Three layout variants supported via `navAlignment`. Flyout (mega-menu) items: the desktop link list is ALWAYS a single SectionNav render path — there is no Path A/B fork. | `import { Nav } from "@working-theory/ui/chrome"` |
259
+ | `NavBackLink` | The bare header's back arrow, deck-return-aware. *(weak)* | `import { NavBackLink } from "@working-theory/ui/chrome"` |
252
260
  | `NavMobileMenu` | 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). *(weak)* | internal — compose from `packages/ui/src/chrome/NavMobileMenu.tsx` |
253
261
  | `NavOverflowMenu` | The Nav row's per-breakpoint link-overflow disclosure. The desktop link row never wraps: a fixed per-breakpoint inline-link budget (4 at md, 6 at lg+) is enforced by the caller (Nav), which splits `navItems` into an inline slice and an overflow slice. This component renders ONLY the overflow slice, behind a disclosure trigger, in source order — it never computes the budget itself. | internal — compose from `packages/ui/src/chrome/NavOverflowMenu.tsx` |
254
262
  | `NotificationsMenu` | Bell trigger + notifications popover chrome (ploy-capture app-frame). Presentational, props-driven. The unread count and row list arrive entirely via props — this component carries NO fetch/polling logic of its own. Rows are pre-formatted at the call site (relative time, heading, empty copy) per the AILK i18n/formatting convention (design-system-rules Rule 10). | `import { NotificationsMenu } from "@working-theory/ui/chrome"` |
@@ -440,7 +448,7 @@ Import: `import { X } from "@working-theory/ui-scheduling";` (or from the `@work
440
448
 
441
449
  ---
442
450
 
443
- ## Scheduling Blocks (5)
451
+ ## Scheduling Blocks (6)
444
452
 
445
453
  Paid — delivered by the `scheduling` module bundle, not present in a Free scaffold. The pieces the booker section is built from — slot pickers, timezone and confirmation furniture.
446
454
 
@@ -453,6 +461,7 @@ Import: `import { X } from "@working-theory/ui-scheduling";` (or from the `@work
453
461
  | `BookerEventHeader` | TWO KINDS OF TEXT, AND THE DIFFERENCE MATTERS. `eyebrow` and `headline` are AUTHORED: the operator's words about why someone would book. Everything in the meta row — duration, meeting type, timezone — is DERIVED from the calendar on the availability read, which is why the section schema has no field for any of it. | internal — compose from `packages-paid/ui-scheduling/src/blocks/BookerEventHeader.tsx` |
454
462
  | `BookerPanel` | The booking card, fully controlled. One chassis, five views, ONE FOOTER. | internal — compose from `packages-paid/ui-scheduling/src/blocks/BookerPanel.tsx` |
455
463
  | `BookerSlotGrid` | The selected day's open times. Two columns at 48px on mobile — the measured pixel target — widening to three at `lg` so the desktop variant shows more of the day without scrolling. Two columns is not an arbitrary density: at 390px it is the widest grid where a time still reads at a glance, and it puts eight times (a full working day's openings) inside one thumb-reachable block. | internal — compose from `packages-paid/ui-scheduling/src/blocks/BookerSlotGrid.tsx` |
464
+ | `BookingProfileHeader` | The /meet hub's opening header. The Cal.com / Calendly profile shape: whose calendar this is, then one line saying what to do here. A centered portrait over a single h1, and nothing beneath it — the booker cards below carry each event's own title, so a subtitle here would only repeat them. | `import { BookingProfileHeader } from "@working-theory/ui-scheduling"` |
456
465
 
457
466
  ---
458
467
 
@@ -542,7 +551,7 @@ Import: `import { X } from "@working-theory/ui-commerce";` (or from the `@workin
542
551
 
543
552
  ## Weak descriptions
544
553
 
545
- These 31 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.
554
+ These 34 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.
546
555
 
547
556
  | Component | Tier | What the source gives us |
548
557
  | --- | --- | --- |
@@ -550,14 +559,17 @@ These 31 components have a description that is accurate but does not do the job
550
559
  | `AgentContextCard` | blocks | Capability-bearing block that renders agent provenance. RSC note: no hooks or browser APIs — server-renderable by default. |
551
560
  | `PageHeader` | blocks | RSC. Page-level header composing the Breadcrumb primitive for the left-side wayfinding trail, a generic right-side `actions` slot, and an optional `switcher` slot for header-owned segment switching. Fills AppShell's `header` slot (AdminShell and similar). No accent bar. No tab API. No named action props — the caller assembles whatever button/menu nodes it needs and passes them as `actions`. |
552
561
  | `PostCard` | blocks | Same props as PostRow, a vertical arrangement. A single linked blog-index card (cover · date · reading time · title · excerpt · "Read more"). Tier 3 block; composes MediaFigure + primitives. RSC default. |
562
+ | `PostTeaser` | blocks | The featured post (`size="lg"`) and "Latest posts" grid card (`size="md"`); no cover renders no placeholder. Composes MediaFigure + PostMetaLine + primitives. No author byline (C7). RSC: no hooks or browser APIs. |
553
563
  | `PricingTierCardSeatControl` | blocks | Seat-count range picker for the Team tier card. Renders a Select primitive that lets the user choose an "Up to N seats" option from a caller-supplied list of numbers. The parent PricingTierCard stays a pure RSC; only this tiny island is hydrated. Pattern mirrors PageHeaderSwitcher.tsx: small "use client" island imported by an otherwise-server-rendered parent, keeping the client bundle minimal. |
554
564
  | `SinglePriceCard` | blocks | 2-column split card for the Pricing 'single-price-with-details' variant. |
555
565
  | `SocialIcons` | blocks | 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. |
566
+ | `TopicChips` | blocks | "All" plus one chip per tag, each a real server-rendered link — never a client-side filter — with the current page's chip marked `aria-current="page"` / `data-current="true"` in the served HTML. Composes Button (asChild) + Link; RSC, no client state. |
556
567
  | `Banner` | chrome | Site-wide announcement and consent banner chrome. |
557
568
  | `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). |
558
569
  | `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. |
559
570
  | `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). |
560
571
  | `FloatingCta` | chrome | Persistent fixed corner action pill. |
572
+ | `NavBackLink` | chrome | The bare header's back arrow, deck-return-aware. |
561
573
  | `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). |
562
574
  | `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.). |
563
575
  | `SidebarNavRailItems` | chrome | Client boundary for the collapsed rail tooltip affordance. Isolated "use client" boundary so SidebarNav itself stays a Server Component. Renders icon-only nav links with Tooltip labels when the rail is collapsed. |
@@ -168,16 +168,20 @@ export async function applyModuleToScaffold(opts) {
168
168
  }
169
169
  : {}),
170
170
  });
171
- // #7838 D8 — commerce on a spoke brings the spoke's own checkout, stamped
172
- // from the site directory's name (the `projectName` scaffold() defaults to).
173
- // A file the site already has is left as it is. #8217 D8 — on a spoke that
174
- // signs in through its hub, the billing action and its seam render too, so
175
- // re-running add-module commerce gives an existing commerce spoke the action.
171
+ // #7838 D8 — commerce on a spoke brings the spoke's own checkout — the
172
+ // standard, copied from the template (#8748 D3) — stamped from the site
173
+ // directory's name (the `projectName` scaffold() defaults to). A file the
174
+ // site already has is left as it is. No record row: the record is written
175
+ // only at scaffold time. #8217 D8 — on a spoke that signs in through its
176
+ // hub, the billing action and its seam render too, so re-running
177
+ // add-module commerce gives an existing commerce spoke the action.
176
178
  const spokeCommerce = spoke !== undefined && opts.moduleName === "commerce";
177
179
  const checkoutRows = spokeCommerce
178
180
  ? writeSpokeCheckoutSlice(targetAbs, {
179
181
  site: spokeCheckoutSite(basename(targetAbs)),
182
+ projectName: basename(targetAbs),
180
183
  versions: opts.versions,
184
+ templatesDir: opts.templatesDir,
181
185
  signIn: spoke.signIn,
182
186
  })
183
187
  : [];