@swirls/looms 0.0.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/dist/actor/cell.d.ts +9 -4
  3. package/dist/actor/cell.js +7 -11
  4. package/dist/actor/host.d.ts +7 -3
  5. package/dist/actor/host.js +57 -33
  6. package/dist/actor/index.d.ts +1 -1
  7. package/dist/actor/index.js +1 -1
  8. package/dist/actor/routing.d.ts +12 -1
  9. package/dist/actor/routing.js +57 -51
  10. package/dist/agent/agent-session.d.ts +5 -0
  11. package/dist/agent/agent-session.js +270 -0
  12. package/dist/agent/debugger/agent-chat.d.ts +2 -0
  13. package/dist/agent/debugger/agent-chat.js +176 -0
  14. package/dist/agent/debugger/index.d.ts +5 -0
  15. package/dist/agent/debugger/index.js +19 -0
  16. package/dist/agent/debugger/summarize.d.ts +2 -0
  17. package/dist/agent/debugger/summarize.js +51 -0
  18. package/dist/agent/debugger/token-usage.d.ts +3 -0
  19. package/dist/agent/debugger/token-usage.js +11 -0
  20. package/dist/agent/definitions-store.d.ts +2 -3
  21. package/dist/agent/definitions-store.js +2 -4
  22. package/dist/agent/definitions.d.ts +47 -29
  23. package/dist/agent/definitions.js +39 -12
  24. package/dist/agent/effects.d.ts +359 -2
  25. package/dist/agent/effects.js +60 -34
  26. package/dist/agent/events.d.ts +175 -19
  27. package/dist/agent/events.js +45 -10
  28. package/dist/agent/index.d.ts +1 -0
  29. package/dist/agent/index.js +1 -0
  30. package/dist/agent/llm.d.ts +11 -2
  31. package/dist/agent/llm.js +13 -3
  32. package/dist/agent/module.d.ts +812 -78
  33. package/dist/agent/module.js +16 -4
  34. package/dist/agent/scope.d.ts +174 -18
  35. package/dist/agent/signals.d.ts +26 -1
  36. package/dist/agent/signals.js +79 -0
  37. package/dist/agent/threads.d.ts +3 -33
  38. package/dist/agent/threads.js +20 -25
  39. package/dist/agent/tool-schema.d.ts +1 -17
  40. package/dist/agent/tool-schema.js +3 -43
  41. package/dist/agent/types.d.ts +49 -4
  42. package/dist/agent/types.js +5 -5
  43. package/dist/ai-vercel/index.d.ts +1 -0
  44. package/dist/ai-vercel/index.js +1 -0
  45. package/dist/ai-vercel/messages.js +22 -7
  46. package/dist/ai-vercel/vercel-evaluator.d.ts +36 -0
  47. package/dist/ai-vercel/vercel-evaluator.js +73 -0
  48. package/dist/ai-vercel/vercel-llm.js +64 -44
  49. package/dist/approval/debugger/approvals-view.d.ts +3 -0
  50. package/dist/approval/debugger/approvals-view.js +16 -0
  51. package/dist/approval/debugger/index.d.ts +4 -0
  52. package/dist/approval/debugger/index.js +11 -0
  53. package/dist/approval/debugger/summarize.d.ts +2 -0
  54. package/dist/approval/debugger/summarize.js +17 -0
  55. package/dist/approval/effects.d.ts +89 -0
  56. package/dist/approval/module.d.ts +179 -29
  57. package/dist/approval/projections.d.ts +0 -14
  58. package/dist/approval/projections.js +20 -3
  59. package/dist/approval/signals.d.ts +2 -0
  60. package/dist/approval/signals.js +18 -2
  61. package/dist/cli/cli.js +2 -0
  62. package/dist/cli/commands.js +12 -16
  63. package/dist/client/client.d.ts +1213 -18
  64. package/dist/client/client.js +120 -120
  65. package/dist/client/index.d.ts +1 -1
  66. package/dist/client/index.js +1 -1
  67. package/dist/client/sse.d.ts +13 -0
  68. package/dist/client/sse.js +38 -44
  69. package/dist/cloudflare/alarm-scheduler.d.ts +12 -2
  70. package/dist/cloudflare/alarm-scheduler.js +34 -7
  71. package/dist/cloudflare/durable-object.d.ts +10 -2
  72. package/dist/cloudflare/durable-object.js +59 -28
  73. package/dist/cloudflare/event-store.d.ts +7 -2
  74. package/dist/cloudflare/event-store.js +8 -7
  75. package/dist/cloudflare/index.d.ts +2 -2
  76. package/dist/cloudflare/index.js +2 -2
  77. package/dist/cloudflare/router.d.ts +1 -1
  78. package/dist/cloudflare/router.js +15 -16
  79. package/dist/core/catalog.d.ts +3 -3
  80. package/dist/core/catalog.js +2 -5
  81. package/dist/core/coalesce.js +5 -3
  82. package/dist/core/compose.d.ts +2 -1
  83. package/dist/core/compose.js +19 -2
  84. package/dist/core/debugger-host.d.ts +16 -0
  85. package/dist/core/debugger-host.js +1 -0
  86. package/dist/core/definition-store.d.ts +11 -0
  87. package/dist/core/definition-store.js +6 -0
  88. package/dist/core/effects.d.ts +127 -15
  89. package/dist/core/effects.js +89 -17
  90. package/dist/core/envelope.d.ts +27 -7
  91. package/dist/core/envelope.js +20 -6
  92. package/dist/core/event-codec.d.ts +4 -2
  93. package/dist/core/event-codec.js +10 -5
  94. package/dist/core/event-index.js +1 -3
  95. package/dist/core/fold.d.ts +3 -0
  96. package/dist/core/fold.js +171 -24
  97. package/dist/core/ids.d.ts +5 -0
  98. package/dist/core/ids.js +35 -2
  99. package/dist/core/index.d.ts +7 -1
  100. package/dist/core/index.js +6 -1
  101. package/dist/core/kind.d.ts +22 -0
  102. package/dist/core/kind.js +17 -0
  103. package/dist/core/limits.d.ts +34 -0
  104. package/dist/core/limits.js +46 -0
  105. package/dist/core/match.d.ts +1 -0
  106. package/dist/core/match.js +10 -1
  107. package/dist/core/module-scope.d.ts +22 -20
  108. package/dist/core/module-scope.js +38 -28
  109. package/dist/core/module.d.ts +18 -5
  110. package/dist/core/module.js +4 -0
  111. package/dist/core/projection.d.ts +13 -13
  112. package/dist/core/projection.js +9 -5
  113. package/dist/core/protocol.d.ts +68 -11
  114. package/dist/core/protocol.js +82 -11
  115. package/dist/core/published.d.ts +43 -0
  116. package/dist/core/published.js +44 -0
  117. package/dist/core/replay.d.ts +312 -3
  118. package/dist/core/replay.js +11 -1
  119. package/dist/core/schema.d.ts +11 -20
  120. package/dist/core/schema.js +63 -80
  121. package/dist/core/serialize.js +2 -4
  122. package/dist/core/snapshot-store.d.ts +6 -1
  123. package/dist/core/snapshot-store.js +15 -2
  124. package/dist/core/snapshots.d.ts +5 -1
  125. package/dist/core/snapshots.js +10 -15
  126. package/dist/core/sqlite-store.js +95 -11
  127. package/dist/core/state.d.ts +142 -4
  128. package/dist/core/state.js +78 -0
  129. package/dist/core/store.d.ts +38 -3
  130. package/dist/core/store.js +77 -22
  131. package/dist/core/thread.d.ts +10 -9
  132. package/dist/core/types.d.ts +5 -8
  133. package/dist/core/types.js +28 -24
  134. package/dist/core/validate-event.js +14 -4
  135. package/dist/core/worker-callback.d.ts +17 -0
  136. package/dist/core/worker-callback.js +1 -0
  137. package/dist/debugger/app/assets/index-B3Ah8WAE.css +2 -0
  138. package/dist/debugger/app/assets/index-Qb371t_O.js +22 -0
  139. package/dist/debugger/app/assets/rolldown-runtime-CbXtAM7H.js +1 -0
  140. package/dist/debugger/app/assets/routes-CjtEHgPQ.js +47 -0
  141. package/dist/debugger/app/index.html +14 -0
  142. package/dist/debugger/components/composer.d.ts +17 -0
  143. package/dist/debugger/components/composer.js +25 -0
  144. package/dist/debugger/components/debugger-split.d.ts +2 -1
  145. package/dist/debugger/components/debugger-split.js +2 -2
  146. package/dist/debugger/components/event-inspector.d.ts +2 -1
  147. package/dist/debugger/components/event-inspector.js +25 -16
  148. package/dist/debugger/components/event-stream.d.ts +2 -1
  149. package/dist/debugger/components/event-stream.js +14 -74
  150. package/dist/debugger/components/follow-list.d.ts +19 -0
  151. package/dist/debugger/components/follow-list.js +96 -0
  152. package/dist/debugger/components/json-tree.d.ts +11 -0
  153. package/dist/debugger/components/json-tree.js +120 -0
  154. package/dist/debugger/components/json-view.js +2 -2
  155. package/dist/debugger/components/run-tree.js +53 -20
  156. package/dist/debugger/components/status-dot.d.ts +4 -0
  157. package/dist/debugger/components/status-dot.js +6 -2
  158. package/dist/debugger/context.d.ts +14 -0
  159. package/dist/debugger/context.js +17 -0
  160. package/dist/debugger/contracts.d.ts +7 -4
  161. package/dist/debugger/events/builtin-families.d.ts +3 -0
  162. package/dist/debugger/events/builtin-families.js +9 -0
  163. package/dist/debugger/events/catalog.d.ts +3 -4
  164. package/dist/debugger/events/catalog.js +27 -17
  165. package/dist/debugger/events/protocol-summarize.d.ts +1 -2
  166. package/dist/debugger/events/protocol-summarize.js +42 -24
  167. package/dist/debugger/index.d.ts +20 -6
  168. package/dist/debugger/index.js +19 -5
  169. package/dist/debugger/lib/cn.d.ts +1 -0
  170. package/dist/debugger/lib/cn.js +3 -0
  171. package/dist/debugger/lib/event-meta.d.ts +2 -3
  172. package/dist/debugger/lib/event-meta.js +6 -12
  173. package/dist/debugger/lib/json.d.ts +25 -0
  174. package/dist/debugger/lib/json.js +87 -0
  175. package/dist/debugger/lib/payload.d.ts +7 -0
  176. package/dist/debugger/lib/payload.js +11 -0
  177. package/dist/debugger/lib/time.d.ts +3 -0
  178. package/dist/debugger/lib/time.js +24 -0
  179. package/dist/debugger/plugin.d.ts +36 -0
  180. package/dist/debugger/plugin.js +14 -0
  181. package/dist/debugger/server/index.d.ts +14 -0
  182. package/dist/debugger/server/index.js +114 -0
  183. package/dist/debugger/shell/debugger-shell.d.ts +15 -0
  184. package/dist/debugger/shell/debugger-shell.js +43 -0
  185. package/dist/debugger/shell/definition-key.d.ts +3 -0
  186. package/dist/debugger/shell/definition-key.js +4 -0
  187. package/dist/debugger/shell/events-pane.d.ts +12 -0
  188. package/dist/debugger/shell/events-pane.js +25 -0
  189. package/dist/debugger/shell/projections-panel.d.ts +4 -0
  190. package/dist/debugger/shell/projections-panel.js +59 -0
  191. package/dist/debugger/shell/run-controls.d.ts +4 -0
  192. package/dist/debugger/shell/run-controls.js +72 -0
  193. package/dist/debugger/shell/run-search.d.ts +6 -0
  194. package/dist/debugger/shell/run-search.js +102 -0
  195. package/dist/debugger/shell/run-type-menu.d.ts +6 -0
  196. package/dist/debugger/shell/run-type-menu.js +11 -0
  197. package/dist/debugger/shell/schema-form.d.ts +31 -0
  198. package/dist/debugger/shell/schema-form.js +117 -0
  199. package/dist/debugger/shell/start-form.d.ts +3 -0
  200. package/dist/debugger/shell/start-form.js +75 -0
  201. package/dist/debugger/shell/use-start-run.d.ts +14 -0
  202. package/dist/debugger/shell/use-start-run.js +32 -0
  203. package/dist/debugger/shell/workspace.d.ts +6 -0
  204. package/dist/debugger/shell/workspace.js +15 -0
  205. package/dist/debugger/styles.css +111 -27
  206. package/dist/debugger/ui/button.d.ts +24 -0
  207. package/dist/debugger/ui/button.js +24 -0
  208. package/dist/debugger/ui/command.d.ts +9 -0
  209. package/dist/debugger/ui/command.js +24 -0
  210. package/dist/debugger/ui/dropdown-menu.d.ts +13 -0
  211. package/dist/debugger/ui/dropdown-menu.js +37 -0
  212. package/dist/debugger/ui/panel.d.ts +14 -0
  213. package/dist/debugger/ui/panel.js +16 -0
  214. package/dist/debugger/ui/popover.d.ts +7 -0
  215. package/dist/debugger/ui/popover.js +17 -0
  216. package/dist/debugger/ui/resizable.d.ts +7 -0
  217. package/dist/debugger/ui/resizable.js +14 -0
  218. package/dist/debugger/ui/scroll-area.d.ts +2 -1
  219. package/dist/debugger/ui/scroll-area.js +5 -3
  220. package/dist/debugger/ui/select.d.ts +10 -0
  221. package/dist/debugger/ui/select.js +21 -0
  222. package/dist/debugger/ui/tabs.js +2 -2
  223. package/dist/debugger/ui/textarea.d.ts +3 -0
  224. package/dist/debugger/ui/textarea.js +7 -0
  225. package/dist/evaluator/definitions-store.d.ts +11 -0
  226. package/dist/evaluator/definitions-store.js +8 -0
  227. package/dist/evaluator/definitions.d.ts +18 -0
  228. package/dist/evaluator/definitions.js +4 -0
  229. package/dist/evaluator/effects.d.ts +226 -0
  230. package/dist/evaluator/effects.js +132 -0
  231. package/dist/evaluator/evaluator.d.ts +24 -0
  232. package/dist/evaluator/evaluator.js +67 -0
  233. package/dist/evaluator/events.d.ts +59 -0
  234. package/dist/evaluator/events.js +21 -0
  235. package/dist/evaluator/index.d.ts +11 -0
  236. package/dist/evaluator/index.js +11 -0
  237. package/dist/evaluator/module.d.ts +606 -0
  238. package/dist/evaluator/module.js +25 -0
  239. package/dist/evaluator/projections.d.ts +50 -0
  240. package/dist/evaluator/projections.js +20 -0
  241. package/dist/evaluator/scope.d.ts +59 -0
  242. package/dist/evaluator/scope.js +7 -0
  243. package/dist/evaluator/signals.d.ts +3 -0
  244. package/dist/evaluator/signals.js +17 -0
  245. package/dist/evaluator/threads.d.ts +62 -0
  246. package/dist/evaluator/threads.js +117 -0
  247. package/dist/evaluator/types.d.ts +125 -0
  248. package/dist/evaluator/types.js +51 -0
  249. package/dist/projectors/delivery.d.ts +83 -0
  250. package/dist/projectors/delivery.js +182 -0
  251. package/dist/projectors/index-model.d.ts +1 -1
  252. package/dist/projectors/index-model.js +4 -6
  253. package/dist/projectors/index.d.ts +2 -0
  254. package/dist/projectors/index.js +2 -0
  255. package/dist/projectors/memory.d.ts +1 -1
  256. package/dist/projectors/memory.js +10 -8
  257. package/dist/projectors/postgres.js +44 -58
  258. package/dist/projectors/projector.d.ts +17 -0
  259. package/dist/projectors/projector.js +26 -1
  260. package/dist/projectors/sqlite-delivery.d.ts +4 -0
  261. package/dist/projectors/sqlite-delivery.js +147 -0
  262. package/dist/projectors/sqlite.js +23 -23
  263. package/dist/projectors/with-projectors.d.ts +7 -2
  264. package/dist/projectors/with-projectors.js +27 -28
  265. package/dist/react/notify.js +11 -8
  266. package/dist/react/react.d.ts +2 -3
  267. package/dist/react/react.js +14 -30
  268. package/dist/react/sse.d.ts +1 -1
  269. package/dist/react/sse.js +1 -1
  270. package/dist/react/store.d.ts +3 -3
  271. package/dist/react/store.js +109 -119
  272. package/dist/runtime/cancellation.d.ts +16 -0
  273. package/dist/runtime/cancellation.js +129 -0
  274. package/dist/runtime/deadline-transition-planner.d.ts +31 -0
  275. package/dist/runtime/deadline-transition-planner.js +139 -0
  276. package/dist/runtime/dispatch-effect.d.ts +3 -2
  277. package/dist/runtime/dispatch-effect.js +7 -13
  278. package/dist/runtime/errors.d.ts +51 -0
  279. package/dist/runtime/errors.js +39 -0
  280. package/dist/runtime/{runtime-helpers.d.ts → helpers.d.ts} +24 -4
  281. package/dist/runtime/{runtime-helpers.js → helpers.js} +56 -19
  282. package/dist/runtime/index.d.ts +3 -3
  283. package/dist/runtime/index.js +3 -3
  284. package/dist/runtime/ingress.d.ts +15 -0
  285. package/dist/runtime/ingress.js +42 -0
  286. package/dist/runtime/local-effect-executor.d.ts +21 -0
  287. package/dist/runtime/local-effect-executor.js +137 -0
  288. package/dist/runtime/looms.d.ts +30 -4
  289. package/dist/runtime/looms.js +57 -37
  290. package/dist/runtime/observer.d.ts +45 -0
  291. package/dist/runtime/observer.js +11 -0
  292. package/dist/runtime/repository.d.ts +55 -0
  293. package/dist/runtime/repository.js +293 -0
  294. package/dist/runtime/retry-policy.d.ts +16 -0
  295. package/dist/runtime/retry-policy.js +29 -0
  296. package/dist/runtime/run-execution-context.d.ts +30 -0
  297. package/dist/runtime/run-execution-context.js +56 -0
  298. package/dist/runtime/run-start.d.ts +4 -0
  299. package/dist/runtime/run-start.js +44 -0
  300. package/dist/runtime/runtime.d.ts +6 -87
  301. package/dist/runtime/runtime.js +248 -470
  302. package/dist/runtime/server/auth.d.ts +41 -0
  303. package/dist/runtime/server/auth.js +45 -0
  304. package/dist/runtime/server/debugger-host.d.ts +11 -0
  305. package/dist/runtime/server/debugger-host.js +32 -0
  306. package/dist/runtime/server/fetch-handler.d.ts +18 -0
  307. package/dist/runtime/server/fetch-handler.js +62 -0
  308. package/dist/runtime/server/http.d.ts +38 -0
  309. package/dist/runtime/server/http.js +66 -0
  310. package/dist/runtime/server/route-table.d.ts +55 -0
  311. package/dist/runtime/server/route-table.js +74 -0
  312. package/dist/runtime/server/routes/definitions.d.ts +3 -0
  313. package/dist/runtime/server/routes/definitions.js +20 -0
  314. package/dist/runtime/server/routes/effects.d.ts +3 -0
  315. package/dist/runtime/server/routes/effects.js +59 -0
  316. package/dist/runtime/server/routes/operations.d.ts +3 -0
  317. package/dist/runtime/server/routes/operations.js +14 -0
  318. package/dist/runtime/server/routes/runs.d.ts +3 -0
  319. package/dist/runtime/server/routes/runs.js +199 -0
  320. package/dist/runtime/server.d.ts +4 -8
  321. package/dist/runtime/server.js +6 -211
  322. package/dist/runtime/sse.d.ts +2 -1
  323. package/dist/runtime/sse.js +24 -17
  324. package/dist/runtime/types.d.ts +117 -0
  325. package/dist/runtime/types.js +1 -0
  326. package/dist/runtime/wake-engine.d.ts +20 -0
  327. package/dist/runtime/wake-engine.js +311 -0
  328. package/dist/runtime/wake-scheduler.d.ts +3 -4
  329. package/dist/runtime/wake-scheduler.js +25 -31
  330. package/dist/runtime/worker-callbacks.d.ts +16 -0
  331. package/dist/runtime/worker-callbacks.js +167 -0
  332. package/dist/runtime/worker-effect-dispatch.d.ts +36 -0
  333. package/dist/runtime/worker-effect-dispatch.js +126 -0
  334. package/dist/s2/config.d.ts +1 -1
  335. package/dist/s2/config.js +1 -1
  336. package/dist/s2/lite.js +70 -82
  337. package/dist/s2/pagination.d.ts +15 -0
  338. package/dist/s2/pagination.js +36 -0
  339. package/dist/s2/projector.js +6 -5
  340. package/dist/s2/snapshot-store.js +188 -161
  341. package/dist/s2/store.d.ts +3 -21
  342. package/dist/s2/store.js +71 -165
  343. package/dist/testing/index.d.ts +15 -0
  344. package/dist/testing/index.js +53 -16
  345. package/dist/workflow/debugger/index.d.ts +4 -0
  346. package/dist/workflow/debugger/index.js +11 -0
  347. package/dist/workflow/debugger/nodes-view.d.ts +3 -0
  348. package/dist/workflow/debugger/nodes-view.js +13 -0
  349. package/dist/workflow/debugger/summarize.d.ts +2 -0
  350. package/dist/workflow/debugger/summarize.js +30 -0
  351. package/dist/workflow/definitions-store.d.ts +2 -3
  352. package/dist/workflow/definitions-store.js +2 -2
  353. package/dist/workflow/definitions.d.ts +78 -22
  354. package/dist/workflow/definitions.js +80 -14
  355. package/dist/workflow/effects.d.ts +453 -107
  356. package/dist/workflow/effects.js +214 -115
  357. package/dist/workflow/events.d.ts +149 -5
  358. package/dist/workflow/events.js +27 -5
  359. package/dist/workflow/graph.d.ts +56 -0
  360. package/dist/workflow/graph.js +207 -0
  361. package/dist/workflow/index.d.ts +1 -0
  362. package/dist/workflow/index.js +1 -0
  363. package/dist/workflow/module.d.ts +1047 -266
  364. package/dist/workflow/projections.d.ts +14 -12
  365. package/dist/workflow/projections.js +30 -8
  366. package/dist/workflow/scope.d.ts +146 -4
  367. package/dist/workflow/threads.d.ts +383 -155
  368. package/dist/workflow/threads.js +308 -110
  369. package/package.json +26 -2
  370. package/dist/debugger/events/domain-summarize.d.ts +0 -3
  371. package/dist/debugger/events/domain-summarize.js +0 -107
  372. package/dist/debugger/events/family.d.ts +0 -8
  373. package/dist/debugger/events/family.js +0 -44
  374. package/dist/runtime/events-api.d.ts +0 -4
  375. package/dist/runtime/events-api.js +0 -84
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Looms debugger</title>
7
+ <script type="module" crossorigin src="./assets/index-Qb371t_O.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-CbXtAM7H.js">
9
+ <link rel="stylesheet" crossorigin href="./assets/index-B3Ah8WAE.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Message input with one primary action. Enter sends, Shift+Enter adds a newline,
4
+ * and `toolbar` holds secondary controls such as delivery mode.
5
+ */
6
+ export declare function Composer({ value, onChange, onSubmit, pending, disabled, placeholder, submitLabel, toolbar, error, className, }: {
7
+ value: string;
8
+ onChange: (value: string) => void;
9
+ onSubmit: () => void;
10
+ pending?: boolean;
11
+ disabled?: boolean;
12
+ placeholder?: string;
13
+ submitLabel?: string;
14
+ toolbar?: ReactNode;
15
+ error?: string | null;
16
+ className?: string;
17
+ }): import("react").JSX.Element;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ArrowUp, LoaderCircle } from 'lucide-react';
3
+ import { cn } from '../lib/cn.js';
4
+ import { Button } from '../ui/button.js';
5
+ import { Kbd } from '../ui/panel.js';
6
+ /**
7
+ * Message input with one primary action. Enter sends, Shift+Enter adds a newline,
8
+ * and `toolbar` holds secondary controls such as delivery mode.
9
+ */
10
+ export function Composer({ value, onChange, onSubmit, pending = false, disabled = false, placeholder = 'Message', submitLabel = 'Send', toolbar, error, className, }) {
11
+ const canSubmit = !pending && !disabled && value.trim().length > 0;
12
+ return (_jsxs("form", { className: cn('border-border shrink-0 border-t p-3', className), onSubmit: (event) => {
13
+ event.preventDefault();
14
+ if (canSubmit) {
15
+ onSubmit();
16
+ }
17
+ }, children: [_jsxs("div", { className: "border-input bg-card focus-within:border-ring/70 focus-within:ring-ring/25 rounded-lg border transition-[border-color,box-shadow] duration-150 focus-within:ring-[3px]", children: [_jsx("textarea", { "data-slot": "composer-input", value: value, rows: 1, placeholder: placeholder, "aria-label": placeholder, disabled: disabled, onChange: (event) => onChange(event.target.value), onKeyDown: (event) => {
18
+ if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing) {
19
+ event.preventDefault();
20
+ if (canSubmit) {
21
+ onSubmit();
22
+ }
23
+ }
24
+ }, className: "placeholder:text-muted-foreground block field-sizing-content max-h-48 min-h-11 w-full resize-none bg-transparent px-3 pt-2.5 pb-1 text-[13px] leading-relaxed outline-none disabled:cursor-not-allowed disabled:opacity-50" }), _jsxs("div", { className: "flex h-10 items-center gap-1 pr-1.5 pl-1.5", children: [toolbar, _jsxs("span", { className: "text-muted-foreground ml-auto hidden items-center gap-1 pr-1 text-[10px] @sm:flex", children: [_jsx(Kbd, { children: "\u21B5" }), submitLabel.toLowerCase(), _jsx(Kbd, { className: "ml-1.5", children: "\u21E7\u21B5" }), "newline"] }), _jsx(Button, { type: "submit", size: "icon-sm", "aria-label": submitLabel, disabled: !canSubmit, className: "ml-auto rounded-full @sm:ml-0", children: pending ? _jsx(LoaderCircle, { className: "animate-spin" }) : _jsx(ArrowUp, {}) })] })] }), error ? _jsx("p", { className: "text-status-failed mt-2 px-1 text-[11px]", children: error }) : null] }));
25
+ }
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { ThreadTree } from '@swirls/looms/core';
2
+ import type { ThreadNode, ThreadTree } from '@swirls/looms/core';
3
3
  import type { DebuggerEvent, EventStreamCatalog, ReplayLoader } from '../contracts';
