@crouton-kit/crouter 0.3.27 → 0.3.29

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 (343) hide show
  1. package/dist/build-root.js +4 -2
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
  5. package/dist/builtin-memory/crouter-development/personas.md +1 -1
  6. package/dist/builtin-memory/crouter-development/plugins.md +20 -18
  7. package/dist/builtin-memory/design.md +1 -1
  8. package/dist/builtin-memory/development.md +1 -1
  9. package/dist/builtin-memory/internal/INDEX.md +1 -1
  10. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  12. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  13. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  14. package/dist/builtin-memory/planning.md +1 -1
  15. package/dist/builtin-memory/spec.md +6 -4
  16. package/dist/builtin-personas/orchestration-kernel.md +4 -5
  17. package/dist/builtin-personas/runtime-base.md +8 -4
  18. package/dist/builtin-personas/spec/PERSONA.md +8 -4
  19. package/dist/builtin-personas/spec/orchestrator.md +5 -3
  20. package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
  21. package/dist/builtin-views/canvas/core.mjs +586 -0
  22. package/dist/builtin-views/canvas/text.mjs +58 -0
  23. package/dist/builtin-views/canvas/tui.mjs +165 -0
  24. package/dist/builtin-views/canvas/web.jsx +120 -0
  25. package/dist/builtin-views/git-pr/core.mjs +673 -0
  26. package/dist/builtin-views/git-pr/text.mjs +84 -0
  27. package/dist/builtin-views/git-pr/tui.mjs +302 -0
  28. package/dist/builtin-views/git-pr/web.jsx +216 -0
  29. package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
  30. package/dist/builtin-views/inbox/core.mjs +1273 -0
  31. package/dist/builtin-views/inbox/text.mjs +73 -0
  32. package/dist/builtin-views/inbox/tui.mjs +312 -0
  33. package/dist/builtin-views/inbox/web.jsx +188 -0
  34. package/dist/builtin-views/linkedin/core.mjs +906 -0
  35. package/dist/builtin-views/linkedin/text.mjs +69 -0
  36. package/dist/builtin-views/linkedin/tui.mjs +427 -0
  37. package/dist/builtin-views/linkedin/web.jsx +206 -0
  38. package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
  39. package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
  40. package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
  41. package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
  42. package/dist/cli.js +3 -3
  43. package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
  44. package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
  45. package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
  46. package/dist/clients/attach/__tests__/git-info.test.js +31 -0
  47. package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
  48. package/dist/clients/attach/__tests__/reconnect-giveup.test.js +30 -0
  49. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  50. package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
  51. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  52. package/dist/clients/attach/attach-cmd.js +445 -41
  53. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  54. package/dist/clients/attach/auth-pickers.js +194 -0
  55. package/dist/clients/attach/canvas-panels.d.ts +12 -0
  56. package/dist/clients/attach/canvas-panels.js +78 -0
  57. package/dist/clients/attach/chat-view.d.ts +27 -2
  58. package/dist/clients/attach/chat-view.js +98 -9
  59. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  60. package/dist/clients/attach/clipboard-image.js +150 -26
  61. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  62. package/dist/clients/attach/clipboard-text.js +39 -0
  63. package/dist/clients/attach/config-load.d.ts +54 -7
  64. package/dist/clients/attach/config-load.js +99 -3
  65. package/dist/clients/attach/context-message.d.ts +12 -0
  66. package/dist/clients/attach/context-message.js +72 -0
  67. package/dist/clients/attach/git-info.d.ts +20 -0
  68. package/dist/clients/attach/git-info.js +44 -0
  69. package/dist/clients/attach/graph-overlay.d.ts +43 -0
  70. package/dist/clients/attach/graph-overlay.js +306 -0
  71. package/dist/clients/attach/input-controller.d.ts +81 -13
  72. package/dist/clients/attach/input-controller.js +307 -67
  73. package/dist/clients/attach/pickers.d.ts +58 -0
  74. package/dist/clients/attach/pickers.js +171 -0
  75. package/dist/clients/attach/slash-commands.d.ts +44 -1
  76. package/dist/clients/attach/slash-commands.js +250 -27
  77. package/dist/clients/attach/titled-editor.d.ts +33 -0
  78. package/dist/clients/attach/titled-editor.js +84 -0
  79. package/dist/clients/attach/view-socket.d.ts +37 -2
  80. package/dist/clients/attach/view-socket.js +131 -7
  81. package/dist/clients/web/dev-server.d.ts +7 -0
  82. package/dist/clients/web/dev-server.js +59 -0
  83. package/dist/clients/web/events.d.ts +14 -0
  84. package/dist/clients/web/events.js +151 -0
  85. package/dist/clients/web/server.d.ts +18 -0
  86. package/dist/clients/web/server.js +450 -0
  87. package/dist/clients/web/web-cmd.d.ts +2 -0
  88. package/dist/clients/web/web-cmd.js +120 -0
  89. package/dist/commands/canvas.js +1 -2
  90. package/dist/commands/chord.js +1 -1
  91. package/dist/commands/dashboard.js +5 -1
  92. package/dist/commands/human/prompts.js +3 -3
  93. package/dist/commands/human/queue.d.ts +17 -0
  94. package/dist/commands/human/queue.js +111 -4
  95. package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
  96. package/dist/commands/memory/lint.d.ts +5 -4
  97. package/dist/commands/memory/lint.js +23 -7
  98. package/dist/commands/memory/read.js +2 -2
  99. package/dist/commands/memory/shared.d.ts +7 -1
  100. package/dist/commands/memory/shared.js +18 -3
  101. package/dist/commands/memory/write.js +20 -7
  102. package/dist/commands/memory.js +4 -4
  103. package/dist/commands/node.d.ts +1 -1
  104. package/dist/commands/node.js +64 -65
  105. package/dist/commands/pkg.js +1 -2
  106. package/dist/commands/revive.js +11 -3
  107. package/dist/commands/sys/feedback.d.ts +1 -0
  108. package/dist/commands/sys/feedback.js +163 -0
  109. package/dist/commands/sys/sync.d.ts +1 -0
  110. package/dist/commands/sys/sync.js +187 -0
  111. package/dist/commands/sys.js +4 -2
  112. package/dist/commands/view-cycle.js +2 -2
  113. package/dist/commands/view-list.js +8 -8
  114. package/dist/commands/view-new.js +21 -17
  115. package/dist/commands/view-pick.js +1 -1
  116. package/dist/commands/view-run.js +35 -14
  117. package/dist/commands/view.js +8 -6
  118. package/dist/commands/workspace.d.ts +2 -0
  119. package/dist/commands/workspace.js +161 -0
  120. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  121. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  122. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  123. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  124. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  125. package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
  126. package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
  127. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  128. package/dist/core/__tests__/close.test.js +14 -20
  129. package/dist/core/__tests__/context-intro.test.js +19 -19
  130. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  131. package/dist/core/__tests__/draw-style.test.js +1 -1
  132. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  133. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  134. package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
  135. package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
  136. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  137. package/dist/core/__tests__/focuses.test.js +25 -13
  138. package/dist/core/__tests__/fork.test.js +22 -5
  139. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  140. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  141. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  142. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  143. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  144. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  145. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  146. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  147. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  148. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  149. package/dist/core/__tests__/helpers/harness.js +19 -36
  150. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  151. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  152. package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
  153. package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
  154. package/dist/core/__tests__/lifecycle.test.js +1 -19
  155. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  156. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  157. package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
  158. package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
  159. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  160. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  161. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  162. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  163. package/dist/core/__tests__/reset.test.js +19 -79
  164. package/dist/core/__tests__/steer-note.test.js +9 -7
  165. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  166. package/dist/core/bootstrap.d.ts +0 -3
  167. package/dist/core/bootstrap.js +1 -143
  168. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  169. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  170. package/dist/core/canvas/browse/app.js +131 -8
  171. package/dist/core/canvas/browse/model.d.ts +23 -5
  172. package/dist/core/canvas/browse/model.js +68 -13
  173. package/dist/core/canvas/browse/render.d.ts +7 -1
  174. package/dist/core/canvas/browse/render.js +178 -47
  175. package/dist/core/canvas/canvas.js +1 -1
  176. package/dist/core/canvas/focuses.d.ts +4 -4
  177. package/dist/core/canvas/focuses.js +7 -7
  178. package/dist/core/canvas/nav-model.d.ts +162 -0
  179. package/dist/core/canvas/nav-model.js +487 -0
  180. package/dist/core/canvas/paths.d.ts +7 -0
  181. package/dist/core/canvas/paths.js +9 -0
  182. package/dist/core/canvas/render.d.ts +68 -9
  183. package/dist/core/canvas/render.js +195 -61
  184. package/dist/core/canvas/types.d.ts +16 -23
  185. package/dist/core/config.js +1 -4
  186. package/dist/core/memory-resolver.js +43 -5
  187. package/dist/core/runtime/bearings.d.ts +1 -1
  188. package/dist/core/runtime/bearings.js +8 -8
  189. package/dist/core/runtime/branded-host.d.ts +16 -0
  190. package/dist/core/runtime/branded-host.js +127 -0
  191. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  192. package/dist/core/runtime/broker-sdk.d.ts +0 -12
  193. package/dist/core/runtime/broker-sdk.js +77 -6
  194. package/dist/core/runtime/broker.d.ts +5 -1
  195. package/dist/core/runtime/broker.js +520 -59
  196. package/dist/core/runtime/close.d.ts +12 -2
  197. package/dist/core/runtime/close.js +37 -16
  198. package/dist/core/runtime/front-door.js +14 -7
  199. package/dist/core/runtime/host.d.ts +14 -34
  200. package/dist/core/runtime/host.js +14 -50
  201. package/dist/core/runtime/launch.d.ts +12 -9
  202. package/dist/core/runtime/launch.js +27 -15
  203. package/dist/core/runtime/lifecycle.d.ts +1 -1
  204. package/dist/core/runtime/lifecycle.js +15 -19
  205. package/dist/core/runtime/nodes.d.ts +0 -55
  206. package/dist/core/runtime/nodes.js +6 -74
  207. package/dist/core/runtime/placement.d.ts +91 -334
  208. package/dist/core/runtime/placement.js +262 -828
  209. package/dist/core/runtime/promote.d.ts +2 -0
  210. package/dist/core/runtime/promote.js +24 -6
  211. package/dist/core/runtime/recap.d.ts +8 -0
  212. package/dist/core/runtime/recap.js +107 -0
  213. package/dist/core/runtime/recycle.js +25 -61
  214. package/dist/core/runtime/reset.d.ts +43 -37
  215. package/dist/core/runtime/reset.js +131 -218
  216. package/dist/core/runtime/revive.d.ts +9 -29
  217. package/dist/core/runtime/revive.js +45 -135
  218. package/dist/core/runtime/spawn.d.ts +15 -11
  219. package/dist/core/runtime/spawn.js +168 -136
  220. package/dist/core/runtime/surface-bg.d.ts +11 -0
  221. package/dist/core/runtime/surface-bg.js +68 -0
  222. package/dist/core/runtime/tmux.d.ts +15 -43
  223. package/dist/core/runtime/tmux.js +38 -80
  224. package/dist/core/scope.d.ts +1 -2
  225. package/dist/core/scope.js +1 -5
  226. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  227. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  228. package/dist/core/skill-sync/builtins.d.ts +42 -0
  229. package/dist/core/skill-sync/builtins.js +112 -0
  230. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  231. package/dist/core/skill-sync/claude-plugins.js +71 -0
  232. package/dist/core/skill-sync/engine.d.ts +42 -0
  233. package/dist/core/skill-sync/engine.js +633 -0
  234. package/dist/core/skill-sync/export.d.ts +23 -0
  235. package/dist/core/skill-sync/export.js +86 -0
  236. package/dist/core/skill-sync/manifest.d.ts +64 -0
  237. package/dist/core/skill-sync/manifest.js +181 -0
  238. package/dist/core/skill-sync/profile.d.ts +76 -0
  239. package/dist/core/skill-sync/profile.js +173 -0
  240. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  241. package/dist/core/skill-sync/snapshot.js +120 -0
  242. package/dist/core/spawn.d.ts +14 -0
  243. package/dist/core/spawn.js +29 -9
  244. package/dist/core/substrate/index.d.ts +2 -2
  245. package/dist/core/substrate/index.js +7 -7
  246. package/dist/core/substrate/injected-store.d.ts +10 -0
  247. package/dist/core/substrate/injected-store.js +55 -0
  248. package/dist/core/substrate/on-read.js +68 -6
  249. package/dist/core/substrate/render.d.ts +8 -11
  250. package/dist/core/substrate/render.js +29 -43
  251. package/dist/core/substrate/schema.d.ts +16 -11
  252. package/dist/core/substrate/schema.js +32 -31
  253. package/dist/core/tui/host.d.ts +12 -4
  254. package/dist/core/tui/host.js +280 -149
  255. package/dist/core/view/bridge.d.ts +10 -0
  256. package/dist/core/view/bridge.js +31 -0
  257. package/dist/core/view/chrome.d.ts +9 -0
  258. package/dist/core/view/chrome.js +22 -0
  259. package/dist/core/view/contract.d.ts +171 -0
  260. package/dist/core/view/contract.js +23 -0
  261. package/dist/core/view/loader.d.ts +31 -0
  262. package/dist/core/view/loader.js +188 -0
  263. package/dist/core/view/transport-local.d.ts +7 -0
  264. package/dist/core/view/transport-local.js +70 -0
  265. package/dist/core/view/transport.d.ts +4 -0
  266. package/dist/core/view/transport.js +15 -0
  267. package/dist/daemon/crtrd.d.ts +38 -27
  268. package/dist/daemon/crtrd.js +303 -376
  269. package/dist/daemon/manage.js +6 -1
  270. package/dist/index.js +1 -1
  271. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  272. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  273. package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
  274. package/dist/pi-extensions/canvas-nav.js +35 -385
  275. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  276. package/dist/pi-extensions/canvas-recap.js +236 -0
  277. package/dist/pi-extensions/canvas-resume.js +2 -1
  278. package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
  279. package/dist/pi-extensions/canvas-stophook.js +119 -103
  280. package/dist/pi-extensions/canvas-view.js +2 -1
  281. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  282. package/dist/pi-extensions/widget-order-bus.js +34 -0
  283. package/dist/prompts/view.d.ts +2 -2
  284. package/dist/prompts/view.js +145 -73
  285. package/dist/types.d.ts +1 -6
  286. package/dist/types.js +1 -3
  287. package/dist/web/ViewChrome.d.ts +7 -0
  288. package/dist/web/ViewChrome.js +28 -0
  289. package/dist/web/ViewPane.d.ts +39 -0
  290. package/dist/web/ViewPane.js +48 -0
  291. package/dist/web/index.d.ts +6 -0
  292. package/dist/web/index.js +16 -0
  293. package/dist/web/runtime.d.ts +39 -0
  294. package/dist/web/runtime.js +133 -0
  295. package/dist/web/states.d.ts +24 -0
  296. package/dist/web/states.js +24 -0
  297. package/dist/web/transport-http.d.ts +5 -0
  298. package/dist/web/transport-http.js +28 -0
  299. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  300. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  301. package/dist/web-client/index.html +13 -0
  302. package/package.json +20 -6
  303. package/dist/builtin-views/canvas/client.mjs +0 -303
  304. package/dist/builtin-views/canvas/view.mjs +0 -576
  305. package/dist/builtin-views/git-pr/client.mjs +0 -440
  306. package/dist/builtin-views/git-pr/view.mjs +0 -675
  307. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  308. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  309. package/dist/builtin-views/inbox/view.mjs +0 -889
  310. package/dist/builtin-views/linkedin/client.mjs +0 -610
  311. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  312. package/dist/commands/pkg/bridge.d.ts +0 -1
  313. package/dist/commands/pkg/bridge.js +0 -137
  314. package/dist/commands/skill/author.d.ts +0 -3
  315. package/dist/commands/skill/author.js +0 -140
  316. package/dist/commands/skill/shared.d.ts +0 -3
  317. package/dist/commands/skill/shared.js +0 -19
  318. package/dist/commands/skill.d.ts +0 -2
  319. package/dist/commands/skill.js +0 -21
  320. package/dist/commands/tmux-spread.d.ts +0 -2
  321. package/dist/commands/tmux-spread.js +0 -144
  322. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  323. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  324. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  325. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  326. package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
  327. package/dist/core/__tests__/home-session.test.js +0 -186
  328. package/dist/core/__tests__/relaunch.test.js +0 -335
  329. package/dist/core/bridge-map.d.ts +0 -19
  330. package/dist/core/bridge-map.js +0 -73
  331. package/dist/core/tui/contract.d.ts +0 -83
  332. package/dist/core/tui/contract.js +0 -8
  333. package/dist/core/tui/loader.d.ts +0 -16
  334. package/dist/core/tui/loader.js +0 -94
  335. package/dist/prompts/skill.d.ts +0 -2
  336. package/dist/prompts/skill.js +0 -650
  337. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  338. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  339. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  340. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  341. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  342. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
  343. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
