@north-light/crouter 0.3.198 → 0.3.199

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 (314) hide show
  1. package/dist/api/client.d.ts +18 -18
  2. package/dist/api/client.js +29 -37
  3. package/dist/api/dto/broker-ops.d.ts +3 -1
  4. package/dist/api/dto/broker.d.ts +13 -2
  5. package/dist/api/dto/broker.js +4 -4
  6. package/dist/api/dto/human.d.ts +6 -5
  7. package/dist/api/dto/inbox.d.ts +81 -34
  8. package/dist/api/dto/nodes.d.ts +8 -2
  9. package/dist/api/dto/profiles.d.ts +23 -0
  10. package/dist/api/dto/profiles.js +2 -2
  11. package/dist/api/dto/worktree.d.ts +3 -5
  12. package/dist/api/routes.d.ts +3 -5
  13. package/dist/api/routes.js +4 -6
  14. package/dist/builtin-memory/00-runtime-base.md +6 -6
  15. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  16. package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
  17. package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
  18. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
  20. package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
  21. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
  22. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
  23. package/dist/builtin-memory/insights/capture.md +2 -2
  24. package/dist/builtin-memory/insights/init.md +3 -3
  25. package/dist/builtin-memory/internal/INDEX.md +1 -1
  26. package/dist/builtin-memory/internal/memory-loading.md +1 -1
  27. package/dist/builtin-memory/internal/nodes-and-canvas.md +2 -2
  28. package/dist/builtin-memory/internal/plugins.md +59 -2
  29. package/dist/builtin-memory/internal/storage-tiers.md +3 -1
  30. package/dist/builtin-memory/spec/roadmap.md +2 -2
  31. package/dist/builtin-memory/testing.md +1 -1
  32. package/dist/clients/attach/chrome/bash-jobs.js +3 -3
  33. package/dist/clients/attach/render/chat-view.d.ts +26 -1
  34. package/dist/clients/attach/render/chat-view.js +61 -11
  35. package/dist/clients/attach/render/page-block.d.ts +34 -0
  36. package/dist/clients/attach/render/page-block.js +226 -0
  37. package/dist/clients/attach/session/whip.d.ts +2 -0
  38. package/dist/clients/attach/session/whip.js +2 -0
  39. package/dist/clients/attach/viewer.js +1419 -993
  40. package/dist/clients/conversation/projection.js +18 -3
  41. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
  42. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -21
  43. package/dist/clients/inbox/controller.js +10 -16
  44. package/dist/clients/inbox/page-adapter.d.ts +1 -3
  45. package/dist/clients/inbox/page-adapter.js +3 -4
  46. package/dist/clients/inbox/resolve.d.ts +1 -1
  47. package/dist/clients/inbox/resolve.js +3 -3
  48. package/dist/clients/inbox/review/document-surface.js +3 -1
  49. package/dist/clients/inbox/review/state.d.ts +2 -1
  50. package/dist/clients/inbox/review/state.js +5 -16
  51. package/dist/clients/inbox/tui/input.js +37 -19
  52. package/dist/clients/inbox/tui/panel.js +5 -31
  53. package/dist/clients/inbox/tui/render.d.ts +11 -0
  54. package/dist/clients/inbox/tui/render.js +71 -27
  55. package/dist/clients/inbox/tui/slots.d.ts +3 -1
  56. package/dist/clients/inbox/tui/slots.js +16 -12
  57. package/dist/clients/inbox/tui/types.d.ts +2 -7
  58. package/dist/clients/inbox/tui.js +2 -2
  59. package/dist/commands/__tests__/human.test.js +71 -114
  60. package/dist/commands/__tests__/node-message.test.js +14 -0
  61. package/dist/commands/attention.js +4 -4
  62. package/dist/commands/canvas-snapshot.js +1 -1
  63. package/dist/commands/canvas.js +1 -1
  64. package/dist/commands/dashboard.js +1 -1
  65. package/dist/commands/human/{html.d.ts → components.d.ts} +1 -1
  66. package/dist/commands/human/components.js +77 -0
  67. package/dist/commands/human/feedback.d.ts +2 -0
  68. package/dist/commands/human/feedback.js +108 -0
  69. package/dist/commands/human/prompts.d.ts +13 -5
  70. package/dist/commands/human/prompts.js +160 -97
  71. package/dist/commands/human/queue.js +57 -88
  72. package/dist/commands/human/shared.d.ts +2 -12
  73. package/dist/commands/human/shared.js +17 -11
  74. package/dist/commands/human.js +29 -21
  75. package/dist/commands/memory/origin.js +2 -2
  76. package/dist/commands/node/bash.js +4 -4
  77. package/dist/commands/node/inspect.js +3 -3
  78. package/dist/commands/node/lifecycle.js +1 -1
  79. package/dist/commands/node/message.js +2 -2
  80. package/dist/commands/node-worktree.js +8 -8
  81. package/dist/commands/pkg/browse.js +1 -1
  82. package/dist/commands/pkg/plugin-manage.js +129 -10
  83. package/dist/commands/profile/delete.js +41 -15
  84. package/dist/commands/profile.js +1 -1
  85. package/dist/commands/push.js +1 -1
  86. package/dist/commands/surface/node/focus.js +29 -14
  87. package/dist/commands/surface-edit.js +3 -3
  88. package/dist/commands/surface-inbox.d.ts +2 -0
  89. package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
  90. package/dist/commands/surface.js +2 -1
  91. package/dist/commands/sys/doctor.js +60 -3
  92. package/dist/commands/sys/feedback.js +79 -25
  93. package/dist/commands/sys/settings.js +1 -1
  94. package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
  95. package/dist/core/__tests__/extension-abort.test.js +5 -2
  96. package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
  97. package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
  98. package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
  99. package/dist/core/__tests__/human-deliver.test.js +31 -26
  100. package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
  101. package/dist/core/__tests__/plugin-page-components.test.js +157 -0
  102. package/dist/core/__tests__/seam/dormancy-release.test.js +40 -0
  103. package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
  104. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -6
  105. package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
  106. package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
  107. package/dist/core/__tests__/serial/worktree.test.js +24 -198
  108. package/dist/core/__tests__/session-cycles.test.js +22 -0
  109. package/dist/core/__tests__/stop-guard.test.js +4 -4
  110. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
  111. package/dist/core/bash-jobs.d.ts +6 -0
  112. package/dist/core/bash-jobs.js +10 -0
  113. package/dist/core/canvas/__tests__/attention.test.js +8 -5
  114. package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
  115. package/dist/core/canvas/attention.js +8 -6
  116. package/dist/core/canvas/browse/model.d.ts +8 -2
  117. package/dist/core/canvas/browse/model.js +11 -4
  118. package/dist/core/canvas/canvas.d.ts +7 -0
  119. package/dist/core/canvas/canvas.js +37 -0
  120. package/dist/core/canvas/crons.d.ts +9 -0
  121. package/dist/core/canvas/crons.js +51 -0
  122. package/dist/core/canvas/node-order.d.ts +22 -1
  123. package/dist/core/canvas/node-order.js +26 -1
  124. package/dist/core/canvas/render-source.d.ts +5 -0
  125. package/dist/core/canvas/render-source.js +1 -0
  126. package/dist/core/canvas/types.d.ts +9 -3
  127. package/dist/core/command-plugins/bundle.d.ts +6 -1
  128. package/dist/core/command-plugins/bundle.js +26 -13
  129. package/dist/core/command.js +2 -1
  130. package/dist/core/config.d.ts +47 -1
  131. package/dist/core/config.js +137 -4
  132. package/dist/core/help.js +1 -1
  133. package/dist/core/human/__tests__/page-catalog.test.js +4 -0
  134. package/dist/core/human/__tests__/page-tickets.test.js +65 -52
  135. package/dist/core/human/__tests__/page.test.js +56 -98
  136. package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -6
  137. package/dist/core/human/answer-text.js +3 -3
  138. package/dist/core/human/answer.d.ts +6 -4
  139. package/dist/core/human/answer.js +4 -10
  140. package/dist/core/human/component-docs.d.ts +11 -7
  141. package/dist/core/human/component-docs.js +361 -88
  142. package/dist/core/human/convention.d.ts +17 -3
  143. package/dist/core/human/convention.js +36 -5
  144. package/dist/core/human/feedback-companion.d.ts +20 -0
  145. package/dist/core/human/feedback-companion.js +98 -0
  146. package/dist/core/human/feedback.d.ts +65 -0
  147. package/dist/core/human/feedback.js +116 -0
  148. package/dist/core/human/page-catalog.d.ts +40 -5
  149. package/dist/core/human/page-catalog.js +102 -30
  150. package/dist/core/human/page-errors.d.ts +4 -0
  151. package/dist/core/human/page-errors.js +7 -0
  152. package/dist/core/human/page-eval.d.ts +20 -0
  153. package/dist/core/human/page-eval.js +123 -0
  154. package/dist/core/human/page-schema.d.ts +23 -191
  155. package/dist/core/human/page-schema.js +60 -89
  156. package/dist/core/human/page-synth.d.ts +1 -1
  157. package/dist/core/human/page-synth.js +15 -18
  158. package/dist/core/human/page.d.ts +12 -27
  159. package/dist/core/human/page.js +142 -474
  160. package/dist/core/human/root.d.ts +6 -2
  161. package/dist/core/human/root.js +12 -4
  162. package/dist/core/human/scan.d.ts +3 -4
  163. package/dist/core/human/scan.js +16 -15
  164. package/dist/core/human/summary.d.ts +1 -2
  165. package/dist/core/human/summary.js +2 -2
  166. package/dist/core/human/tickets.d.ts +20 -14
  167. package/dist/core/human/tickets.js +91 -31
  168. package/dist/core/human/types.d.ts +10 -2
  169. package/dist/core/preview-registry.js +8 -10
  170. package/dist/core/profiles/default-binding.d.ts +4 -0
  171. package/dist/core/profiles/default-binding.js +24 -0
  172. package/dist/core/profiles/deletion-reservation.d.ts +8 -0
  173. package/dist/core/profiles/deletion-reservation.js +38 -0
  174. package/dist/core/profiles/manifest.d.ts +3 -0
  175. package/dist/core/profiles/manifest.js +12 -0
  176. package/dist/core/profiles/select.js +2 -2
  177. package/dist/core/review/realize.js +3 -3
  178. package/dist/core/review/store.d.ts +6 -0
  179. package/dist/core/review/store.js +31 -0
  180. package/dist/core/runtime/bearings.d.ts +0 -4
  181. package/dist/core/runtime/bearings.js +4 -62
  182. package/dist/core/runtime/bin-contributions.d.ts +44 -0
  183. package/dist/core/runtime/bin-contributions.js +186 -0
  184. package/dist/core/runtime/broker/client-registry.d.ts +4 -0
  185. package/dist/core/runtime/broker/client-registry.js +13 -1
  186. package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
  187. package/dist/core/runtime/broker/extension-abort.js +4 -2
  188. package/dist/core/runtime/broker/fault-retry.js +4 -0
  189. package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
  190. package/dist/core/runtime/broker/frame-dispatch.js +5 -6
  191. package/dist/core/runtime/broker/read-ops.d.ts +1 -1
  192. package/dist/core/runtime/broker/read-ops.js +2 -2
  193. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  194. package/dist/core/runtime/broker/rebind.js +6 -1
  195. package/dist/core/runtime/broker-extension-render.js +19 -0
  196. package/dist/core/runtime/broker-protocol.d.ts +14 -0
  197. package/dist/core/runtime/broker.d.ts +1 -1
  198. package/dist/core/runtime/broker.js +17 -6
  199. package/dist/core/runtime/front-door-env.d.ts +5 -0
  200. package/dist/core/runtime/front-door-env.js +16 -0
  201. package/dist/core/runtime/front-door.d.ts +1 -5
  202. package/dist/core/runtime/front-door.js +2 -5
  203. package/dist/core/runtime/host.d.ts +1 -1
  204. package/dist/core/runtime/invocation.d.ts +6 -0
  205. package/dist/core/runtime/invocation.js +10 -0
  206. package/dist/core/runtime/kickoff.js +34 -0
  207. package/dist/core/runtime/launch.d.ts +2 -6
  208. package/dist/core/runtime/node-read.js +4 -1
  209. package/dist/core/runtime/nodes.js +2 -0
  210. package/dist/core/runtime/promote.d.ts +1 -0
  211. package/dist/core/runtime/promote.js +3 -1
  212. package/dist/core/runtime/session-cycles.d.ts +6 -0
  213. package/dist/core/runtime/session-cycles.js +15 -1
  214. package/dist/core/runtime/session-visibility.d.ts +17 -8
  215. package/dist/core/runtime/session-visibility.js +19 -10
  216. package/dist/core/runtime/spawn-env.d.ts +9 -1
  217. package/dist/core/runtime/spawn-env.js +62 -2
  218. package/dist/core/runtime/stop-guard.d.ts +2 -2
  219. package/dist/core/runtime/stop-guard.js +2 -2
  220. package/dist/core/runtime/stop-signals.d.ts +1 -1
  221. package/dist/core/runtime/stop-signals.js +2 -2
  222. package/dist/core/runtime/tmux-bindings.js +1 -1
  223. package/dist/core/session-model/session-state.d.ts +4 -3
  224. package/dist/core/session-model/session-state.js +14 -1
  225. package/dist/core/user-settings.d.ts +21 -2
  226. package/dist/core/user-settings.js +32 -11
  227. package/dist/core/worktree.d.ts +15 -45
  228. package/dist/core/worktree.js +59 -261
  229. package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
  230. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
  231. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  232. package/dist/daemon/api/handlers/broker-ops.js +9 -5
  233. package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
  234. package/dist/daemon/api/handlers/feedback-comments.js +207 -0
  235. package/dist/daemon/api/handlers/human.js +24 -20
  236. package/dist/daemon/api/handlers/inbox.d.ts +10 -0
  237. package/dist/daemon/api/handlers/inbox.js +39 -115
  238. package/dist/daemon/api/handlers/profiles.js +11 -29
  239. package/dist/daemon/api/map.d.ts +6 -1
  240. package/dist/daemon/api/map.js +23 -0
  241. package/dist/daemon/api/server.js +3 -1
  242. package/dist/daemon/companion-retire.d.ts +2 -0
  243. package/dist/daemon/companion-retire.js +33 -0
  244. package/dist/daemon/cron-run.js +14 -16
  245. package/dist/daemon/human/finish.d.ts +12 -7
  246. package/dist/daemon/human/finish.js +101 -38
  247. package/dist/daemon/human/sweep.js +5 -1
  248. package/dist/daemon/profile-delete.d.ts +7 -0
  249. package/dist/daemon/profile-delete.js +306 -0
  250. package/dist/daemon/reconcilers/broker-supervision.js +3 -1
  251. package/dist/daemon/review/deliver.js +3 -3
  252. package/dist/daemon/review/finish.d.ts +0 -2
  253. package/dist/daemon/review/finish.js +1 -29
  254. package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
  255. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
  256. package/dist/pi-extensions/canvas-bash-valve.js +19 -5
  257. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  258. package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
  259. package/dist/pi-extensions/canvas-review-boundary.js +18 -9
  260. package/dist/pi-extensions/canvas-stophook.js +4 -4
  261. package/dist/shared/generated-context.js +2 -2
  262. package/dist/types.d.ts +44 -2
  263. package/dist/types.js +7 -2
  264. package/package.json +4 -4
  265. package/runtime.lock.json +5 -33
  266. package/dist/builtin-memory/init.md +0 -38
  267. package/dist/builtin-memory/plan.md +0 -19
  268. package/dist/builtin-memory/spec.md +0 -19
  269. package/dist/commands/human/doc.d.ts +0 -2
  270. package/dist/commands/human/doc.js +0 -91
  271. package/dist/commands/human/html.js +0 -81
  272. package/dist/commands/human/inbox.d.ts +0 -2
  273. package/dist/core/human/__tests__/html-markdown.test.js +0 -52
  274. package/dist/core/human/__tests__/page-render.test.js +0 -66
  275. package/dist/core/human/html-markdown.d.ts +0 -3
  276. package/dist/core/human/html-markdown.js +0 -288
  277. package/dist/core/human/page-render.d.ts +0 -4
  278. package/dist/core/human/page-render.js +0 -105
  279. package/dist/pages/bundle.css +0 -1
  280. package/dist/pages/bundle.js +0 -1778
  281. package/dist/pages/comments.d.ts +0 -72
  282. package/dist/pages/comments.js +0 -176
  283. package/dist/pages/controls.d.ts +0 -98
  284. package/dist/pages/controls.js +0 -261
  285. package/dist/pages/elements/cards.d.ts +0 -28
  286. package/dist/pages/elements/cards.js +0 -915
  287. package/dist/pages/elements/chart.d.ts +0 -23
  288. package/dist/pages/elements/chart.js +0 -664
  289. package/dist/pages/elements/options.d.ts +0 -30
  290. package/dist/pages/elements/options.js +0 -742
  291. package/dist/pages/elements/pages.d.ts +0 -30
  292. package/dist/pages/elements/pages.js +0 -377
  293. package/dist/pages/elements/slot.d.ts +0 -26
  294. package/dist/pages/elements/slot.js +0 -112
  295. package/dist/pages/elements/table.d.ts +0 -36
  296. package/dist/pages/elements/table.js +0 -1043
  297. package/dist/pages/elements/text.d.ts +0 -31
  298. package/dist/pages/elements/text.js +0 -1175
  299. package/dist/pages/entry.d.ts +0 -8
  300. package/dist/pages/entry.js +0 -8
  301. package/dist/pages/host.d.ts +0 -134
  302. package/dist/pages/host.js +0 -165
  303. package/dist/pages/readonly.d.ts +0 -24
  304. package/dist/pages/readonly.js +0 -31
  305. package/dist/pages/register.d.ts +0 -2
  306. package/dist/pages/register.js +0 -3
  307. package/dist/pages/responses.d.ts +0 -37
  308. package/dist/pages/responses.js +0 -56
  309. package/dist/pages/slot-config.d.ts +0 -36
  310. package/dist/pages/slot-config.js +0 -50
  311. package/dist/pages/types.d.ts +0 -98
  312. package/dist/pages/types.js +0 -23
  313. /package/dist/{core/human/__tests__/html-markdown.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
  314. /package/dist/core/{human/__tests__/page-render.test.d.ts → __tests__/plugin-page-components.test.d.ts} +0 -0
@@ -1,161 +1,434 @@
1
- const ELEMENT_RULES = `Element contract
2
- - Put the JSON config in the first direct \`<script type="application/json">\` child of the component element. It must contain one JSON object.
3
- - \`id\` must match \`^[A-Za-z0-9_-]{1,64}$\`, be unique across the page when present, and is required only for response-bearing components. Display-only components do not need an id.
4
- - When a page has two or more response-bearing components, every one needs a nonempty \`config.label\`.
5
- - Config objects and every documented nested object reject unknown fields.`;
6
- const COMMENT_SHAPE = `Comments are \`{id:string (nonempty), text:string, anchor:Anchor}\`. Every response carries \`comments\` as an array. \`Anchor\` is one of \`{kind:"whole"}\`, \`{kind:"option",optionId:string}\`, \`{kind:"row",rowId:string}\`, \`{kind:"column",columnId:string}\`, \`{kind:"card",cardId:string}\`, or \`{kind:"range",start:nonnegative integer,end:nonnegative integer >= start,quote:string}\`; each component accepts only the anchors named below.`;
1
+ import { pageComponentCatalog } from './page-catalog.js';
2
+ /** A product registers a lowercase-hyphen kind; its JSX tag is the PascalCase derivation
3
+ * (`worker-card` → `<WorkerCard>`), which page-catalog carries as the entry `tag`. */
4
+ function productTags(components) {
5
+ return new Map(pageComponentCatalog(components).filter((entry) => !entry.builtin).map((entry) => [entry.kind, entry.tag]));
6
+ }
7
+ const SLOT_RULES = `Component contract
8
+ - Props are the component's config, spread flat as JSX attributes; the component registers itself with the host at mount and enforces its own response shape.
9
+ - \`id\` is required, must match \`^[A-Za-z0-9_-]{1,64}$\`, and must be unique across the page.
10
+ - When a page has two or more response-bearing components, every one needs a nonempty \`label\`.
11
+ - Config objects and every documented nested object reject unknown fields.
12
+ - The page is rendered once at submit to build the ticket manifest, so every prop reaches it whatever its shape — mapped data, a helper component, a value from \`useState\`. Event handlers are dropped, because a manifest is JSON. The user's answer lands in \`response.json\` keyed by this \`id\`.`;
13
+ const COMMENT_SHAPE = `Comments are \`{id:string (nonempty), text:string, anchor:Anchor}\` and qualify the answer itself. \`Anchor\` is \`{kind:"option",optionId:string}\` or \`{kind:"row",rowId:string}\`; each component accepts only the anchors named next. Feedback on rendered page content — including the question text — is given by highlighting it on the page, never authored into a response.`;
14
+ const DISPLAY_RULES = `Display-only: it is not recorded in the page manifest and contributes nothing to the response. Every display component also takes \`className\` for Tailwind utilities, and standard shadcn props otherwise.`;
7
15
  export const BUILTIN_COMPONENT_DOCS = [
8
16
  {
9
- kind: 'crtr-pages',
10
- tag: '<crtr-pages>',
11
- selectionLine: '`<crtr-pages>` — step wrapper. Use when a page is interactive or multi-step; it is always the wrapper for interactive/multi-step pages.',
12
- contract: `\`<crtr-pages>\`
17
+ names: ['Page'],
18
+ group: 'structural',
19
+ selectionLine: '`Page` — the page root. Every module default-exports a component whose root element is `<Page>`; it carries title and subtitle.',
20
+ contract: `\`<Page>\`
21
+
22
+ Structural root. The \`.tsx\` module's default export returns exactly one \`<Page>\`; it takes no \`id\` and contributes no response.
13
23
 
14
- Display-only structural primitive. It takes no config or response.
24
+ Props
25
+ - \`title\` — required nonempty string. The headline the user sees in their inbox and in the terminal summary.
26
+ - \`subtitle?:string\` — one line under the title.
15
27
 
16
- - Use exactly one wrapper when the page has a response-bearing component or more than one step; do not nest wrappers.
17
- - It contains one or more \`<crtr-page>\` elements. A wrapper with no steps is invalid.`,
28
+ Rules
29
+ - Children are page content: registry components, plain elements with Tailwind classes, or \`<Step>\` elements for a multi-step page.
30
+ - Never render submit or dismiss chrome — the host owns it.
31
+
32
+ Example
33
+ \`\`\`jsx
34
+ export default function ShipGate() {
35
+ return (
36
+ <Page title="Ship 0.4.0?" subtitle="Two gates left.">
37
+ <UserQuestion id="lane" label="Release lane" body="The audit closes tomorrow." mode="single"
38
+ options={[{ id: 'now', label: 'Ship now' }, { id: 'hold', label: 'Hold for the audit' }]} />
39
+ </Page>
40
+ );
41
+ }
42
+ \`\`\``,
18
43
  },
19
44
  {
20
- kind: 'crtr-page',
21
- tag: '<crtr-page>',
22
- selectionLine: '`<crtr-page>` — one page step. Use inside `<crtr-pages>` to group a step of an interactive or multi-step page.',
23
- contract: `\`<crtr-page>\`
45
+ names: ['Step'],
46
+ group: 'structural',
47
+ selectionLine: '`Step` — one step of a multi-step page. Use as a direct child of `<Page>` when the user works through content in sequence.',
48
+ contract: `\`<Step>\`
24
49
 
25
- Display-only structural primitive. It takes no config or response.
50
+ Structural. One step of a multi-step page. It takes no \`id\` and contributes no response.
26
51
 
27
- - Every \`<crtr-page>\` is a direct-or-descendant step of the one \`<crtr-pages>\` wrapper; it may not appear outside that wrapper.
28
- - Response-bearing components must be inside a \`<crtr-page>\`. Step order is document order.`,
52
+ Props
53
+ - \`title?:string\` — the step heading.
54
+
55
+ Rules
56
+ - \`<Step>\` elements are children of \`<Page>\`; step order is source order, and the ticket manifest records the step count.
57
+ - A single-step page needs no \`<Step>\` at all — put the content directly in \`<Page>\`.
58
+
59
+ Example
60
+ \`\`\`jsx
61
+ <Page title="Migration plan">
62
+ <Step title="What changes"><Card>…</Card></Step>
63
+ <Step title="Approve"><UserQuestion id="go" label="Proceed?" body="Applies on the next deploy." mode="single" options={lanes} /></Step>
64
+ </Page>
65
+ \`\`\``,
29
66
  },
30
67
  {
31
- kind: 'options',
32
- tag: '<crtr-options>',
33
- selectionLine: '`<crtr-options>` — known-alternative picker. Use when the human picks among known alternatives.',
34
- contract: `\`<crtr-options>\`
68
+ names: ['UserQuestion'],
69
+ group: 'slot',
70
+ selectionLine: '`UserQuestion` — known-alternative picker (wire kind `options`). Use when the user picks among known alternatives.',
71
+ contract: `\`<UserQuestion>\` — wire kind \`options\`
35
72
 
36
73
  Response-bearing. The response is \`{selectedOptionIds:string[], comments:Comment[], freetext?:string}\`.
37
74
 
38
- ${ELEMENT_RULES}
75
+ ${SLOT_RULES}
39
76
 
40
- Config JSON
77
+ Props
78
+ - \`id\` — required component id.
41
79
  - \`options\` — required nonempty array of \`{id:string (nonempty, unique), label:string (nonempty), description?:string}\`.
42
80
  - \`mode\` — required: \`single\` or \`multi\`.
43
- - \`label?:string\` — slot label; required by the multi-response page rule.
44
- - \`allowFreetext?:boolean\`, \`freetextLabel?:string\`, \`freetextPlaceholder?:string\` — optional free-text affordance fields.
81
+ - \`label\` — required nonempty string: the question itself, drawn as the bold first line.
82
+ - \`body\` — required nonempty string: markdown rendered above the choices — the context under the question a label cannot carry. Display-only; contributes nothing to the response.
83
+ - \`allowFreetext?:boolean\`, \`freetextLabel?:string\`, \`freetextPlaceholder?:string\` — adds a final "something else" choice, drawn as one more row with its own control and a field to write in rather than as a note on the question. It is exclusive on a \`single\` question (choosing it clears the selection, and choosing an option clears what was written) and just another box on a \`multi\` one. \`freetextLabel\` names that row, default "Something else".
45
84
 
46
85
  Response rules
47
86
  - \`selectedOptionIds\` contains unique known option ids. A \`single\` response has at most one selected id and requires exactly one selection or nonempty allowed \`freetext\`; a \`multi\` response requires a selection, comment, or nonempty allowed \`freetext\`.
48
87
  - \`freetext\` is accepted only when \`allowFreetext:true\`.
49
- - ${COMMENT_SHAPE} Options accept only \`whole\` and \`option\` anchors; \`optionId\` must name a configured option.`,
88
+ - ${COMMENT_SHAPE} UserQuestion accepts only \`option\` anchors; \`optionId\` must name a configured option.
89
+
90
+ Example
91
+ \`\`\`jsx
92
+ <UserQuestion id="lane" label="Release lane" body="The audit closes tomorrow." mode="single" allowFreetext
93
+ freetextLabel="Something else"
94
+ options={[
95
+ { id: 'now', label: 'Ship now', description: 'Both gates are green.' },
96
+ { id: 'hold', label: 'Hold for the audit' },
97
+ ]} />
98
+ \`\`\``,
50
99
  },
51
100
  {
52
- kind: 'text',
53
- tag: '<crtr-text>',
54
- selectionLine: '`<crtr-text>` — prose field. Use when presenting or collecting prose; use `singleLine` for a subject, name, or URL.',
55
- contract: `\`<crtr-text>\`
101
+ names: ['UserText'],
102
+ group: 'slot',
103
+ selectionLine: '`UserText` — writing surface (wire kind `text`). Use when you need prose back from the user — a value they write, or a draft they hand back revised; use `singleLine` for a subject, name, or URL. Prose the user only reads is a plain element, never this.',
104
+ contract: `\`<UserText>\` — wire kind \`text\`
105
+
106
+ Response-bearing. The response is \`{text:string, edited:boolean}\`: the text the user hands back, and whether they changed what you drafted.
56
107
 
57
- Response-bearing. The response is \`{text:string, edited:boolean, comments:Comment[]}\`.
108
+ It collects prose, it does not present it. Text the user only reads is a plain element with Tailwind classes — \`<p className="whitespace-pre-wrap">\` — which renders the same and asks nothing of them. Reach for it only when the answer you need is the string left in the field. Feedback on the rendered text is given by highlighting it on the page, not authored into the response.
58
109
 
59
- ${ELEMENT_RULES}
110
+ ${SLOT_RULES}
60
111
 
61
- Config JSON
112
+ Props
113
+ - \`id\` — required component id.
62
114
  - \`initialText\` — required string, including an empty string.
63
- - \`label?:string\` — slot label; required by the multi-response page rule.
64
- - \`editable?:boolean\`, \`placeholder?:string\`, \`singleLine?:boolean\` — optional presentation/input settings.
65
- - \`singleLine:true\` is mutually exclusive with \`editable:false\`: a single-line field is always editable.
115
+ - \`label?:string\` — the component's label; required by the multi-response page rule.
116
+ - \`placeholder?:string\` — the hint shown while the field is empty.
117
+ - \`singleLine?:boolean\` — swaps the writing surface for one compact form field.
66
118
 
67
- Response rules
68
- - ${COMMENT_SHAPE} Text accepts only \`whole\` and \`range\` anchors.`,
119
+ The field is always writable; there is no read-only mode, and the config rejects an \`editable\` prop.
120
+
121
+ Example
122
+ \`\`\`jsx
123
+ <UserText id="subject" label="Subject line" singleLine initialText="Following up on the audit" />
124
+ \`\`\``,
69
125
  },
70
126
  {
71
- kind: 'table',
72
- tag: '<crtr-table>',
73
- selectionLine: '`<crtr-table>` — structured rows and columns. Use when the human needs to inspect or select tabular data.',
74
- contract: `\`<crtr-table>\`
127
+ names: ['UserTable'],
128
+ group: 'slot',
129
+ selectionLine: '`UserTable` — selectable rows and columns (wire kind `table`). Use when the user inspects or selects tabular data; for a table the user only reads, `Table` is lighter.',
130
+ contract: `\`<UserTable>\` — wire kind \`table\`
75
131
 
76
132
  Display-only when both selection modes are \`none\`; response-bearing when \`rowSelect\` or \`columnSelect\` is \`single\` or \`multi\`. Its response when selectable is \`{selectedRowIds:string[], selectedColumnIds:string[], comments:Comment[]}\`.
77
133
 
78
- ${ELEMENT_RULES}
134
+ ${SLOT_RULES}
79
135
 
80
- Config JSON
136
+ Props
137
+ - \`id\` — required component id.
81
138
  - \`columns\` — required nonempty array of \`{id:string (nonempty, unique), label:string (nonempty), align?:"left"|"right", mono?:boolean}\`.
82
- - \`rows?:\` array of \`{id:string (nonempty, unique), cells:Record<string,string|number|boolean|null>}\`.
83
- - \`source?:string\` — path to a local JSON file containing the rows array. At submit it is read, validated as \`rows\`, and embedded; the stored ticket is self-contained and elements never see \`source\`. \`rows\` and \`source\` are mutually exclusive.
139
+ - \`rows?:\` array of \`{id:string (nonempty, unique), cells:Record<string,string|number|boolean|null>}\`. Rows are inline — map your data into this array in the module.
84
140
  - \`rowSelect?:"none"|"single"|"multi"\` — defaults to \`none\`.
85
141
  - \`columnSelect?:"none"|"single"|"multi"\` — defaults to \`none\`.
86
- - \`label?:string\` — slot label; required by the multi-response page rule when the table is selectable.
142
+ - \`label?:string\` — the component's label; required by the multi-response page rule when the table is selectable.
143
+ - \`body?:string\` — optional markdown rendered above the table. Display-only; it contributes nothing to the response.
87
144
 
88
145
  Response rules when selectable
89
146
  - Selected row and column ids are unique and must name configured ids. \`none\` permits no corresponding selected ids; \`single\` permits at most one.
90
- - ${COMMENT_SHAPE} Tables accept only \`whole\`, \`row\`, and \`column\` anchors. Row and column anchors must name configured ids.`,
147
+ - ${COMMENT_SHAPE} UserTable accepts only \`row\` anchors, which must name configured row ids.
148
+
149
+ Example
150
+ \`\`\`jsx
151
+ <UserTable id="picks" label="Which findings do we fix?" rowSelect="multi"
152
+ columns={[{ id: 'finding', label: 'Finding' }, { id: 'severity', label: 'Severity' }]}
153
+ rows={findings.map((f) => ({ id: f.id, cells: { finding: f.title, severity: f.severity } }))} />
154
+ \`\`\``,
91
155
  },
92
156
  {
93
- kind: 'cards',
94
- tag: '<crtr-cards>',
95
- selectionLine: '`<crtr-cards>` — visual item picker. Use when the human chooses one or more rich, card-shaped alternatives.',
96
- contract: `\`<crtr-cards>\`
157
+ names: ['UserCards'],
158
+ group: 'slot',
159
+ selectionLine: '`UserCards` — visual item picker (wire kind `cards`). Use when the user chooses among rich, card-shaped alternatives.',
160
+ contract: `\`<UserCards>\` — wire kind \`cards\`
97
161
 
98
- Response-bearing. The response is \`{selectedCardIds:string[], comments:Comment[]}\`.
162
+ Response-bearing. The response is \`{selectedCardIds:string[]}\`. Feedback on a card's content is given by highlighting it on the page, not authored into the response.
99
163
 
100
- ${ELEMENT_RULES}
164
+ ${SLOT_RULES}
101
165
 
102
- Config JSON
103
- - \`cards?:\` array of \`{id:string (nonempty, unique), title:string (nonempty), subtitle?:string, body?:string, tag?:string}\`.
104
- - \`source?:string\` — path to a local JSON file containing the cards array. At submit it is read, validated as \`cards\`, and embedded; the stored ticket is self-contained and elements never see \`source\`. \`cards\` and \`source\` are mutually exclusive.
166
+ Props
167
+ - \`id\` — required component id.
168
+ - \`cards?:\` array of \`{id:string (nonempty, unique), title:string (nonempty), subtitle?:string, body?:string, tag?:string}\`. Cards are inline — map your data into this array in the module.
105
169
  - \`mode\` — required: \`single\` or \`multi\`.
106
170
  - \`columns?:2|3\` — optional layout column count.
107
- - \`label?:string\` — slot label; required by the multi-response page rule.
171
+ - \`label?:string\` — the component's label; required by the multi-response page rule.
172
+ - \`body?:string\` — optional markdown rendered above the cards. Display-only; it contributes nothing to the response.
108
173
 
109
174
  Response rules
110
175
  - \`selectedCardIds\` contains unique configured card ids. \`single\` permits at most one selected card.
111
- - ${COMMENT_SHAPE} Cards accept only \`whole\` and \`card\` anchors. Card anchors must name configured ids.`,
176
+
177
+ Example
178
+ \`\`\`jsx
179
+ <UserCards id="candidate" label="Who do we interview next?" mode="single" columns={3}
180
+ cards={[{ id: 'a', title: 'Dana Whitfield', subtitle: 'Staff eng', tag: 'referral' }]} />
181
+ \`\`\``,
112
182
  },
113
183
  {
114
- kind: 'chart',
115
- tag: '<crtr-chart>',
116
- selectionLine: '`<crtr-chart>` — data visualization. Use when a chart communicates a trend or comparison better than prose or a table.',
117
- contract: `\`<crtr-chart>\`
184
+ names: ['Chart'],
185
+ group: 'slot',
186
+ selectionLine: '`Chart` — data visualization (wire kind `chart`, display-only). Use when a trend or comparison reads better than prose or a table.',
187
+ contract: `\`<Chart>\` — wire kind \`chart\`
118
188
 
119
- Display-only; it has no response.
189
+ Display-only; it has no response. It still takes an \`id\` when you want the chart recorded in the ticket manifest.
120
190
 
121
- ${ELEMENT_RULES}
122
-
123
- Config JSON
191
+ Props
124
192
  - \`kind\` — required: \`line\`, \`bar\`, or \`area\`.
125
193
  - \`x\` — required nonempty string naming the x field.
126
194
  - \`y\` — required nonempty array of \`{field:string (nonempty), label?:string}\` series.
127
- - \`title?:string\`, \`data?:\` array of JSON row objects — optional display inputs.
128
- - \`source?:string\` — path to a local JSON file containing the \`data\` array. At submit it is read, validated as \`data\`, and embedded; the stored ticket is self-contained and elements never see \`source\`. \`data\` and \`source\` are mutually exclusive.`,
195
+ - \`title?:string\`, \`data?:\` array of JSON row objects. Rows are inline — build them in the module.
196
+ - \`id?:string\` — component id, subject to the same id rules as a response-bearing component when present.
197
+
198
+ Example
199
+ \`\`\`jsx
200
+ <Chart kind="line" title="Build minutes" x="day"
201
+ y={[{ field: 'minutes', label: 'CI minutes' }]}
202
+ data={days.map((d) => ({ day: d.label, minutes: d.minutes }))} />
203
+ \`\`\``,
204
+ },
205
+ {
206
+ names: ['Card', 'CardHeader', 'CardTitle', 'CardDescription', 'CardContent', 'CardFooter'],
207
+ group: 'display',
208
+ selectionLine: '`Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` — the standard surface for grouping a block of page content.',
209
+ contract: `\`<Card>\` family — \`Card\`, \`CardHeader\`, \`CardTitle\`, \`CardDescription\`, \`CardContent\`, \`CardFooter\`
210
+
211
+ ${DISPLAY_RULES}
212
+
213
+ Props
214
+ - All six take \`className\` and children only; the nesting carries the meaning.
215
+ - Order inside \`<Card>\`: \`<CardHeader>\` (holding \`<CardTitle>\` and optional \`<CardDescription>\`), then \`<CardContent>\`, then optional \`<CardFooter>\`.
216
+
217
+ Example
218
+ \`\`\`jsx
219
+ <Card>
220
+ <CardHeader><CardTitle>Release gates</CardTitle><CardDescription>Two left.</CardDescription></CardHeader>
221
+ <CardContent><Chart kind="bar" x="gate" y={[{ field: 'minutes' }]} data={gates} /></CardContent>
222
+ </Card>
223
+ \`\`\``,
224
+ },
225
+ {
226
+ names: ['Badge'],
227
+ group: 'display',
228
+ selectionLine: '`Badge` — small status or category chip.',
229
+ contract: `\`<Badge>\`
230
+
231
+ ${DISPLAY_RULES}
232
+
233
+ Props
234
+ - \`variant?:"default"|"secondary"|"destructive"|"outline"\`.
235
+ - \`className\`, children.
236
+
237
+ Example
238
+ \`\`\`jsx
239
+ <Badge variant="destructive">blocked</Badge>
240
+ \`\`\``,
241
+ },
242
+ {
243
+ names: ['Button'],
244
+ group: 'display',
245
+ selectionLine: '`Button` — an in-page control. Never a submit: the host owns submit and dismiss.',
246
+ contract: `\`<Button>\`
247
+
248
+ ${DISPLAY_RULES} A page never renders its own submit or dismiss — use it for in-page interaction only (revealing detail, switching a local view).
249
+
250
+ Props — this Button does NOT take shadcn's \`variant\`; a \`variant\` prop is silently ignored.
251
+ - \`tone?:"brand"|"neutral"|"danger"\` — what the action means. Unstated, it resolves to \`brand\` on a solid button and \`neutral\` at every other weight.
252
+ - \`weight?:"solid"|"outline"|"quiet"|"link"\` — how loudly it reads. Default \`solid\`. \`tone="danger"\` has no \`link\` weight — that pair warns and renders untoned.
253
+ - \`size?:"xs"|"sm"|"default"|"lg"\` (icon-only circles: \`"icon-xs"|"icon-sm"|"icon"|"icon-lg"\`). Default \`default\`.
254
+ - \`width?:"auto"|"full"\`. Default \`auto\`.
255
+ - \`loading?:boolean\`, \`onClick?:(event) => void\`, \`disabled?:boolean\`, \`className\`, children.
256
+
257
+ Example
258
+ \`\`\`jsx
259
+ <Button tone="neutral" weight="outline" size="sm" onClick={() => setShowAll(true)}>Show all 12</Button>
260
+ \`\`\``,
261
+ },
262
+ {
263
+ names: ['Table', 'TableHeader', 'TableBody', 'TableRow', 'TableHead', 'TableCell', 'TableCaption'],
264
+ group: 'display',
265
+ selectionLine: '`Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`, `TableCaption` — read-only tabular layout. Use `UserTable` when the user must select rows or columns.',
266
+ contract: `\`<Table>\` family — \`Table\`, \`TableHeader\`, \`TableBody\`, \`TableRow\`, \`TableHead\`, \`TableCell\`, \`TableCaption\`
267
+
268
+ ${DISPLAY_RULES} It is layout only: nothing in it is selectable and nothing reaches the response. For selection, use \`UserTable\`.
269
+
270
+ Props
271
+ - All take \`className\` and children. \`TableHead\` and \`TableCell\` additionally take \`colSpan\`.
272
+ - Structure: \`<Table>\` wraps optional \`<TableCaption>\`, \`<TableHeader>\` (rows of \`<TableHead>\`), and \`<TableBody>\` (rows of \`<TableCell>\`).
273
+
274
+ Example
275
+ \`\`\`jsx
276
+ <Table>
277
+ <TableHeader><TableRow><TableHead>Gate</TableHead><TableHead>State</TableHead></TableRow></TableHeader>
278
+ <TableBody>{gates.map((g) => (
279
+ <TableRow key={g.id}><TableCell>{g.name}</TableCell><TableCell>{g.state}</TableCell></TableRow>
280
+ ))}</TableBody>
281
+ </Table>
282
+ \`\`\``,
283
+ },
284
+ {
285
+ names: ['Tabs', 'TabsList', 'TabsTrigger', 'TabsContent'],
286
+ group: 'display',
287
+ selectionLine: '`Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` — parallel views of the same subject behind one control.',
288
+ contract: `\`<Tabs>\` family — \`Tabs\`, \`TabsList\`, \`TabsTrigger\`, \`TabsContent\`
289
+
290
+ ${DISPLAY_RULES} Which tab the user opened is not part of the response.
291
+
292
+ Props
293
+ - \`Tabs\`: \`defaultValue?:string\`, \`value?:string\`, \`onValueChange?:(value:string) => void\`, \`className\`.
294
+ - \`TabsTrigger\` and \`TabsContent\`: \`value\` — required, and the two match by that value.
295
+ - \`TabsList\`: \`className\` and children.
296
+
297
+ Example
298
+ \`\`\`jsx
299
+ <Tabs defaultValue="diff">
300
+ <TabsList><TabsTrigger value="diff">Diff</TabsTrigger><TabsTrigger value="risk">Risk</TabsTrigger></TabsList>
301
+ <TabsContent value="diff">…</TabsContent>
302
+ <TabsContent value="risk">…</TabsContent>
303
+ </Tabs>
304
+ \`\`\``,
305
+ },
306
+ {
307
+ names: ['Separator'],
308
+ group: 'display',
309
+ selectionLine: '`Separator` — a rule between sections.',
310
+ contract: `\`<Separator>\`
311
+
312
+ ${DISPLAY_RULES}
313
+
314
+ Props
315
+ - \`orientation?:"horizontal"|"vertical"\` — defaults to \`horizontal\`.
316
+ - \`className\`.
317
+
318
+ Example
319
+ \`\`\`jsx
320
+ <Separator className="my-4" />
321
+ \`\`\``,
322
+ },
323
+ {
324
+ names: ['Alert', 'AlertTitle', 'AlertDescription'],
325
+ group: 'display',
326
+ selectionLine: '`Alert`, `AlertTitle`, `AlertDescription` — a callout for the one thing the user must not miss.',
327
+ contract: `\`<Alert>\` family — \`Alert\`, \`AlertTitle\`, \`AlertDescription\`
328
+
329
+ ${DISPLAY_RULES}
330
+
331
+ Props
332
+ - \`Alert\`: \`variant?:"default"|"destructive"\`, \`className\`.
333
+ - \`AlertTitle\` and \`AlertDescription\`: \`className\` and children.
334
+
335
+ Example
336
+ \`\`\`jsx
337
+ <Alert variant="destructive">
338
+ <AlertTitle>Migration is irreversible</AlertTitle>
339
+ <AlertDescription>The old rows are dropped, not archived.</AlertDescription>
340
+ </Alert>
341
+ \`\`\``,
342
+ },
343
+ {
344
+ names: ['Progress'],
345
+ group: 'display',
346
+ selectionLine: '`Progress` — a completion bar for a known ratio.',
347
+ contract: `\`<Progress>\`
348
+
349
+ ${DISPLAY_RULES}
350
+
351
+ Props
352
+ - \`value?:number\` — 0 to 100.
353
+ - \`className\`.
354
+
355
+ Example
356
+ \`\`\`jsx
357
+ <Progress value={Math.round((done / total) * 100)} />
358
+ \`\`\``,
359
+ },
360
+ {
361
+ names: ['Accordion', 'AccordionItem', 'AccordionTrigger', 'AccordionContent'],
362
+ group: 'display',
363
+ selectionLine: '`Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent` — supporting detail the user opens only if they want it.',
364
+ contract: `\`<Accordion>\` family — \`Accordion\`, \`AccordionItem\`, \`AccordionTrigger\`, \`AccordionContent\`
365
+
366
+ ${DISPLAY_RULES}
367
+
368
+ Props
369
+ - \`Accordion\`: \`type\` — required: \`single\` or \`multiple\`; \`collapsible?:boolean\`, \`defaultValue?:string|string[]\`, \`className\`.
370
+ - \`AccordionItem\`: \`value\` — required, unique within the accordion.
371
+ - \`AccordionTrigger\` and \`AccordionContent\`: \`className\` and children.
372
+
373
+ Example
374
+ \`\`\`jsx
375
+ <Accordion type="single" collapsible>
376
+ <AccordionItem value="why">
377
+ <AccordionTrigger>Why this is blocked</AccordionTrigger>
378
+ <AccordionContent>The audit job has not run since Tuesday.</AccordionContent>
379
+ </AccordionItem>
380
+ </Accordion>
381
+ \`\`\``,
129
382
  },
130
383
  ];
131
- const docsByKind = new Map(BUILTIN_COMPONENT_DOCS.map((doc) => [doc.kind, doc]));
132
- /** Compact catalog rows shared by `human -h` and `human html list`. */
133
- export const BUILTIN_COMPONENT_SELECTION_LINES = BUILTIN_COMPONENT_DOCS.map((doc) => doc.selectionLine);
134
- export function componentDoc(kind) {
135
- return docsByKind.get(kind);
384
+ const docsByName = new Map(BUILTIN_COMPONENT_DOCS.flatMap((doc) => doc.names.map((name) => [name, doc])));
385
+ /** The structural and response-bearing rows — the ones that decide what a page can ask. `human -h` carries these;
386
+ * the display set is one line there and the full catalog is `human components list`. */
387
+ export const CORE_COMPONENT_SELECTION_LINES = [...BUILTIN_COMPONENT_DOCS.filter((doc) => doc.group !== 'display').map((doc) => doc.selectionLine), '`usePageHost` — reserved host hook. Its product-supplied members are opaque to crouter; consult the owning product documentation.'];
388
+ export function componentDoc(name) {
389
+ return docsByName.get(name);
136
390
  }
137
- export function componentKinds(productKinds) {
138
- return [...BUILTIN_COMPONENT_DOCS.map((doc) => doc.kind), ...productKinds];
391
+ export function componentNames(productComponents) {
392
+ return [...BUILTIN_COMPONENT_DOCS.flatMap((doc) => [...doc.names]), ...productTags(productComponents).values()];
139
393
  }
394
+ const GROUP_HEADINGS = {
395
+ structural: 'Structural — every page is built from these.',
396
+ slot: 'Response — each is recorded in the page manifest under its wire kind, and their answers are the page response.',
397
+ display: 'Display — shadcn components with their standard props, plus `className` for Tailwind. They contribute nothing to the response.',
398
+ };
140
399
  export function renderComponentCatalog(productComponents) {
141
- const lines = [`<components count="${BUILTIN_COMPONENT_DOCS.length + productComponents.length}">`, ...BUILTIN_COMPONENT_SELECTION_LINES];
400
+ const builtinCount = BUILTIN_COMPONENT_DOCS.reduce((total, doc) => total + doc.names.length, 0);
401
+ const lines = [`<components count="${builtinCount + productComponents.length}">`];
402
+ for (const group of ['structural', 'slot', 'display']) {
403
+ lines.push(GROUP_HEADINGS[group], ...BUILTIN_COMPONENT_DOCS.filter((doc) => doc.group === group).map((doc) => doc.selectionLine), '');
404
+ }
405
+ const tags = productTags(productComponents);
406
+ if (productComponents.length > 0)
407
+ lines.push('Product-registered — registry components this product ships. Write each one under the tag shown; a `(kind …)` suffix names its lowercase-hyphen wire kind.');
408
+ else
409
+ lines.pop();
142
410
  for (const component of productComponents) {
411
+ const tag = tags.get(component.kind) ?? component.kind;
412
+ const label = tag === component.kind ? `\`${tag}\`` : `\`${tag}\` (kind \`${component.kind}\`)`;
143
413
  if (component.description === undefined && component.useWhen === undefined) {
144
- lines.push('', `\`${component.kind}\``, component.display === true ? 'product-registered; display-only' : 'product-registered');
414
+ lines.push(`${label} — ${component.display === true ? 'product-registered; display-only' : 'product-registered'}`);
145
415
  continue;
146
416
  }
147
417
  const details = [component.description, component.useWhen === undefined ? undefined : `${component.useWhen[0].toUpperCase()}${component.useWhen.slice(1)}`, ...(component.display === true ? ['Display-only'] : [])].filter((detail) => detail !== undefined);
148
- lines.push('', `\`${component.kind}\` — ${details.join('. ')}`);
418
+ lines.push(`${label} — ${details.join('. ')}`);
149
419
  }
150
420
  lines.push('</components>');
151
421
  return lines.join('\n');
152
422
  }
153
- export function renderComponentContract(kind, productComponents) {
154
- const builtin = componentDoc(kind);
423
+ export function renderComponentContract(name, productComponents) {
424
+ const builtin = componentDoc(name);
155
425
  if (builtin !== undefined)
156
426
  return builtin.contract;
157
- const productComponent = productComponents.find((component) => component.kind === kind);
158
- if (productComponent !== undefined)
159
- return `\`${kind}\`\n\n${productComponent.doc ?? `product-registered${productComponent.display === true ? '; display-only' : '; config is passed through unvalidated (unvalidated: true)'} — see the product's own docs`}`;
160
- return undefined;
427
+ const tags = productTags(productComponents);
428
+ const productComponent = productComponents.find((component) => component.kind === name || tags.get(component.kind) === name);
429
+ if (productComponent === undefined)
430
+ return undefined;
431
+ const tag = tags.get(productComponent.kind) ?? productComponent.kind;
432
+ const heading = `\`<${tag}>\` — product-registered, wire kind \`${productComponent.kind}\``;
433
+ return `${heading}\n\n${productComponent.doc ?? `product-registered${productComponent.display === true ? '; display-only' : '; config is passed through unvalidated (unvalidated: true)'} — see the product's own docs`}`;
161
434
  }
@@ -1,17 +1,31 @@
1
- import type { PageManifest } from './page-schema.js';
2
- /** The authored page source, colocated with its derived manifest in the bridge node directory. */
3
- export declare function pagePath(dir: string, dialect: PageManifest['dialect']): string;
1
+ /** The authored page source path, derived from the persisted dialect. */
2
+ export declare function pagePath(dir: string, dialect?: 'jsx' | 'html'): string;
3
+ /** The compiled module the Gateway evaluates for JSX pages. */
4
+ export declare function pageModulePath(dir: string): string;
4
5
  export declare function pageManifestPath(dir: string): string;
5
6
  export declare function reviewPath(dir: string): string;
6
7
  export declare function responsePath(dir: string): string;
7
8
  export declare function progressPath(dir: string): string;
8
9
  export declare function claimPath(dir: string): string;
10
+ export declare function replyRoutePath(dir: string): string;
11
+ export interface TicketReplyRoute {
12
+ bridge_node_id: string;
13
+ }
14
+ /** A reply route exists only when completing the ticket must finish a human
15
+ * bridge and fan its result back to subscribers. Its absence is the complete
16
+ * representation of a standalone page. */
17
+ export declare function readTicketReplyRoute(dir: string): TicketReplyRoute | null;
18
+ export declare function bindTicketReplyRoute(dir: string, bridgeNodeId: string): void;
9
19
  export type InteractionState = 'pending' | 'claimed' | 'resolved' | 'missing';
10
20
  export declare function interactionState(dir: string): InteractionState;
11
21
  export declare function isResolved(dir: string): boolean;
12
22
  export declare function isClaimed(dir: string): boolean;
13
23
  /** Resolve and verify a ticket directory without a registry. */
14
24
  export declare function requireTicket(dir: string): string;
25
+ /** Replace one file's contents in a single rename, so a concurrent reader —
26
+ * the tmux inbox watcher, or crtrd serving a ticket read — sees either the old
27
+ * bytes or the new ones and never a truncated file mid-write. */
28
+ export declare function atomicWriteText(path: string, contents: string): void;
15
29
  export declare function atomicWriteJson(path: string, value: unknown): void;
16
30
  /** Publish one immutable JSON record without replacing an existing winner. */
17
31
  export declare function publishJsonExclusive(path: string, value: unknown): boolean;
@@ -1,12 +1,37 @@
1
- import { existsSync, linkSync, mkdirSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, linkSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
- /** The authored page source, colocated with its derived manifest in the bridge node directory. */
4
- export function pagePath(dir, dialect) { return `${dir}/page.${dialect}`; }
3
+ /** The authored page source path, derived from the persisted dialect. */
4
+ export function pagePath(dir, dialect = 'jsx') { return `${dir}/page.${dialect === 'jsx' ? 'tsx' : 'html'}`; }
5
+ /** The compiled module the Gateway evaluates for JSX pages. */
6
+ export function pageModulePath(dir) { return `${dir}/page.js`; }
5
7
  export function pageManifestPath(dir) { return `${dir}/page.json`; }
6
8
  export function reviewPath(dir) { return `${dir}/review.json`; }
7
9
  export function responsePath(dir) { return `${dir}/response.json`; }
8
10
  export function progressPath(dir) { return `${dir}/progress.json`; }
9
11
  export function claimPath(dir) { return `${dir}/claim.json`; }
12
+ export function replyRoutePath(dir) { return `${dir}/reply-route.json`; }
13
+ /** A reply route exists only when completing the ticket must finish a human
14
+ * bridge and fan its result back to subscribers. Its absence is the complete
15
+ * representation of a standalone page. */
16
+ export function readTicketReplyRoute(dir) {
17
+ const path = replyRoutePath(dir);
18
+ if (!existsSync(path))
19
+ return null;
20
+ let raw;
21
+ try {
22
+ raw = JSON.parse(readFileSync(path, 'utf8'));
23
+ }
24
+ catch {
25
+ throw new Error(`invalid ticket reply route: ${path}`);
26
+ }
27
+ const bridgeNodeId = raw?.bridge_node_id;
28
+ if (typeof bridgeNodeId !== 'string' || bridgeNodeId === '')
29
+ throw new Error(`invalid ticket reply route: ${path}`);
30
+ return { bridge_node_id: bridgeNodeId };
31
+ }
32
+ export function bindTicketReplyRoute(dir, bridgeNodeId) {
33
+ atomicWriteJson(replyRoutePath(dir), { bridge_node_id: bridgeNodeId });
34
+ }
10
35
  export function interactionState(dir) {
11
36
  if (!existsSync(pageManifestPath(dir)) && !existsSync(reviewPath(dir)))
12
37
  return 'missing';
@@ -24,12 +49,18 @@ export function requireTicket(dir) {
24
49
  }
25
50
  return canonicalDir;
26
51
  }
27
- export function atomicWriteJson(path, value) {
52
+ /** Replace one file's contents in a single rename, so a concurrent reader —
53
+ * the tmux inbox watcher, or crtrd serving a ticket read — sees either the old
54
+ * bytes or the new ones and never a truncated file mid-write. */
55
+ export function atomicWriteText(path, contents) {
28
56
  const tmp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
29
57
  mkdirSync(dirname(path), { recursive: true });
30
- writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
58
+ writeFileSync(tmp, contents, { mode: 0o600 });
31
59
  renameSync(tmp, path);
32
60
  }
61
+ export function atomicWriteJson(path, value) {
62
+ atomicWriteText(path, `${JSON.stringify(value, null, 2)}\n`);
63
+ }
33
64
  /** Publish one immutable JSON record without replacing an existing winner. */
34
65
  export function publishJsonExclusive(path, value) {
35
66
  const tmp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;