4
+ export declare function findThreadName(node: ThreadNode | null, threadId: string | undefined): string | undefined;
4
5
  export declare function DebuggerSplit<TEvent extends DebuggerEvent>({ runId, runStatus, tree, events, eventCounts, startedAt, selectedThread, onSelectThread, selectedSeq, onSelectSeq, catalog, loadReplayStep, renderInspector, orientation, showTreeHeading, className, treeClassName, streamClassName, }: {
5
6
  runId: string;
6
7
  runStatus: string;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../lib/cn.js';
3
3
  import { EventStream } from './event-stream.js';
4
4
  import { RunTree } from './run-tree.js';
5
- function findThreadName(node, threadId) {
5
+ export function findThreadName(node, threadId) {
6
6
  if (!node || !threadId) {
7
7
  return undefined;
8
8
  }
@@ -21,5 +21,5 @@ export function DebuggerSplit({ runId, runStatus, tree, events, eventCounts, sta
21
21
  const vertical = orientation === 'vertical';
22
22
  return (_jsxs("div", { className: cn('grid h-full min-h-0', vertical
23
23
  ? 'grid-rows-[minmax(0,38%)_minmax(0,1fr)]'
24
- : 'grid-cols-1 min-[36rem]:grid-cols-[11rem_minmax(0,1fr)]', className), children: [_jsx("section", { "aria-label": "Run tree", className: cn('border-border min-h-0 overflow-auto border-b py-2', !vertical && 'min-[36rem]:border-r min-[36rem]:border-b-0', treeClassName), children: _jsx(RunTree, { showHeading: showTreeHeading, runId: runId, runStatus: runStatus, tree: tree, eventCounts: eventCounts, totalEventCount: events.length, selectedThread: selectedThread, onSelectThread: onSelectThread }) }), _jsx("section", { className: cn('min-h-0 overflow-hidden', streamClassName), children: _jsx(EventStream, { events: events, startedAt: startedAt, threadId: selectedThread, threadLabel: findThreadName(tree.root, selectedThread), selectedSeq: selectedSeq, onSelectSeq: onSelectSeq, catalog: catalog, loadReplayStep: loadReplayStep, renderInspector: renderInspector }) })] }));
24
+ : 'grid-cols-1 min-[36rem]:grid-cols-[14rem_minmax(0,1fr)]', className), children: [_jsx("section", { "aria-label": "Run tree", className: cn('border-border min-h-0 overflow-hidden border-b', !vertical && 'min-[36rem]:border-r min-[36rem]:border-b-0', treeClassName), children: _jsx(RunTree, { showHeading: showTreeHeading, runId: runId, runStatus: runStatus, tree: tree, eventCounts: eventCounts, totalEventCount: events.length, selectedThread: selectedThread, onSelectThread: onSelectThread }) }), _jsx("section", { className: cn('min-h-0 overflow-hidden', streamClassName), children: _jsx(EventStream, { events: events, startedAt: startedAt, threadId: selectedThread, threadLabel: findThreadName(tree.root, selectedThread), onClearThread: selectedThread ? () => onSelectThread(undefined) : undefined, selectedSeq: selectedSeq, onSelectSeq: onSelectSeq, catalog: catalog, loadReplayStep: loadReplayStep, renderInspector: renderInspector }) })] }));
25
25
  }
@@ -1,5 +1,6 @@
1
1
  import type { DebuggerEvent, ReplayLoader } from '../contracts';
2
- export declare function EventInspector({ event, loadReplayStep, }: {
2
+ export declare function EventInspector({ event, loadReplayStep, onClose, }: {
3
3
  event: DebuggerEvent;
4
4
  loadReplayStep?: ReplayLoader;
5
+ onClose?: () => void;
5
6
  }): import("react").JSX.Element;
@@ -1,9 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { X } from 'lucide-react';
2
3
  import { useEffect, useState } from 'react';
3
4
  import { asJson } from '@swirls/looms/core';
5
+ import { Button } from '../ui/button.js';
6
+ import { ScrollArea } from '../ui/scroll-area.js';
4
7
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs.js';
5
- import { JsonView } from './json-view.js';
6
- export function EventInspector({ event, loadReplayStep, }) {
8
+ import { JsonTree } from './json-tree.js';
9
+ function Pane({ value, children }) {
10
+ return (_jsx(TabsContent, { value: value, className: "min-h-0", children: _jsx(ScrollArea, { className: "h-full", children: _jsx("div", { className: "px-3 pb-3", children: children }) }) }));
11
+ }
12
+ function StepJson({ step, pick, }) {
13
+ return step ? (_jsx(JsonTree, { value: pick(step), defaultExpandDepth: 2 })) : (_jsx("p", { className: "text-muted-foreground text-xs", children: "Loading\u2026" }));
14
+ }
15
+ export function EventInspector({ event, loadReplayStep, onClose, }) {
7
16
  const [step, setStep] = useState(null);
8
17
  const canReplay = loadReplayStep !== undefined;
9
18
  useEffect(() => {
@@ -21,18 +30,18 @@ export function EventInspector({ event, loadReplayStep, }) {
21
30
  cancelled = true;
22
31
  };
23
32
  }, [event.runId, event.seq, loadReplayStep]);
24
- return (_jsxs("div", { className: "border-border flex max-h-[45%] min-h-0 shrink-0 flex-col border-t", children: [_jsxs("div", { className: "text-muted-foreground flex items-center gap-2 px-3 py-1.5 text-[11px]", children: [_jsx("span", { className: "font-mono", children: event.seq }), _jsx("span", { className: "text-foreground truncate font-mono", children: event.type })] }), _jsxs(Tabs, { defaultValue: "payload", className: "min-h-0 flex-1 px-3 pb-2", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "payload", children: "Payload" }), _jsx(TabsTrigger, { value: "envelope", children: "Envelope" }), canReplay ? _jsx(TabsTrigger, { value: "before", children: "Before" }) : null, canReplay ? _jsx(TabsTrigger, { value: "after", children: "After" }) : null, canReplay ? _jsx(TabsTrigger, { value: "effects", children: "Effects" }) : null] }), _jsx(TabsContent, { value: "payload", className: "overflow-auto", children: _jsx(JsonView, { value: event.payload }) }), _jsx(TabsContent, { value: "envelope", className: "overflow-auto", children: _jsx(JsonView, { value: asJson({
25
- id: event.id,
26
- runId: event.runId,
27
- seq: event.seq,
28
- ts: event.ts,
29
- type: event.type,
30
- threadId: event.threadId,
31
- parentThreadId: event.parentThreadId ?? null,
32
- causationId: event.causationId ?? null,
33
- correlationId: event.correlationId ?? null,
34
- effectId: event.effectId ?? null,
35
- origin: event.origin,
36
- ephemeral: event.ephemeral ?? false,
37
- }) }) }), canReplay ? (_jsx(TabsContent, { value: "before", className: "overflow-auto", children: step ? (_jsx(JsonView, { value: asJson(step.before) })) : (_jsx("p", { className: "text-muted-foreground text-xs", children: "Loading\u2026" })) })) : null, canReplay ? (_jsx(TabsContent, { value: "after", className: "overflow-auto", children: step ? (_jsx(JsonView, { value: asJson(step.after) })) : (_jsx("p", { className: "text-muted-foreground text-xs", children: "Loading\u2026" })) })) : null, canReplay ? (_jsx(TabsContent, { value: "effects", className: "overflow-auto", children: step ? (_jsx(JsonView, { value: asJson(step.effects) })) : (_jsx("p", { className: "text-muted-foreground text-xs", children: "Loading\u2026" })) })) : null] })] }));
33
+ return (_jsx("div", { className: "border-border bg-sidebar flex h-[45%] min-h-40 shrink-0 flex-col border-t", children: _jsxs(Tabs, { defaultValue: "payload", className: "min-h-0 flex-1 gap-0", children: [_jsxs("div", { className: "flex h-10 shrink-0 items-center gap-2 pr-2 pl-3", children: [_jsxs("span", { className: "text-muted-foreground font-mono text-[11px] tabular-nums", children: ["#", event.seq] }), _jsx("span", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: event.type }), _jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "payload", children: "Payload" }), _jsx(TabsTrigger, { value: "envelope", children: "Envelope" }), canReplay ? _jsx(TabsTrigger, { value: "state", children: "State" }) : null, canReplay ? _jsx(TabsTrigger, { value: "effects", children: "Effects" }) : null] }), onClose ? (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Close inspector", onClick: onClose, children: _jsx(X, {}) })) : null] }), _jsx(Pane, { value: "payload", children: _jsx(JsonTree, { value: event.payload, defaultExpandDepth: 2 }) }), _jsx(Pane, { value: "envelope", children: _jsx(JsonTree, { value: asJson({
34
+ id: event.id,
35
+ runId: event.runId,
36
+ seq: event.seq,
37
+ ts: event.ts,
38
+ type: event.type,
39
+ threadId: event.threadId,
40
+ parentThreadId: event.parentThreadId ?? null,
41
+ causationId: event.causationId ?? null,
42
+ correlationId: event.correlationId ?? null,
43
+ effectId: event.effectId ?? null,
44
+ origin: event.origin,
45
+ ephemeral: event.ephemeral ?? false,
46
+ }) }) }), canReplay ? (_jsx(Pane, { value: "state", children: _jsxs("div", { className: "grid gap-3", children: [_jsxs("section", { children: [_jsx("h4", { className: "text-muted-foreground mb-1 text-[11px] font-medium", children: "Before" }), _jsx(StepJson, { step: step, pick: (loaded) => asJson(loaded.before) })] }), _jsxs("section", { children: [_jsx("h4", { className: "text-muted-foreground mb-1 text-[11px] font-medium", children: "After" }), _jsx(StepJson, { step: step, pick: (loaded) => asJson(loaded.after) })] })] }) })) : null, canReplay ? (_jsx(Pane, { value: "effects", children: _jsx(StepJson, { step: step, pick: (loaded) => asJson(loaded.effects) }) })) : null] }) }));
38
47
  }