@@ -1,101 +1,173 @@
1
- // Scaffold stub for `crtr view new`. Returns the contents of a runnable
2
- // `view.mjs` a minimal, self-contained ViewModule that runs as-is via
3
- // `crtr view run <name>` and prints a sane snapshot when piped (`| cat`).
1
+ // Scaffold stubs for `crtr view new`. Returns the four files of a dual-target
2
+ // view directory — a portable `core.mjs` plus the TUI / web / text presenters
3
+ // that runs as-is via `crtr view run <name>` (TUI), `crtr view serve <name>`
4
+ // (web), and `crtr view run <name> | cat` (static snapshot).
4
5
  //
5
- // Authoring guidance lives INSIDE the stub (the comment block at the top of the
6
+ // Authoring guidance lives INSIDE each stub (the comment block at the top of the
6
7
  // generated file), so a view author reads the contract right where they edit.
7
8
  /** Escape a value for embedding inside a single-quoted JS string literal. */
8
9
  function jsStr(s) {
9
10
  return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
10
11
  }
11
- /** Build the `view.mjs` source for a freshly scaffolded view. */
12
- export function viewScaffold(v) {
12
+ /** The portable core: manifest · init · sources · intents. Runs in BOTH Node
13
+ * (the TUI host) and the browser (the web bundle) — imports NOTHING. */
14
+ function coreStub(v) {
13
15
  const id = jsStr(v.id);
14
16
  const title = jsStr(v.title);
15
17
  const description = jsStr(v.description);
16
- return `// ${v.title} — a crtr view.
18
+ return `// ${v.title} — portable core (manifest · init · sources · intents).
17
19
  //
18
- // A view is a self-contained ESM module the \`crtr view\` host loads and runs.
19
- // Run it: crtr view run ${v.id} (interactive, inside tmux)
20
- // crtr view run ${v.id} | cat (static snapshot, exits 0)
20
+ // Runs in BOTH Node (the TUI host) and the browser (the web bundle), so it
21
+ // imports NOTHING no \`node:*\`, no crtr. ALL state + behavior live here;
22
+ // presenters (tui.mjs / web.jsx) are pure reads that dispatch named intents.
21
23
  //
22
- // THE CONTRACT (the host injects everything — import NOTHING from crtr):
23
- // manifest { id, title, description, refreshMs?, keymap? }
24
- // id MUST equal this view's directory name.
25
- // init(host) build initial state — keep it CHEAP and sync-ish;
26
- // do NOT do slow fetches here (do them in refresh).
27
- // refresh(state,host) async data load; the host runs it on launch, every
28
- // manifest.refreshMs (if set), and whenever a key handler
29
- // returns { type: 'refresh' }. Mutate state in place.
30
- // render(state,draw,content) paint into the \`content\` Rect via draw.* —
31
- // a pure read of state; NEVER write ANSI yourself.
32
- // onKey(k,state,host) handle one keystroke; mutate state; return a ViewAction.
33
- // dump(state) plain text for the piped / non-TTY path (exit 0).
24
+ // Run it: crtr view run ${v.id} (TUI, inside tmux)
25
+ // crtr view run ${v.id} | cat (static snapshot, exits 0)
26
+ // crtr view serve ${v.id} (React + Tailwind web page)
34
27
  //
35
- // draw API (all absolute cells inside \`content\`):
36
- // draw.text(row,col,text,style?) · draw.spans(row,col,spans,maxWidth?)
37
- // draw.hline(row,fromCol?,toCol?) · draw.box(rect,title?)
38
- // draw.columns(rect,[1,2]) -> Rect[] (proportional split)
39
- // draw.list(rect,items,cursor,scroll) -> { scroll } (windows + cursor row)
40
- // style: { fg, bg, bold, dim, reverse } (fg/bg honored only when color is on)
41
- // host API:
42
- // host.options CLI --flags, camelCased & stringified (e.g. options.port)
43
- // host.setStatus(msg) transient footer-left status; null clears it
44
- // host.setError(msg) sticky red banner above the footer; null clears it
45
- // ViewAction: { type: 'render' | 'refresh' | 'quit' | 'none' }
28
+ // THE CONTRACT:
29
+ // manifest { id, title, description, refreshMs? } — id MUST equal
30
+ // this view's directory name.
31
+ // init(opts) cheap, SYNCHRONOUS initial state — NO fetch, NO screen.
32
+ // CLI flags arrive in \`opts\` (crtr view run --port/--target).
33
+ // sources { request, parse } READ descriptors. The host's transport
34
+ // runs request() (exec/file/http) and hands parse() the bytes
35
+ // your parse is pure, runs in Node AND the browser.
36
+ // intents semantic actions both targets dispatch. State updates are
37
+ // IMMUTABLE via ctx.set; async effects (ctx.resolve a source)
38
+ // live in the same handler. ctx.signal raises chrome signals.
46
39
 
47
- /** @type {import('@crouton-kit/crouter/dist/core/tui/contract.js').ViewModule} */
48
- const view = {
40
+ /** A READ source: a transport-agnostic request + a pure parse. */
41
+ export const clockSource = {
42
+ id: 'clock',
43
+ request: () => ({ kind: 'exec', bin: 'date', args: [] }),
44
+ parse: (raw) =>
45
+ raw.ok && raw.exitCode === 0
46
+ ? { ok: true, data: raw.stdout.trim() }
47
+ : { ok: false, error: { kind: 'clock-failed', display: {
48
+ headline: 'Could not read the clock',
49
+ explanation: raw.stderr || 'the date command failed',
50
+ nextStep: 'Check the \`date\` binary is on PATH.',
51
+ level: 'error', blocking: true } } },
52
+ };
53
+
54
+ /** @type {import('@crouton-kit/crouter/dist/core/view/contract.js').ViewCore} */
55
+ const core = {
49
56
  manifest: {
50
57
  id: '${id}',
51
58
  title: '${title}',
52
59
  description: '${description}',
53
60
  // refreshMs: 30000, // uncomment to auto-poll; omit ⇒ on-demand (g) only
54
- keymap: [
55
- { keys: 'g', label: 'refresh' },
56
- { keys: 'q', label: 'quit' },
57
- ],
58
61
  },
59
62
 
60
- init(host) {
61
- // Cheap, synchronous setup. Stash CLI flags off host.options here.
62
- return { count: 0, port: host.options.port ?? null };
63
- },
63
+ // Cheap, synchronous setup. Stash CLI flags off \`opts\` here.
64
+ init: (opts) => ({ count: 0, clock: null, port: opts.port ?? null }),
64
65
 
65
- async refresh(state, host) {
66
- // Load your data here (shell out, fetch, read files). Mutate \`state\`.
67
- state.count += 1;
68
- host.setStatus(\`refreshed \${state.count}×\`);
69
- },
66
+ sources: { clockSource },
70
67
 
71
- render(state, draw, content) {
72
- draw.text(content.row, content.col, '${title}', { bold: true });
73
- draw.text(content.row + 2, content.col, \`refreshed \${state.count} time(s)\`);
74
- draw.text(content.row + 3, content.col, \`port option: \${state.port ?? '(none)'}\`, { dim: true });
75
- draw.text(
76
- content.row + 5,
77
- content.col,
78
- 'Edit view.mjs to build your view g refreshes, q quits.',
79
- { dim: true },
80
- );
68
+ intents: {
69
+ // Async intent: ctx.set updates state immutably; ctx.resolve runs a source
70
+ // through the host transport. The host runs 'refresh' on launch, every
71
+ // manifest.refreshMs (if set), and on the \`g\` key (see tui.mjs).
72
+ async refresh(ctx) {
73
+ ctx.signal.setStatus('Reading clock…');
74
+ const r = await ctx.resolve(clockSource);
75
+ if (!r.ok) { ctx.signal.setBanner(r.error.display.headline, r.error.display.level); ctx.signal.setStatus(null); return; }
76
+ ctx.set((s) => ({ ...s, count: s.count + 1, clock: r.data }));
77
+ ctx.signal.clearBanner();
78
+ ctx.signal.setStatus(\`refreshed \${ctx.state.count}×\`);
79
+ },
80
+ quit: (ctx) => ctx.signal.quit(),
81
81
  },
82
+ };
82
83
 
83
- onKey(k, state, host) {
84
- if (k.input === 'q') return { type: 'quit' };
85
- if (k.input === 'g') return { type: 'refresh' };
86
- return { type: 'none' };
87
- },
84
+ export default core;
85
+ `;
86
+ }
87
+ /** The TUI presenter: render(state, draw, content) + keymap. Node only. */
88
+ function tuiStub(v) {
89
+ const title = jsStr(v.title);
90
+ return `// ${v.title} — TUI presenter: render(state, draw, content) + keymap.
91
+ //
92
+ // A pure read of state — paint via draw.*; NEVER write ANSI. Keys map to the
93
+ // core's intents (no logic here; the keymap names an intent, the host dispatches).
94
+ //
95
+ // draw API (all absolute cells inside \`content\`):
96
+ // draw.text(row,col,text,style?) · draw.spans(row,col,spans,maxWidth?)
97
+ // draw.hline(row,fromCol?,toCol?) · draw.box(rect,title?)
98
+ // draw.columns(rect,[1,2]) -> Rect[] · draw.list(rect,items,cursor,scroll)
99
+ // style: { fg, bg, bold, dim, reverse } (fg/bg honored only when color is on)
88
100
 
89
- dump(state) {
90
- return [
91
- '${title}',
92
- '',
93
- \`refreshed \${state.count} time(s)\`,
94
- \`port option: \${state.port ?? '(none)'}\`,
95
- ].join('\\n');
96
- },
97
- };
101
+ export const keymap = [
102
+ { keys: ['g'], intent: 'refresh', hint: { keys: 'g', label: 'refresh' } },
103
+ { keys: ['q'], intent: 'quit', hint: { keys: 'q', label: 'quit' } },
104
+ ];
98
105
 
99
- export default view;
106
+ export function render(state, draw, content) {
107
+ draw.text(content.row, content.col, '${title}', { bold: true });
108
+ draw.text(content.row + 2, content.col, \`clock: \${state.clock ?? '(loading…)'}\`);
109
+ draw.text(content.row + 3, content.col, \`refreshed \${state.count} time(s)\`, { dim: true });
110
+ draw.text(content.row + 4, content.col, \`port option: \${state.port ?? '(none)'}\`, { dim: true });
111
+ draw.text(content.row + 6, content.col, 'Edit core.mjs + tui.mjs to build your view — g refreshes, q quits.', { dim: true });
112
+ }
100
113
  `;
101
114
  }
115
+ /** The web presenter: a React + Tailwind component. Browser only (Vite owns the
116
+ * JSX + Tailwind transpile; this file is NEVER Node-imported). */
117
+ function webStub(v) {
118
+ const title = jsStr(v.title);
119
+ return `// ${v.title} — web presenter: a React + Tailwind component.
120
+ //
121
+ // A pure function of \`state\`; DOM events call \`dispatch(intent)\`. Consumed ONLY
122
+ // by \`crtr view serve\` (Vite owns JSX + Tailwind) — never Node-imported. The
123
+ // SAME state + intents the TUI reads; zero shared render code (an intentional fork).
124
+
125
+ export default function View({ state, dispatch }) {
126
+ return (
127
+ <div
128
+ className="font-mono text-sm p-4 outline-none"
129
+ tabIndex={0}
130
+ onKeyDown={(e) => { if (e.key === 'g') dispatch('refresh'); }}
131
+ >
132
+ <h1 className="text-lg font-bold">${title}</h1>
133
+ <p className="mt-2">clock: {state.clock ?? '(loading…)'}</p>
134
+ <p className="text-gray-500">refreshed {state.count} time(s)</p>
135
+ <button
136
+ className="mt-3 rounded bg-blue-600 px-2 py-1 text-white hover:bg-blue-500"
137
+ onClick={() => dispatch('refresh')}
138
+ >
139
+ Refresh
140
+ </button>
141
+ </div>
142
+ );
143
+ }
144
+ `;
145
+ }
146
+ /** The text presenter: the piped / non-TTY snapshot. Node only. Optional — omit
147
+ * it and the host synthesizes a generic one-line dump. */
148
+ function textStub(v) {
149
+ const title = jsStr(v.title);
150
+ return `// ${v.title} — text presenter: the piped / non-TTY snapshot (exits 0).
151
+ //
152
+ // A pure read of current state. Omit this file and the host synthesizes a
153
+ // one-line dump (\`<title> — <n> items\`).
154
+
155
+ export function dump(state) {
156
+ return [
157
+ '${title}',
158
+ '',
159
+ \`clock: \${state.clock ?? '(none)'}\`,
160
+ \`refreshed \${state.count} time(s)\`,
161
+ ].join('\\n');
162
+ }
163
+ `;
164
+ }
165
+ /** Build every file of a freshly scaffolded dual-target view, keyed by filename. */
166
+ export function viewScaffold(v) {
167
+ return {
168
+ 'core.mjs': coreStub(v),
169
+ 'tui.mjs': tuiStub(v),
170
+ 'web.jsx': webStub(v),
171
+ 'text.mjs': textStub(v),
172
+ };
173
+ }
package/dist/types.d.ts CHANGED
@@ -20,7 +20,6 @@ export interface PluginManifest {
20
20
  source?: string;
21
21
  owner?: OwnerRef;
22
22
  kinds?: string[];
23
- generator?: 'claude-bridge';
24
23
  }
25
24
  export interface MarketplacePluginEntry {
26
25
  name: string;
@@ -80,10 +79,6 @@ export interface ScopeConfig {
80
79
  auto_update: AutoUpdateConfig;
81
80
  max_panes_per_window: number;
82
81
  canvasNav: CanvasNavConfig;
83
- /** Default to spawning nodes on the headless broker host instead of a tmux
84
- * pane. Off by default (tmux stays the default); `--headless` overrides it
85
- * per-spawn. Read at spawn via `readConfig('user').headless`. */
86
- headless?: boolean;
87
82
  }
88
83
  export interface ScopeState {
89
84
  marketplaces: Record<string, {
@@ -155,7 +150,7 @@ export declare const AGENTS_DIR = "agents";
155
150
  export declare const DEFAULT_MAX_PANES_PER_WINDOW = 3;
156
151
  export declare function defaultScopeConfig(): ScopeConfig;
157
152
  /** Built-in canvas-nav binds so an absent config still gives the intended UX.
158
- * prefixBinds: g→GRAPH toggle (sentinel), m→focus manager, e→expand to tmux.
153
+ * prefixBinds: g→GRAPH toggle (sentinel), m→focus manager.
159
154
  * 1..9 (focus report N) are generated by the menu layer, not literal entries.
160
155
  * graphBinds: empty — built-in GRAPH keys are reserved, custom keys add on. */
161
156
  export declare function defaultCanvasNavConfig(): CanvasNavConfig;
package/dist/types.js CHANGED
@@ -32,11 +32,10 @@ export function defaultScopeConfig() {
32
32
  auto_update: { crtr: 'notify', content: 'notify', interval_hours: 24 },
33
33
  max_panes_per_window: DEFAULT_MAX_PANES_PER_WINDOW,
34
34
  canvasNav: defaultCanvasNavConfig(),
35
- headless: false,
36
35
  };
37
36
  }
38
37
  /** Built-in canvas-nav binds so an absent config still gives the intended UX.
39
- * prefixBinds: g→GRAPH toggle (sentinel), m→focus manager, e→expand to tmux.
38
+ * prefixBinds: g→GRAPH toggle (sentinel), m→focus manager.
40
39
  * 1..9 (focus report N) are generated by the menu layer, not literal entries.
41
40
  * graphBinds: empty — built-in GRAPH keys are reserved, custom keys add on. */
42
41
  export function defaultCanvasNavConfig() {
@@ -45,7 +44,6 @@ export function defaultCanvasNavConfig() {
45
44
  prefixBinds: {
46
45
  g: { run: '__graph__', desc: 'graph view' },
47
46
  m: { run: 'node focus {manager}', desc: 'focus manager' },
48
- e: { run: 'canvas tmux-spread {self}', desc: 'expand to tmux' },
49
47
  },
50
48
  graphBinds: {},
51
49
  };
@@ -0,0 +1,7 @@
1
+ import type { JSX, ReactNode } from 'react';
2
+ import type { ChromeState } from '../core/view/contract.js';
3
+ export declare function ViewChrome({ chrome, title, children }: {
4
+ chrome: ChromeState;
5
+ title: string;
6
+ children: ReactNode;
7
+ }): JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { deriveState } from '../core/view/chrome.js';
3
+ const CHIP_LABEL = {
4
+ working: 'working',
5
+ blocked: 'blocked',
6
+ attention: 'attention',
7
+ ready: 'ready',
8
+ idle: 'idle',
9
+ };
10
+ const CHIP_DOT = {
11
+ working: 'bg-blue-500',
12
+ blocked: 'bg-red-500',
13
+ attention: 'bg-amber-500',
14
+ ready: 'bg-green-500',
15
+ idle: 'bg-slate-400',
16
+ };
17
+ const BANNER_BAR = {
18
+ info: 'bg-slate-100 text-slate-800 border-slate-300',
19
+ action: 'bg-amber-100 text-amber-900 border-amber-300',
20
+ error: 'bg-red-100 text-red-900 border-red-300',
21
+ };
22
+ export function ViewChrome({ chrome, title, children }) {
23
+ // deriveState's contract: an explicit interaction mode overrides the derived
24
+ // chip, and each target's renderer applies that precedence. Web keeps the mode
25
+ // as its own header pill (the §4.1 web idiom) AND lets it win the chip.
26
+ const chip = chrome.mode ? 'attention' : deriveState(chrome);
27
+ return (_jsxs("div", { className: "min-h-screen bg-white text-slate-900", children: [_jsxs("header", { className: "flex items-center gap-3 border-b border-slate-200 px-4 py-2", children: [_jsx("h1", { className: "font-mono text-sm font-semibold", children: title }), chrome.subtitle ? (_jsxs("span", { className: "font-mono text-xs text-slate-400", children: ["\u00B7 ", chrome.subtitle] })) : null, chrome.mode ? (_jsx("span", { className: "rounded-full bg-slate-800 px-2 py-0.5 text-xs text-white", children: chrome.mode })) : null, _jsxs("span", { className: "ml-auto flex items-center gap-2 text-xs text-slate-500", children: [chrome.busy ? (_jsx("span", { className: "inline-block h-3 w-3 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600" })) : null, _jsx("span", { className: `inline-block h-2 w-2 rounded-full ${CHIP_DOT[chip]}` }), _jsx("span", { children: CHIP_LABEL[chip] })] })] }), chrome.banner ? (_jsx("div", { className: `border-b px-4 py-1.5 font-mono text-xs ${BANNER_BAR[chrome.banner.level]}`, children: chrome.banner.msg })) : null, _jsx("main", { className: "px-4 py-3", children: children }), chrome.status ? (_jsx("footer", { className: "border-t border-slate-200 px-4 py-1.5 font-mono text-xs text-slate-500", children: chrome.status })) : null] }));
28
+ }
@@ -0,0 +1,39 @@
1
+ import type { JSX, FunctionComponent } from 'react';
2
+ import type { ViewCore, ChromeState } from '../core/view/contract.js';
3
+ import { type IntentTap } from './runtime.js';
4
+ type ViewPropsWeb = {
5
+ state: unknown;
6
+ dispatch: (intent: string, payload?: unknown) => void;
7
+ chrome: ChromeState;
8
+ };
9
+ type WebComponent = FunctionComponent<ViewPropsWeb>;
10
+ export interface UseViewCoreOptions {
11
+ /** Frozen options map handed to core.init. */
12
+ options?: Record<string, string>;
13
+ /** Bridge endpoint for the HTTP transport; defaults to /__crtr/source. */
14
+ endpoint?: string;
15
+ /** The host TAP (§5): observe/intercept dispatched intents. */
16
+ onIntent?: IntentTap;
17
+ }
18
+ export interface ViewCoreHandle {
19
+ state: unknown;
20
+ chrome: ChromeState;
21
+ dispatch: (intent: string, payload?: unknown) => void;
22
+ /** Force an immediate refresh — the seam the shell calls on an SSE
23
+ * invalidation (Wave 3). */
24
+ refresh: () => void;
25
+ }
26
+ /** Run a view core client-side and bind it to React. Returns the live state +
27
+ * chrome + a dispatch (and a refresh seam). The onIntent tap is read fresh on
28
+ * every render, so a parent can change it without tearing down the view. */
29
+ export declare function useViewCore(core: ViewCore, opts?: UseViewCoreOptions): ViewCoreHandle;
30
+ /** Host any dual-target view as a React component: its web component wrapped in
31
+ * <ViewChrome>, driven by useViewCore. Pass onIntent to tap its intents. */
32
+ export declare function ViewPane({ core, View, options, endpoint, onIntent }: {
33
+ core: ViewCore;
34
+ View: WebComponent;
35
+ options?: Record<string, string>;
36
+ endpoint?: string;
37
+ onIntent?: IntentTap;
38
+ }): JSX.Element;
39
+ export {};
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // ViewPane.tsx — the React surface for a dual-target view. <ViewPane> and its
3
+ // useViewCore hook are the composable replacement for the deleted imperative
4
+ // mount(): the shell hosts many views as ordinary React components instead of
5
+ // imperative createRoot calls.
6
+ //
7
+ // useViewCore wraps a createViewStore (runtime.tsx) — the framework-free dispatch
8
+ // loop — with the React lifecycle: it creates the store once (a ref), keeps the
9
+ // onIntent TAP fresh per render WITHOUT restarting, re-renders off the store's
10
+ // external-store surface (useSyncExternalStore), and starts/stops the auto-poll
11
+ // on mount/unmount.
12
+ //
13
+ // <ViewPane> = useViewCore + <ViewChrome> + the view's web component. When
14
+ // onIntent is undefined a ViewPane is byte-identical to the old standalone mount
15
+ // — the hosted view never knows it is in a shell.
16
+ import { createElement, useEffect, useRef, useSyncExternalStore } from 'react';
17
+ import { ViewChrome } from './ViewChrome.js';
18
+ import { createViewStore } from './runtime.js';
19
+ /** Run a view core client-side and bind it to React. Returns the live state +
20
+ * chrome + a dispatch (and a refresh seam). The onIntent tap is read fresh on
21
+ * every render, so a parent can change it without tearing down the view. */
22
+ export function useViewCore(core, opts = {}) {
23
+ const storeRef = useRef(null);
24
+ if (storeRef.current === null) {
25
+ storeRef.current = createViewStore(core, { options: opts.options, endpoint: opts.endpoint });
26
+ }
27
+ const store = storeRef.current;
28
+ // Keep the TAP fresh without recreating the store (which would restart the
29
+ // dispatch loop and lose state).
30
+ store.onIntent = opts.onIntent;
31
+ useSyncExternalStore(store.subscribe, store.getVersion, store.getVersion);
32
+ useEffect(() => {
33
+ store.start();
34
+ return () => store.stop();
35
+ }, [store]);
36
+ return {
37
+ state: store.getState(),
38
+ chrome: store.getChrome(),
39
+ dispatch: store.dispatch,
40
+ refresh: store.refresh,
41
+ };
42
+ }
43
+ /** Host any dual-target view as a React component: its web component wrapped in
44
+ * <ViewChrome>, driven by useViewCore. Pass onIntent to tap its intents. */
45
+ export function ViewPane({ core, View, options, endpoint, onIntent }) {
46
+ const { state, chrome, dispatch } = useViewCore(core, { options, endpoint, onIntent });
47
+ return (_jsx(ViewChrome, { chrome: chrome, title: core.manifest.title, children: createElement(View, { state, dispatch, chrome }) }));
48
+ }
@@ -0,0 +1,6 @@
1
+ export { ViewPane, useViewCore } from './ViewPane.js';
2
+ export type { UseViewCoreOptions, ViewCoreHandle } from './ViewPane.js';
3
+ export { createViewStore } from './runtime.js';
4
+ export type { ViewStore, ViewStoreOptions, IntentTap } from './runtime.js';
5
+ export { ViewChrome } from './ViewChrome.js';
6
+ export { Loading, Empty, ErrorState, NotReady } from './states.js';
@@ -0,0 +1,16 @@
1
+ // index.ts — the public barrel for crtr's web runtime, published as the package
2
+ // `exports` subpath `@crouton-kit/crouter/web` (→ ./dist/web/index.js).
3
+ //
4
+ // A dual-target view's `web.jsx` imports the shared four-state vocabulary +
5
+ // (if it needs them) the chrome wrapper from here, via that stable specifier —
6
+ // NOT a relative path from builtin-views, which would break for user/project
7
+ // views living outside the package tree. The shell + dev Vite resolve this
8
+ // subpath against crouter's own built barrel.
9
+ //
10
+ // The web shell composes views via <ViewPane>/useViewCore (the React surface)
11
+ // over createViewStore (the framework-free dispatch loop + the onIntent tap +
12
+ // the refresh() SSE seam).
13
+ export { ViewPane, useViewCore } from './ViewPane.js';
14
+ export { createViewStore } from './runtime.js';
15
+ export { ViewChrome } from './ViewChrome.js';
16
+ export { Loading, Empty, ErrorState, NotReady } from './states.js';
@@ -0,0 +1,39 @@
1
+ import type { ViewCore, ChromeState } from '../core/view/contract.js';
2
+ /** Host-level pre-dispatch hook: observes every dispatched intent + its payload
3
+ * against a snapshot of state, before the view's own handler runs. The shell
4
+ * taps the small documented shell-intent vocabulary (`activate`, `open`) and
5
+ * lets everything else pass through untouched. */
6
+ export type IntentTap = (name: string, payload: unknown, state: unknown) => void;
7
+ export interface ViewStoreOptions {
8
+ /** Frozen options map handed to core.init (e.g. forwarded --target). */
9
+ options?: Record<string, string>;
10
+ /** Bridge endpoint for the HTTP transport; defaults to /__crtr/source. */
11
+ endpoint?: string;
12
+ /** The host TAP (§5). Omit ⇒ standalone, byte-identical to no shell. */
13
+ onIntent?: IntentTap;
14
+ }
15
+ export interface ViewStore {
16
+ /** Snapshot of the current view state (read after a subscribe fires). */
17
+ getState(): unknown;
18
+ /** Snapshot of the current chrome record. */
19
+ getChrome(): ChromeState;
20
+ /** Dispatch a named intent — runs the TAP, then the core handler. */
21
+ dispatch(intent: string, payload?: unknown): Promise<void>;
22
+ /** Force an immediate, busy-guarded refresh (auto-poll cb + the SSE seam). */
23
+ refresh(): void;
24
+ /** External-store subscribe (for useSyncExternalStore). */
25
+ subscribe(listener: () => void): () => void;
26
+ /** Monotonic version the external store snapshots on. */
27
+ getVersion(): number;
28
+ /** Run the first refresh + start the refreshMs auto-poll. Idempotent. */
29
+ start(): void;
30
+ /** Stop the auto-poll. Idempotent. */
31
+ stop(): void;
32
+ /** The host TAP — mutable so the React layer keeps it fresh per render
33
+ * WITHOUT restarting the store. */
34
+ onIntent?: IntentTap;
35
+ }
36
+ /** Build the running store for one view (its core + the HTTP transport). The
37
+ * React layer (useViewCore) owns lifecycle (start on mount, stop on unmount)
38
+ * and re-renders off subscribe/getVersion; this factory is React-free. */
39
+ export declare function createViewStore(core: ViewCore, opts?: ViewStoreOptions): ViewStore;
@@ -0,0 +1,133 @@
1
+ // runtime.tsx — the framework-agnostic dispatch-loop STORE for a dual-target
2
+ // view. It is what <ViewPane>/useViewCore (ViewPane.tsx) are built from.
3
+ //
4
+ // createViewStore() seeds state from core.init, builds the IntentCtx
5
+ // (set/resolve/execute/signal/dispatch) over the HTTP transport (→ the local
6
+ // bridge), runs the dispatch loop with the busy-guarded refreshMs auto-poll, and
7
+ // exposes a tiny external-store surface (subscribe/getVersion) the React layer
8
+ // binds via useSyncExternalStore. The core it drives is byte-identical to the
9
+ // TUI's; only the injected Transport differs.
10
+ //
11
+ // THE TAP: dispatch calls `onIntent(name, payload, state)` BEFORE delegating to
12
+ // core.intents[name]. This is the host-level pre-dispatch hook the shell uses to
13
+ // observe/intercept semantic intents (e.g. canvas `activate` → open a
14
+ // conversation). When onIntent is undefined the loop is byte-identical to the
15
+ // pre-shell behavior — a view in a ViewPane is unaware it is in a shell.
16
+ //
17
+ // THE SEAM (Wave 3): store.refresh() forces an immediate, busy-guarded refresh.
18
+ // It is both the auto-poll callback AND the entry point the shell calls when an
19
+ // SSE `/__crtr/events` invalidation arrives — event-driven refresh with no
20
+ // contract change (the view still just runs its `refresh` intent).
21
+ import { initialChrome } from '../core/view/chrome.js';
22
+ import { createHttpTransport } from './transport-http.js';
23
+ /** Build the running store for one view (its core + the HTTP transport). The
24
+ * React layer (useViewCore) owns lifecycle (start on mount, stop on unmount)
25
+ * and re-renders off subscribe/getVersion; this factory is React-free. */
26
+ export function createViewStore(core, opts = {}) {
27
+ const transport = createHttpTransport(opts.endpoint);
28
+ let state = core.init(Object.freeze({ ...(opts.options ?? {}) }));
29
+ let chrome = initialChrome();
30
+ let onIntentTap = opts.onIntent;
31
+ // External store: a version counter the React tree subscribes to. state and
32
+ // chrome are read via the getters on each render; bumping `version` re-renders.
33
+ let version = 0;
34
+ const listeners = new Set();
35
+ const emit = () => { version += 1; for (const l of listeners)
36
+ l(); };
37
+ const subscribe = (l) => { listeners.add(l); return () => { listeners.delete(l); }; };
38
+ const setChrome = (patch) => { chrome = { ...chrome, ...patch }; emit(); };
39
+ const signal = {
40
+ setStatus: (msg) => setChrome({ status: msg }),
41
+ setBanner: (msg, level) => setChrome({ banner: { msg, level } }),
42
+ clearBanner: () => setChrome({ banner: null }),
43
+ setSubtitle: (s) => setChrome({ subtitle: s }),
44
+ setMode: (mode) => setChrome({ mode }),
45
+ // Web has no pane to leave; closing the tab is up to the user. No-op.
46
+ quit: () => { },
47
+ };
48
+ const makeCtx = () => ({
49
+ get state() { return state; },
50
+ set(next) {
51
+ state = typeof next === 'function' ? next(state) : next;
52
+ emit();
53
+ },
54
+ async resolve(source, args) {
55
+ const raw = await transport.send(source.request(args));
56
+ return source.parse(raw);
57
+ },
58
+ async execute(command, args) {
59
+ const raw = await transport.send(command.request(args));
60
+ return command.parse(raw);
61
+ },
62
+ signal,
63
+ dispatch,
64
+ });
65
+ // busyDepth counts in-flight async intents — it drives the "working" chip /
66
+ // spinner. It is NOT itself the single-flight lane; the auto-poll guards on it
67
+ // (refresh() below) so a bridge slower than refreshMs can never stack
68
+ // overlapping refreshes that race on ctx.set. The first completed *refresh*
69
+ // marks the view loaded (ready vs idle in deriveState) — a non-refresh async
70
+ // intent before that must not flip the chip to ready.
71
+ let busyDepth = 0;
72
+ async function dispatch(intent, payload) {
73
+ // THE TAP — the shell observes (and may act on) every intent against a
74
+ // snapshot of state BEFORE the view's own handler runs. Standalone (no tap)
75
+ // this is a no-op and the loop is byte-identical to the pre-shell behavior.
76
+ onIntentTap?.(intent, payload, state);
77
+ const fn = core.intents[intent];
78
+ if (!fn) {
79
+ // eslint-disable-next-line no-console
80
+ console.warn(`[crtr view] unknown intent: ${intent}`);
81
+ return;
82
+ }
83
+ const result = fn(makeCtx(), payload);
84
+ if (result instanceof Promise) {
85
+ busyDepth += 1;
86
+ setChrome({ busy: true });
87
+ try {
88
+ await result;
89
+ }
90
+ finally {
91
+ busyDepth -= 1;
92
+ const loaded = chrome.loaded || intent === 'refresh';
93
+ chrome = { ...chrome, busy: busyDepth > 0, loaded, lastRefresh: Date.now() };
94
+ emit();
95
+ }
96
+ }
97
+ }
98
+ // Auto-poll cadence guard AND the shell's invalidate seam: skip while any
99
+ // async intent is still in flight so refreshes never stack.
100
+ const refresh = () => { if (busyDepth === 0)
101
+ void dispatch('refresh'); };
102
+ let started = false;
103
+ let timer = null;
104
+ function start() {
105
+ if (started)
106
+ return;
107
+ started = true;
108
+ // init → render (the React layer) → first refresh, then optional auto-poll.
109
+ void dispatch('refresh');
110
+ const cadence = core.manifest.refreshMs;
111
+ if (typeof cadence === 'number' && cadence > 0)
112
+ timer = setInterval(refresh, cadence);
113
+ }
114
+ function stop() {
115
+ started = false;
116
+ if (timer) {
117
+ clearInterval(timer);
118
+ timer = null;
119
+ }
120
+ }
121
+ return {
122
+ getState: () => state,
123
+ getChrome: () => chrome,
124
+ dispatch,
125
+ refresh,
126
+ subscribe,
127
+ getVersion: () => version,
128
+ start,
129
+ stop,
130
+ get onIntent() { return onIntentTap; },
131
+ set onIntent(v) { onIntentTap = v; },
132
+ };
133
+ }