@@ -1,10 +1,11 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { DebuggerEvent, EventStreamCatalog, ReplayLoader } from '../contracts';
3
- export declare function EventStream<TEvent extends DebuggerEvent>({ events, startedAt, threadId, threadLabel, selectedSeq, onSelectSeq, catalog, loadReplayStep, renderInspector, }: {
3
+ export declare function EventStream<TEvent extends DebuggerEvent>({ events, startedAt, threadId, threadLabel, onClearThread, selectedSeq, onSelectSeq, catalog, loadReplayStep, renderInspector, }: {
4
4
  events: readonly TEvent[];
5
5
  startedAt?: number;
6
6
  threadId?: string;
7
7
  threadLabel?: string;
8
+ onClearThread?: () => void;
8
9
  selectedSeq?: number;
9
10
  onSelectSeq: (seq: number | undefined) => void;
10
11
  catalog?: EventStreamCatalog<TEvent>;
@@ -1,25 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useVirtualizer } from '@tanstack/react-virtual';
3
- import { Check, Search, SlidersHorizontal } from 'lucide-react';
4
- import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui';
5
- import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { ArrowDownToLine, Search, SlidersHorizontal, X } from 'lucide-react';
3
+ import { useCallback, useMemo, useState } from 'react';
6
4
  import { defaultEventCatalog } from '../events/catalog.js';
7
5
  import { cn, shortId } from '../lib/cn.js';
8
6
  import { getEventMeta } from '../lib/event-meta.js';
9
7
  import { useVisibleEvents } from '../lib/visible-events.js';
10
- import { Checkbox } from '../ui/checkbox.js';
8
+ import { Button } from '../ui/button.js';
9
+ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuTrigger, } from '../ui/dropdown-menu.js';
11
10
  import { Input } from '../ui/input.js';
12
- import { ScrollArea } from '../ui/scroll-area.js';
11
+ import { PanelHeader, PanelTitle } from '../ui/panel.js';
13
12
  import { EventInspector } from './event-inspector.js';
14
- const ROW_ESTIMATE = 28;
15
- const FOLLOW_THRESHOLD = 24;
16
- export function EventStream({ events, startedAt, threadId, threadLabel, selectedSeq, onSelectSeq, catalog, loadReplayStep, renderInspector, }) {
13
+ import { FollowList } from './follow-list.js';
14
+ const ROW_ESTIMATE = 26;
15
+ export function EventStream({ events, startedAt, threadId, threadLabel, onClearThread, selectedSeq, onSelectSeq, catalog, loadReplayStep, renderInspector, }) {
17
16
  const eventCatalog = catalog ?? defaultEventCatalog;
18
17
  const [query, setQuery] = useState('');
19
18
  const [follow, setFollow] = useState(true);
20
19
  const [families, setFamilies] = useState(() => new Set(eventCatalog.families));
21
- const parentRef = useRef(null);
22
- const followLock = useRef(false);
23
20
  const selected = useMemo(() => {
24
21
  if (selectedSeq === undefined) {
25
22
  return undefined;
@@ -37,53 +34,6 @@ export function EventStream({ events, startedAt, threadId, threadLabel, selected
37
34
  threadId,
38
35
  catalog: eventCatalog,
39
36
  });
40
- const virtualizer = useVirtualizer({
41
- count: visible.length,
42
- getScrollElement: () => parentRef.current,
43
- estimateSize: () => ROW_ESTIMATE,
44
- overscan: 16,
45
- paddingStart: 4,
46
- paddingEnd: 4,
47
- getItemKey: (index) => visible[index]?.id ?? index,
48
- // Avoid flushSync during ref measurement / follow-scroll under high event rates.
49
- useFlushSync: false,
50
- });
51
- useEffect(() => {
52
- if (!follow || visible.length === 0) {
53
- return () => undefined;
54
- }
55
- followLock.current = true;
56
- let timer;
57
- const rafId = requestAnimationFrame(() => {
58
- virtualizer.scrollToIndex(visible.length - 1, { align: 'end' });
59
- timer = setTimeout(() => {
60
- followLock.current = false;
61
- }, 50);
62
- });
63
- return () => {
64
- cancelAnimationFrame(rafId);
65
- if (timer !== undefined) {
66
- clearTimeout(timer);
67
- }
68
- followLock.current = false;
69
- };
70
- }, [follow, visible.length, virtualizer]);
71
- const onScroll = useCallback(() => {
72
- if (followLock.current) {
73
- return;
74
- }
75
- const el = parentRef.current;
76
- if (!el) {
77
- return;
78
- }
79
- const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < FOLLOW_THRESHOLD;
80
- if (follow && !atBottom) {
81
- setFollow(false);
82
- }
83
- else if (!follow && atBottom) {
84
- setFollow(true);
85
- }
86
- }, [follow]);
87
37
  const toggleFamily = useCallback((family) => {
88
38
  setFamilies((prev) => {
89
39
  const next = new Set(prev);
@@ -97,23 +47,13 @@ export function EventStream({ events, startedAt, threadId, threadLabel, selected
97
47
  });
98
48
  }, []);
99
49
  const hiddenFamilyCount = eventCatalog.families.length - families.size;
100
- return (_jsxs("div", { className: "debugger-event-stream flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "border-border flex min-h-10 items-center gap-1.5 border-b px-2 py-1.5", children: [_jsxs("div", { className: "relative min-w-0 flex-1", children: [_jsx(Search, { "aria-hidden": true, className: "text-muted-foreground pointer-events-none absolute top-1/2 left-2 size-3 -translate-y-1/2" }), _jsx(Input, { value: query, onChange: (event) => setQuery(event.target.value), placeholder: "Search events", "aria-label": "Search events", className: "h-7 w-full pl-7 text-xs" })] }), _jsxs(DropdownMenuPrimitive.Root, { children: [_jsx(DropdownMenuPrimitive.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: "border-input text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring/50 relative inline-flex size-7 shrink-0 items-center justify-center rounded-md border outline-none focus-visible:ring-[3px]", "aria-label": "Filter event families", children: [_jsx(SlidersHorizontal, { className: "size-3.5" }), hiddenFamilyCount > 0 ? (_jsx("span", { className: "bg-foreground text-background absolute -top-1 -right-1 flex size-3.5 items-center justify-center rounded-full font-mono text-[8px]", children: hiddenFamilyCount })) : null] }) }), _jsx(DropdownMenuPrimitive.Portal, { children: _jsxs(DropdownMenuPrimitive.Content, { align: "end", sideOffset: 5, className: "bg-background text-foreground border-border z-50 min-w-36 rounded-md border p-1 shadow-lg", children: [_jsx(DropdownMenuPrimitive.Label, { className: "text-muted-foreground px-2 py-1 text-[10px] font-medium tracking-wide uppercase", children: "Event families" }), eventCatalog.families.map((family) => (_jsxs(DropdownMenuPrimitive.CheckboxItem, { checked: families.has(family), onCheckedChange: () => toggleFamily(family), onSelect: (event) => event.preventDefault(), className: "focus:bg-accent relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-7 font-mono text-xs outline-none select-none", children: [_jsx("span", { className: "absolute left-2 flex size-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Check, { className: "size-3" }) }) }), family] }, family)))] }) })] }), _jsxs("label", { className: "text-muted-foreground hover:text-foreground flex shrink-0 cursor-pointer items-center gap-1.5 px-1 text-[10px]", children: [_jsx(Checkbox, { checked: follow, onCheckedChange: (checked) => setFollow(checked === true), "aria-label": "Follow newest events" }), _jsx("span", { className: "debugger-follow-label", children: "Follow" })] })] }), threadId ? (_jsxs("div", { className: "border-border text-muted-foreground flex items-center gap-1.5 border-b px-2 py-1 font-mono text-[10px]", children: [_jsx("span", { className: "text-[9px] tracking-wide uppercase", children: "Thread" }), _jsx("span", { className: "text-foreground truncate", title: threadId, children: threadLabel ?? shortId(threadId) })] })) : null, _jsx(ScrollArea, { viewportRef: parentRef, className: "min-h-32 flex-1", onViewportScroll: onScroll, children: _jsx("div", { className: "relative w-full px-1", style: { height: virtualizer.getTotalSize() }, children: virtualizer.getVirtualItems().map((row) => {
101
- const event = visible[row.index];
102
- if (!event) {
103
- return null;
104
- }
105
- return (_jsx("div", { "data-index": row.index, ref: virtualizer.measureElement, className: "absolute top-0 left-0 w-full", style: { transform: `translateY(${row.start}px)` }, children: _jsx(EventRow, { event: event, catalog: eventCatalog, startedAt: startedAt, selected: selectedSeq === event.seq, onSelectSeq: onSelectSeq }) }, event.id));
106
- }) }) }), selected
107
- ? (renderInspector?.(selected) ?? (_jsx(EventInspector, { event: selected, loadReplayStep: loadReplayStep })))
50
+ const filtered = visible.length !== events.length;
51
+ return (_jsxs("div", { className: "debugger-event-stream flex h-full min-h-0 flex-col", children: [_jsxs(PanelHeader, { className: "gap-1.5 pr-2", children: [_jsx(PanelTitle, { className: "hidden @md:flex", meta: filtered ? `${visible.length}/${events.length}` : events.length, children: "Events" }), _jsxs("div", { className: "relative ml-auto min-w-0 flex-1 @md:max-w-64", children: [_jsx(Search, { "aria-hidden": true, className: "text-muted-foreground pointer-events-none absolute top-1/2 left-2 size-3 -translate-y-1/2" }), _jsx(Input, { value: query, onChange: (event) => setQuery(event.target.value), placeholder: "Search events", "aria-label": "Search events", className: "h-7 w-full pl-7 text-xs" })] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "icon-sm", className: "relative", "aria-label": "Filter event families", children: [_jsx(SlidersHorizontal, {}), hiddenFamilyCount > 0 ? (_jsx("span", { className: "bg-foreground text-background absolute top-0.5 right-0.5 flex size-3.5 items-center justify-center rounded-full font-mono text-[8px]", children: hiddenFamilyCount })) : null] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuLabel, { children: "Event families" }), eventCatalog.families.map((family) => (_jsxs(DropdownMenuCheckboxItem, { checked: families.has(family), onCheckedChange: () => toggleFamily(family), onSelect: (event) => event.preventDefault(), className: "font-mono", children: [_jsx("span", { "aria-hidden": true, className: "size-1.5 rounded-full", style: { backgroundColor: eventCatalog.familyColor(family) } }), family] }, family)))] })] }), _jsxs(Button, { variant: "ghost", size: "sm", "aria-pressed": follow, "aria-label": "Follow newest events", onClick: () => setFollow(!follow), className: "aria-pressed:bg-accent aria-pressed:text-foreground px-2", children: [_jsx(ArrowDownToLine, {}), _jsx("span", { className: "hidden @lg:inline", children: "Follow" })] })] }), threadId ? (_jsxs("div", { className: "border-border flex h-8 shrink-0 items-center gap-2 border-b px-3 text-[11px]", children: [_jsx("span", { className: "text-muted-foreground", children: "Thread" }), _jsxs("span", { className: "bg-accent text-foreground inline-flex h-5 min-w-0 items-center gap-1 rounded-sm pr-0.5 pl-1.5 font-mono", children: [_jsx("span", { className: "truncate", title: threadId, children: threadLabel ?? shortId(threadId) }), onClearThread ? (_jsx("button", { type: "button", "aria-label": "Show all threads", onClick: onClearThread, className: "text-muted-foreground hover:text-foreground hover:bg-background/60 flex size-4 items-center justify-center rounded-sm", children: _jsx(X, { className: "size-3" }) })) : null] })] })) : null, _jsx(FollowList, { items: visible, getKey: (event) => event.id, estimateSize: ROW_ESTIMATE, overscan: 16, follow: follow, onFollowChange: setFollow, className: "min-h-32", itemClassName: "px-1.5", empty: _jsx("p", { className: "text-muted-foreground p-6 text-center text-xs", children: events.length === 0 ? 'Waiting for events…' : 'No events match these filters.' }), renderItem: (event) => (_jsx(EventRow, { event: event, catalog: eventCatalog, startedAt: startedAt, selected: selectedSeq === event.seq, onSelectSeq: onSelectSeq })) }), selected
52
+ ? (renderInspector?.(selected) ?? (_jsx(EventInspector, { event: selected, loadReplayStep: loadReplayStep, onClose: () => onSelectSeq(undefined) })))
108
53
  : null] }));
109
54
  }
110
- // SAFETY: memoized EventRow preserves generic EventRowProps contract.
111
- const EventRow = memo(function EventRow({ event, catalog, startedAt, selected, onSelectSeq, }) {
55
+ function EventRow({ event, catalog, startedAt, selected, onSelectSeq, }) {
112
56
  const meta = getEventMeta(event, catalog);
113
57
  const offset = startedAt !== undefined ? Math.max(0, event.ts - startedAt) : undefined;
114
- const handleClick = useCallback(() => {
115
- onSelectSeq(selected ? undefined : event.seq);
116
- }, [onSelectSeq, selected, event.seq]);
117
- return (_jsxs("button", { type: "button", onClick: handleClick, title: [meta.summary.title, meta.summary.detail].filter(Boolean).join(' — '), className: cn('flex w-full items-start gap-1.5 rounded-sm px-1.5 py-1 text-left font-mono text-[11px]', selected ? 'bg-accent' : 'hover:bg-accent/40', event.ephemeral && 'opacity-50'), children: [_jsx("span", { className: cn('mt-1.5 size-1.5 shrink-0 rounded-full', catalog.familyClass(meta.family)) }), _jsx("span", { className: "text-muted-foreground w-6 shrink-0 text-right tabular-nums", children: event.seq }), _jsx("span", { className: "debugger-event-offset text-muted-foreground w-10 shrink-0 text-right tabular-nums", children: offset !== undefined ? `+${offset}` : '' }), _jsxs("span", { className: "min-w-0 flex-1 truncate whitespace-nowrap", children: [_jsx("span", { className: "text-foreground", children: meta.summary.title }), meta.summary.detail ? (_jsx("span", { className: "text-muted-foreground ml-1", children: meta.summary.detail })) : null] }), event.causationId ? (_jsxs("span", { className: "debugger-event-causation text-muted-foreground shrink-0", children: ["\u2190 ", shortId(event.causationId)] })) : null, event.threadId ? (_jsx("span", { className: "debugger-event-thread text-muted-foreground shrink-0", children: shortId(event.threadId) })) : null] }));
118
- // SAFETY: memoized EventRow preserves generic EventRowProps contract.
119
- });
58
+ return (_jsxs("button", { type: "button", onClick: () => onSelectSeq(selected ? undefined : event.seq), title: [meta.summary.title, meta.summary.detail].filter(Boolean).join(' — '), className: cn('flex h-[26px] w-full items-center gap-2 rounded-sm px-1.5 text-left font-mono text-[11px]', selected ? 'bg-accent' : 'hover:bg-accent/40', event.ephemeral && 'opacity-50'), children: [_jsx("span", { className: "size-1.5 shrink-0 rounded-full", style: { backgroundColor: catalog.familyColor(meta.family) } }), _jsx("span", { className: "text-muted-foreground w-7 shrink-0 text-right tabular-nums", children: event.seq }), _jsx("span", { className: "debugger-event-offset text-muted-foreground/70 w-12 shrink-0 text-right tabular-nums", children: offset !== undefined ? `+${offset}` : '' }), _jsxs("span", { className: "min-w-0 flex-1 truncate whitespace-nowrap", children: [_jsx("span", { className: "text-foreground", children: meta.summary.title }), meta.summary.detail ? (_jsx("span", { className: "text-muted-foreground ml-1.5", children: meta.summary.detail })) : null] }), event.causationId ? (_jsxs("span", { className: "debugger-event-causation text-muted-foreground/70 shrink-0", children: ["\u2190 ", shortId(event.causationId)] })) : null, event.threadId ? (_jsx("span", { className: "debugger-event-thread text-muted-foreground/70 shrink-0", children: shortId(event.threadId) })) : null] }));
59
+ }
@@ -0,0 +1,19 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * A virtualized list that stays pinned to its newest item while following, stops following
4
+ * when the reader scrolls up, and resumes when they return to the bottom.
5
+ */
6
+ export declare function FollowList<T>({ items, getKey, renderItem, estimateSize, overscan, follow: controlledFollow, onFollowChange, paddingStart, paddingEnd, className, itemClassName, empty, }: {
7
+ items: readonly T[];
8
+ getKey: (item: T, index: number) => string | number;
9
+ renderItem: (item: T, index: number) => ReactNode;
10
+ estimateSize: number;
11
+ overscan?: number;
12
+ follow?: boolean;
13
+ onFollowChange?: (follow: boolean) => void;
14
+ paddingStart?: number;
15
+ paddingEnd?: number;
16
+ className?: string;
17
+ itemClassName?: string;
18
+ empty?: ReactNode;
19
+ }): import("react").JSX.Element;
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useVirtualizer } from '@tanstack/react-virtual';
3
+ import { Effect, Fiber } from 'effect';
4
+ import { ArrowDown } from 'lucide-react';
5
+ import { useCallback, useEffect, useRef, useState, } from 'react';
6
+ import { cn } from '../lib/cn.js';
7
+ import { ScrollArea } from '../ui/scroll-area.js';
8
+ const FOLLOW_THRESHOLD = 24;
9
+ const USER_INPUT_WINDOW = 400;
10
+ /**
11
+ * A virtualized list that stays pinned to its newest item while following, stops following
12
+ * when the reader scrolls up, and resumes when they return to the bottom.
13
+ */
14
+ export function FollowList({ items, getKey, renderItem, estimateSize, overscan = 8, follow: controlledFollow, onFollowChange, paddingStart = 4, paddingEnd = 4, className, itemClassName, empty, }) {
15
+ const [ownFollow, setOwnFollow] = useState(true);
16
+ const follow = controlledFollow ?? ownFollow;
17
+ const parentRef = useRef(null);
18
+ const followLock = useRef(false);
19
+ const lastScrollTop = useRef(0);
20
+ const lastInputAt = useRef(Number.NEGATIVE_INFINITY);
21
+ const setFollow = useCallback((next) => {
22
+ setOwnFollow(next);
23
+ onFollowChange?.(next);
24
+ }, [onFollowChange]);
25
+ const virtualizer = useVirtualizer({
26
+ count: items.length,
27
+ getScrollElement: () => parentRef.current,
28
+ estimateSize: () => estimateSize,
29
+ overscan,
30
+ paddingStart,
31
+ paddingEnd,
32
+ getItemKey: (index) => {
33
+ const item = items[index];
34
+ return item === undefined ? index : getKey(item, index);
35
+ },
36
+ // Avoid flushSync during ref measurement / follow-scroll under high event rates.
37
+ useFlushSync: false,
38
+ });
39
+ const totalSize = virtualizer.getTotalSize();
40
+ const viewportHeight = virtualizer.scrollRect?.height ?? 0;
41
+ useEffect(() => {
42
+ const el = parentRef.current;
43
+ // Content that shrank to fit (e.g. after filtering) never scrolls, so resume following here.
44
+ if (!follow && el && el.scrollHeight <= el.clientHeight + 1) {
45
+ setFollow(true);
46
+ }
47
+ if (!follow || items.length === 0) {
48
+ return () => undefined;
49
+ }
50
+ followLock.current = true;
51
+ let followRelease;
52
+ const rafId = requestAnimationFrame(() => {
53
+ virtualizer.scrollToIndex(items.length - 1, { align: 'end' });
54
+ followRelease = Effect.runFork(Effect.sleep(50).pipe(Effect.andThen(Effect.sync(() => {
55
+ followLock.current = false;
56
+ }))));
57
+ });
58
+ return () => {
59
+ cancelAnimationFrame(rafId);
60
+ if (followRelease !== undefined) {
61
+ Effect.runFork(Fiber.interrupt(followRelease));
62
+ }
63
+ followLock.current = false;
64
+ };
65
+ }, [follow, items.length, setFollow, totalSize, viewportHeight, virtualizer]);
66
+ const noteInput = useCallback((event) => {
67
+ lastInputAt.current = event.timeStamp;
68
+ }, []);
69
+ const onScroll = useCallback((event) => {
70
+ const el = parentRef.current;
71
+ if (!el) {
72
+ return;
73
+ }
74
+ // Row measurement also moves scrollTop, so only an upward scroll right after reader input unfollows.
75
+ const movedUp = el.scrollTop < lastScrollTop.current - 1;
76
+ const fromReader = event.timeStamp - lastInputAt.current < USER_INPUT_WINDOW;
77
+ lastScrollTop.current = el.scrollTop;
78
+ if (followLock.current) {
79
+ return;
80
+ }
81
+ const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < FOLLOW_THRESHOLD;
82
+ if (follow && !atBottom && movedUp && fromReader) {
83
+ setFollow(false);
84
+ }
85
+ else if (!follow && atBottom) {
86
+ setFollow(true);
87
+ }
88
+ }, [follow, setFollow]);
89
+ return (_jsxs("div", { className: cn('relative min-h-0 flex-1', className), children: [_jsx(ScrollArea, { viewportRef: parentRef, className: "size-full", onViewportScroll: onScroll, onWheel: noteInput, onTouchMove: noteInput, onPointerDown: noteInput, onKeyDown: noteInput, children: items.length === 0 && empty !== undefined ? (empty) : (_jsx("div", { className: "relative w-full", style: { height: totalSize }, children: virtualizer.getVirtualItems().map((row) => {
90
+ const item = items[row.index];
91
+ if (item === undefined) {
92
+ return null;
93
+ }
94
+ return (_jsx("div", { "data-index": row.index, ref: virtualizer.measureElement, className: cn('absolute top-0 left-0 w-full', itemClassName), style: { transform: `translateY(${row.start}px)` }, children: renderItem(item, row.index) }, row.key));
95
+ }) })) }), !follow && items.length > 0 ? (_jsxs("button", { type: "button", onClick: () => setFollow(true), className: "debugger-float-in bg-card/95 text-foreground border-border hover:bg-accent absolute bottom-3 left-1/2 z-20 flex h-7 -translate-x-1/2 items-center gap-1.5 rounded-full border px-3 text-[11px] font-medium shadow-lg shadow-black/30 backdrop-blur active:scale-[0.97]", children: [_jsx(ArrowDown, { className: "size-3" }), "Jump to latest"] })) : null] }));
96
+ }
@@ -0,0 +1,11 @@
1
+ import type { JsonValue } from '@swirls/looms/core';
2
+ /**
3
+ * Collapsible, syntax-coloured JSON. Strings that hold JSON expand in place,
4
+ * wide nodes start collapsed, and very large arrays render in buckets.
5
+ */
6
+ export declare function JsonTree({ value, defaultExpandDepth, copyable, className, }: {
7
+ value: JsonValue;
8
+ defaultExpandDepth?: number;
9
+ copyable?: boolean;
10
+ className?: string;
11
+ }): import("react").JSX.Element;
@@ -0,0 +1,120 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Check, ChevronRight, Copy } from 'lucide-react';
3
+ import { memo, useMemo, useState } from 'react';
4
+ import { cn } from '../lib/cn.js';
5
+ import { jsonEntries, jsonNode, jsonPreview, parseStructuredJson } from '../lib/json.js';
6
+ /** Children past this count render in collapsed buckets so huge arrays never flood the DOM. */
7
+ const CHUNK_SIZE = 100;
8
+ /** Nodes wider than this start collapsed even inside the default expand depth. */
9
+ const AUTO_EXPAND_LIMIT = 50;
10
+ const STRING_CLAMP = 320;
11
+ function Chevron({ open }) {
12
+ return (_jsx(ChevronRight, { "aria-hidden": true, className: cn('ease-snappy size-3 transition-transform duration-100', open && 'rotate-90') }));
13
+ }
14
+ function Key({ name, index }) {
15
+ if (name === undefined) {
16
+ return null;
17
+ }
18
+ return (_jsxs("span", { className: cn('mr-1 shrink-0', index ? 'text-muted-foreground' : 'text-json-key'), children: [name, _jsx("span", { className: "text-muted-foreground", children: ":" })] }));
19
+ }
20
+ function StringValue({ value }) {
21
+ const [expanded, setExpanded] = useState(false);
22
+ const clamped = !expanded && value.length > STRING_CLAMP;
23
+ const shown = clamped ? value.slice(0, STRING_CLAMP) : value;
24
+ return (_jsxs("span", { className: "text-json-string", children: ["\"", shown, clamped ? (_jsxs("button", { type: "button", className: "text-muted-foreground hover:text-foreground ml-0.5 rounded-sm px-0.5 hover:underline", onClick: () => setExpanded(true), children: ["\u2026+", value.length - STRING_CLAMP] })) : null, "\""] }));
25
+ }
26
+ function Scalar({ node }) {
27
+ switch (node.kind) {
28
+ case 'string':
29
+ return _jsx(StringValue, { value: node.value });
30
+ case 'number':
31
+ return _jsx("span", { className: "text-json-number", children: String(node.value) });
32
+ case 'boolean':
33
+ return _jsx("span", { className: "text-json-boolean", children: String(node.value) });
34
+ case 'null':
35
+ return _jsx("span", { className: "text-json-null italic", children: "null" });
36
+ case 'object':
37
+ return _jsx("span", { className: "text-muted-foreground", children: '{}' });
38
+ case 'array':
39
+ return _jsx("span", { className: "text-muted-foreground", children: "[]" });
40
+ default: {
41
+ const exhaustive = node;
42
+ return exhaustive;
43
+ }
44
+ }
45
+ }
46
+ /** Leaf values flow inline so long strings wrap beneath their key instead of in a narrow column. */
47
+ function Leaf({ children, className }) {
48
+ return (_jsx("div", { className: cn('py-px pl-4 break-words whitespace-pre-wrap', className), children: children }));
49
+ }
50
+ function Branch({ label, summary, defaultOpen, children, }) {
51
+ const [open, setOpen] = useState(defaultOpen);
52
+ const [deep, setDeep] = useState(false);
53
+ const [generation, setGeneration] = useState(0);
54
+ function toggle(event) {
55
+ const next = !open;
56
+ setOpen(next);
57
+ // Alt-click opens or closes the whole subtree, like browser devtools.
58
+ if (event.altKey) {
59
+ setDeep(next);
60
+ setGeneration((value) => value + 1);
61
+ }
62
+ }
63
+ return (_jsxs("div", { children: [_jsxs("button", { type: "button", "aria-expanded": open, onClick: toggle, className: "hover:bg-accent/50 group/branch -mx-1 flex w-[calc(100%+0.5rem)] min-w-0 items-start gap-1 rounded-sm px-1 py-px text-left", children: [_jsx("span", { className: "text-muted-foreground group-hover/branch:text-foreground flex h-5 w-3 shrink-0 items-center", children: _jsx(Chevron, { open: open }) }), label, _jsx("span", { className: "text-muted-foreground min-w-0 truncate", children: summary })] }), open ? (_jsx("div", { className: "border-border/70 ml-1.5 border-l pl-2.5", children: children(deep) }, generation)) : null] }));
64
+ }
65
+ function Children({ node, depth, options, forceOpen, }) {
66
+ const entries = jsonEntries(node);
67
+ const index = node.kind === 'array';
68
+ if (entries.length <= CHUNK_SIZE) {
69
+ return entries.map(([key, value]) => (_jsx(Entry, { name: key, index: index, value: value, depth: depth, options: options, forceOpen: forceOpen }, key)));
70
+ }
71
+ const chunks = [];
72
+ for (let start = 0; start < entries.length; start += CHUNK_SIZE) {
73
+ chunks.push(entries.slice(start, start + CHUNK_SIZE));
74
+ }
75
+ return chunks.map((chunk, chunkIndex) => {
76
+ const first = chunkIndex * CHUNK_SIZE;
77
+ const last = first + chunk.length - 1;
78
+ return (_jsx(Branch, { defaultOpen: forceOpen, label: _jsxs("span", { className: "text-muted-foreground shrink-0", children: ["[", first, " \u2026 ", last, "]"] }), summary: null, children: (deep) => chunk.map(([key, value]) => (_jsx(Entry, { name: key, index: index, value: value, depth: depth, options: options, forceOpen: deep }, key))) }, first));
79
+ });
80
+ }
81
+ const Entry = memo(function Entry({ name, index = false, value, depth, options, forceOpen = false, }) {
82
+ const node = useMemo(() => jsonNode(value), [value]);
83
+ const embedded = useMemo(() => (node.kind === 'string' ? parseStructuredJson(node.value) : undefined), [node]);
84
+ const branch = embedded !== undefined
85
+ ? jsonNode(embedded)
86
+ : node.kind === 'object' || node.kind === 'array'
87
+ ? node
88
+ : undefined;
89
+ const size = branch ? jsonEntries(branch).length : 0;
90
+ if (!branch || size === 0) {
91
+ return (_jsxs(Leaf, { children: [_jsx(Key, { name: name, index: index }), _jsx(Scalar, { node: branch ?? node })] }));
92
+ }
93
+ const defaultOpen = forceOpen ||
94
+ (embedded === undefined && depth < options.defaultExpandDepth && size <= AUTO_EXPAND_LIMIT);
95
+ return (_jsx(Branch, { defaultOpen: defaultOpen, label: _jsxs(_Fragment, { children: [_jsx(Key, { name: name, index: index }), embedded !== undefined ? (_jsx("span", { className: "border-border text-muted-foreground mt-0.5 shrink-0 rounded-sm border px-1 text-[9px] leading-3.5 uppercase", title: "String containing JSON", children: "json" })) : null] }), summary: jsonPreview(branch), children: (deep) => _jsx(Children, { node: branch, depth: depth + 1, options: options, forceOpen: deep }) }));
96
+ });
97
+ function CopyButton({ value }) {
98
+ const [copied, setCopied] = useState(false);
99
+ return (_jsx("button", { type: "button", "aria-label": "Copy JSON", className: "text-muted-foreground hover:text-foreground bg-card/90 hover:bg-accent border-border absolute top-0 right-0 flex size-6 items-center justify-center rounded-md border opacity-0 transition-opacity duration-150 group-hover/json:opacity-100 focus-visible:opacity-100", onClick: () => {
100
+ void navigator.clipboard
101
+ .writeText(JSON.stringify(value, null, 2))
102
+ .then(() => setCopied(true));
103
+ }, onPointerLeave: () => setCopied(false), children: copied ? _jsx(Check, { className: "size-3" }) : _jsx(Copy, { className: "size-3" }) }));
104
+ }
105
+ /**
106
+ * Collapsible, syntax-coloured JSON. Strings that hold JSON expand in place,
107
+ * wide nodes start collapsed, and very large arrays render in buckets.
108
+ */
109
+ export function JsonTree({ value, defaultExpandDepth = 1, copyable = true, className, }) {
110
+ const options = useMemo(() => ({ defaultExpandDepth }), [defaultExpandDepth]);
111
+ const { root, source } = useMemo(() => {
112
+ const node = jsonNode(value);
113
+ const embedded = node.kind === 'string' ? parseStructuredJson(node.value) : undefined;
114
+ return embedded !== undefined
115
+ ? { root: jsonNode(embedded), source: embedded }
116
+ : { root: node, source: value };
117
+ }, [value]);
118
+ const hasChildren = (root.kind === 'object' || root.kind === 'array') && jsonEntries(root).length > 0;
119
+ return (_jsxs("div", { className: cn('group/json relative min-w-0 font-mono text-[11px] leading-5', className), children: [hasChildren ? (_jsx(Children, { node: root, depth: 0, options: options, forceOpen: false })) : (_jsx(Leaf, { className: "pl-0", children: _jsx(Scalar, { node: root }) })), copyable ? _jsx(CopyButton, { value: source }) : null] }));
120
+ }
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { cn } from '../lib/cn.js';
2
+ import { JsonTree } from './json-tree.js';
3
3
  export function JsonView({ value, className }) {
4
- return (_jsx("pre", { className: cn('text-muted-foreground overflow-auto font-mono text-[11px] leading-relaxed wrap-break-word whitespace-pre-wrap', className), children: JSON.stringify(value, null, 2) }));
4
+ return _jsx(JsonTree, { value: value, className: className });
5
5
  }