@cueloop/client 0.1.0-alpha.10 → 0.1.0-alpha.100

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 (355) hide show
  1. package/native/build-pty.sh +51 -0
  2. package/native/build.sh +82 -0
  3. package/native/darwin-arm64/libcuelooppty.dylib +0 -0
  4. package/native/darwin-arm64/libcueloopvt.dylib +0 -0
  5. package/native/src/pty.zig +193 -0
  6. package/native/src/shim.zig +114 -0
  7. package/package.json +42 -34
  8. package/src/annotations/annotation-palette.ts +62 -0
  9. package/src/annotations/components/AnnotationCards.stories.tsx +86 -0
  10. package/src/annotations/components/AnnotationCards.test.tsx +42 -0
  11. package/src/annotations/components/AnnotationCards.tsx +364 -0
  12. package/src/annotations/components/DiscussionMarkerRail.stories.tsx +54 -0
  13. package/src/annotations/components/DiscussionMarkerRail.tsx +186 -0
  14. package/src/annotations/discussions.ts +77 -0
  15. package/src/annotations/thread-selection.test.ts +210 -0
  16. package/src/annotations/thread-selection.ts +242 -0
  17. package/src/annotations/use-annotation-surface.tsx +1238 -0
  18. package/src/app/App.appearance.test.tsx +53 -0
  19. package/src/{App.autoclose.test.tsx → app/App.autoclose.test.tsx} +77 -51
  20. package/src/app/App.deleted-scroll.test.tsx +187 -0
  21. package/src/app/App.diff.test.tsx +313 -0
  22. package/src/app/App.export.test.tsx +105 -0
  23. package/src/app/App.focus.test.ts +89 -0
  24. package/src/app/App.identity.test.tsx +84 -0
  25. package/src/app/App.plan-render.test.tsx +209 -0
  26. package/src/app/App.plan-review.test.tsx +428 -0
  27. package/src/app/App.prototype-markdown.test.tsx +128 -0
  28. package/src/app/App.prototype.test.tsx +176 -0
  29. package/src/app/App.readonly.test.tsx +210 -0
  30. package/src/app/App.regression.test.tsx +99 -0
  31. package/src/app/App.rendering.test.tsx +134 -0
  32. package/src/app/App.share-dialog.test.tsx +177 -0
  33. package/src/app/App.submit-confirm.test.tsx +267 -0
  34. package/src/app/App.test.tsx +496 -0
  35. package/src/app/App.thread-agent.test.ts +12 -0
  36. package/src/app/App.toast.test.tsx +124 -0
  37. package/src/app/App.tsx +1650 -0
  38. package/src/app/App.visual.test.tsx +59 -0
  39. package/src/app/App.walk.test.tsx +299 -0
  40. package/src/app/App.workbench.test.tsx +1149 -0
  41. package/src/app/app-key-state.ts +61 -0
  42. package/src/app/app-screens.tsx +606 -0
  43. package/src/app/app-view-model.ts +168 -0
  44. package/src/app/components/AppHeader.stories.tsx +50 -0
  45. package/src/app/components/AppHeader.tsx +81 -0
  46. package/src/app/components/AppShell.brand.test.tsx +58 -0
  47. package/src/app/components/AppShell.extensions.test.tsx +191 -0
  48. package/src/app/components/AppShell.stories.tsx +89 -0
  49. package/src/app/components/AppShell.tsx +358 -0
  50. package/src/app/components/AppShell.zoom.test.tsx +149 -0
  51. package/src/app/components/Breadcrumb.stories.tsx +19 -0
  52. package/src/app/components/Breadcrumb.tsx +45 -0
  53. package/src/app/components/MenuBar.stories.tsx +28 -0
  54. package/src/app/components/MenuBar.tsx +44 -0
  55. package/src/app/components/PaneGrid.stories.tsx +84 -0
  56. package/src/app/components/PaneGrid.tsx +128 -0
  57. package/src/app/components/PanelColumn.stories.tsx +25 -0
  58. package/src/app/components/PanelColumn.tsx +93 -0
  59. package/src/app/components/ShellHeader.stories.tsx +50 -0
  60. package/src/app/components/ShellHeader.tsx +137 -0
  61. package/src/app/components/WorkspacePanels.stories.tsx +36 -0
  62. package/src/app/components/WorkspacePanels.tsx +120 -0
  63. package/src/app/components/pages/Workspace.stories.tsx +181 -0
  64. package/src/app/intent-dispatch.test.ts +728 -0
  65. package/src/app/intent-dispatch.ts +687 -0
  66. package/src/app/launch-layout.test.ts +78 -0
  67. package/src/app/launch-layout.ts +57 -0
  68. package/src/app/ready-signal.test.ts +85 -0
  69. package/src/app/ready-signal.ts +52 -0
  70. package/src/app/run.ts +109 -0
  71. package/src/{serve.test.ts → app/serve.test.ts} +24 -12
  72. package/src/app/serve.ts +111 -0
  73. package/src/app/version.ts +5 -0
  74. package/src/appearance/components/theme-context.stories.tsx +46 -0
  75. package/src/appearance/components/theme-context.tsx +37 -0
  76. package/src/appearance/theme-presets.test.ts +51 -0
  77. package/src/appearance/theme-presets.ts +219 -0
  78. package/src/appearance/theme.ts +112 -0
  79. package/src/appearance/visual/contrast.test.ts +35 -0
  80. package/src/appearance/visual/contrast.ts +33 -0
  81. package/src/diff/components/AnnotatableFileView.stories.tsx +42 -0
  82. package/src/diff/components/AnnotatableFileView.test.tsx +157 -0
  83. package/src/diff/components/AnnotatableFileView.tsx +141 -0
  84. package/src/diff/components/BareWorkbenchFileView.stories.tsx +27 -0
  85. package/src/diff/components/BareWorkbenchFileView.tsx +63 -0
  86. package/src/diff/components/DiffContentView.scroll.test.tsx +434 -0
  87. package/src/diff/components/DiffContentView.split.test.tsx +140 -0
  88. package/src/diff/components/DiffContentView.stories.tsx +80 -0
  89. package/src/diff/components/DiffContentView.syntax.test.tsx +82 -0
  90. package/src/diff/components/DiffContentView.tsx +1055 -0
  91. package/src/diff/components/WalkWizard.stories.tsx +100 -0
  92. package/src/diff/components/WalkWizard.tsx +146 -0
  93. package/src/diff/components/diff-content-view-layout.test.ts +198 -0
  94. package/src/diff/components/diff-content-view-layout.ts +84 -0
  95. package/src/diff/diff-fold.test.ts +78 -0
  96. package/src/diff/diff-fold.ts +126 -0
  97. package/src/diff/diff-hunk-curate.test.ts +315 -0
  98. package/src/diff/diff-hunk-curate.ts +152 -0
  99. package/src/diff/diff-intraline.test.ts +239 -0
  100. package/src/diff/diff-intraline.ts +238 -0
  101. package/src/diff/diff-syntax.test.ts +125 -0
  102. package/src/diff/diff-syntax.ts +186 -0
  103. package/src/diff/split-diff.test.ts +110 -0
  104. package/src/diff/split-diff.ts +144 -0
  105. package/src/diff/view-diff.test.ts +325 -0
  106. package/src/diff/view-diff.ts +522 -0
  107. package/src/diff/walk.test.ts +121 -0
  108. package/src/diff/walk.ts +73 -0
  109. package/src/index.ts +31 -2
  110. package/src/integrations/attribution.test.ts +31 -0
  111. package/src/integrations/attribution.ts +42 -0
  112. package/src/integrations/client-extension-registry.test.tsx +61 -0
  113. package/src/integrations/client-extension-registry.ts +159 -0
  114. package/src/integrations/clipboard.test.ts +23 -0
  115. package/src/integrations/clipboard.ts +33 -0
  116. package/src/integrations/components/ExtensionZone.stories.tsx +28 -0
  117. package/src/integrations/components/ExtensionZone.tsx +145 -0
  118. package/src/integrations/components/ShareDialog.stories.tsx +121 -0
  119. package/src/integrations/components/ShareDialog.tsx +550 -0
  120. package/src/integrations/components/share-dialog-store.ts +55 -0
  121. package/src/integrations/editor.test.ts +149 -0
  122. package/src/integrations/editor.ts +160 -0
  123. package/src/integrations/github-identity.test.ts +58 -0
  124. package/src/integrations/github-identity.ts +41 -0
  125. package/src/integrations/herdr.test.ts +43 -0
  126. package/src/integrations/herdr.ts +38 -0
  127. package/src/integrations/integrations.test.ts +41 -0
  128. package/src/integrations/integrations.ts +32 -0
  129. package/src/integrations/pasted-image.test.ts +67 -0
  130. package/src/integrations/pasted-image.ts +60 -0
  131. package/src/integrations/share.ts +238 -0
  132. package/src/keyboard/components/CompletionOverlay.stories.tsx +37 -0
  133. package/src/keyboard/components/CompletionOverlay.tsx +75 -0
  134. package/src/keyboard/components/HandleChipsInput.stories.tsx +24 -0
  135. package/src/keyboard/components/HandleChipsInput.test.tsx +35 -0
  136. package/src/keyboard/components/HandleChipsInput.tsx +143 -0
  137. package/src/keyboard/components/KeybindsDialog.stories.tsx +31 -0
  138. package/src/keyboard/components/KeybindsDialog.tsx +103 -0
  139. package/src/keyboard/components/NavModeHint.stories.tsx +40 -0
  140. package/src/keyboard/components/NavModeHint.tsx +31 -0
  141. package/src/keyboard/components/SlashComposer.stories.tsx +37 -0
  142. package/src/keyboard/components/SlashComposer.tsx +87 -0
  143. package/src/keyboard/key-bindings.test.ts +156 -0
  144. package/src/keyboard/key-bindings.ts +459 -0
  145. package/src/keyboard/keymap.test.ts +611 -0
  146. package/src/keyboard/keymap.ts +395 -0
  147. package/src/keyboard/skills.test.ts +74 -0
  148. package/src/keyboard/skills.ts +75 -0
  149. package/src/keyboard/slash-palette.test.ts +103 -0
  150. package/src/keyboard/slash-palette.ts +129 -0
  151. package/src/keyboard/thread-chords.test.ts +79 -0
  152. package/src/keyboard/thread-chords.ts +163 -0
  153. package/src/keyboard/use-shared-keyboard.ts +55 -0
  154. package/src/markdown/__snapshots__/view-plan.regression.test.ts.snap +100 -0
  155. package/src/markdown/components/MarkdownGridBlock.stories.tsx +33 -0
  156. package/src/markdown/components/MarkdownGridBlock.tsx +72 -0
  157. package/src/markdown/components/MarkdownThreadEditor.stories.tsx +15 -0
  158. package/src/markdown/components/MarkdownThreadEditor.test.tsx +98 -0
  159. package/src/markdown/components/MarkdownThreadEditor.tsx +160 -0
  160. package/src/markdown/components/ThreadView.selection.test.tsx +951 -0
  161. package/src/markdown/components/ThreadView.slash.test.tsx +146 -0
  162. package/src/markdown/components/ThreadView.stories.tsx +26 -0
  163. package/src/markdown/components/ThreadView.tsx +560 -0
  164. package/src/markdown/mark-runs.ts +121 -0
  165. package/src/markdown/markdown-editor-syntax-style.ts +50 -0
  166. package/src/markdown/markdown-grid.test.ts +101 -0
  167. package/src/markdown/markdown-grid.ts +203 -0
  168. package/src/markdown/markdown-highlight.test.ts +61 -0
  169. package/src/markdown/markdown-highlight.ts +124 -0
  170. package/src/markdown/thread-code-syntax.ts +45 -0
  171. package/src/markdown/use-thread-body-editing.tsx +54 -0
  172. package/src/markdown/view-plan.regression.test.ts +60 -0
  173. package/src/markdown/view-plan.test.ts +545 -0
  174. package/src/markdown/view-plan.ts +1009 -0
  175. package/src/performance/otlp-trace.test.ts +119 -0
  176. package/src/performance/otlp-trace.ts +134 -0
  177. package/src/performance/perf-report.ts +18 -0
  178. package/src/performance/perf-timings.test.ts +18 -0
  179. package/src/performance/perf-timings.ts +39 -0
  180. package/src/prototype/components/PrototypeContentView.stories.tsx +19 -0
  181. package/src/prototype/components/PrototypeContentView.tsx +442 -0
  182. package/src/prototype/prototype-browser.test.ts +81 -0
  183. package/src/prototype/prototype-browser.ts +321 -0
  184. package/src/prototype/prototype-pixels.tsx +35 -0
  185. package/src/settings/components/SettingsDialog.stories.tsx +117 -0
  186. package/src/settings/components/SettingsDialog.tsx +183 -0
  187. package/src/settings/components/SettingsRows.stories.tsx +18 -0
  188. package/src/settings/components/SettingsRows.tsx +112 -0
  189. package/src/settings/components/quick-actions-editor.stories.tsx +58 -0
  190. package/src/settings/components/quick-actions-editor.test.tsx +120 -0
  191. package/src/settings/components/quick-actions-editor.tsx +222 -0
  192. package/src/settings/config-identity.test.ts +78 -0
  193. package/src/settings/config.test.ts +676 -0
  194. package/src/settings/config.ts +628 -0
  195. package/src/settings/use-settings-dialog.tsx +351 -0
  196. package/src/stories/__snapshots__/stories.test.tsx.snap +2358 -0
  197. package/src/stories/stories-app.tsx +196 -0
  198. package/src/stories/stories.test.tsx +74 -0
  199. package/src/stories/story-fixtures.ts +102 -0
  200. package/src/stories/story-tree.test.ts +108 -0
  201. package/src/stories/story.ts +154 -0
  202. package/src/terminal/ansi-screen.test.ts +28 -0
  203. package/src/terminal/ansi-screen.ts +72 -0
  204. package/src/terminal/components/AnsiScreen.stories.tsx +12 -0
  205. package/src/terminal/components/AnsiScreen.tsx +28 -0
  206. package/src/terminal/components/terminal-pane.test.tsx +40 -0
  207. package/src/terminal/components/terminal-pane.ts +196 -0
  208. package/src/terminal/ghostty-terminal.test.ts +85 -0
  209. package/src/terminal/ghostty-terminal.ts +197 -0
  210. package/src/terminal/kitty-image.test.ts +66 -0
  211. package/src/terminal/kitty-image.ts +137 -0
  212. package/src/terminal/pty.test.ts +81 -0
  213. package/src/terminal/pty.ts +246 -0
  214. package/src/testing/test-support.test.ts +264 -0
  215. package/src/testing/test-support.ts +367 -0
  216. package/src/thread/agent-continuation.test.ts +32 -0
  217. package/src/thread/agent-continuation.ts +13 -0
  218. package/src/thread/agent-transcript.test.ts +161 -0
  219. package/src/thread/agent-transcript.ts +336 -0
  220. package/src/thread/components/AgentConfigControls.stories.tsx +39 -0
  221. package/src/thread/components/AgentConfigControls.test.tsx +112 -0
  222. package/src/thread/components/AgentConfigControls.tsx +114 -0
  223. package/src/thread/components/AgentThreadPane.stories.tsx +105 -0
  224. package/src/thread/components/AgentThreadPane.test.tsx +829 -0
  225. package/src/thread/components/AgentThreadPane.tsx +367 -0
  226. package/src/thread/components/ThreadFooter.stories.tsx +30 -0
  227. package/src/thread/components/ThreadFooter.test.tsx +49 -0
  228. package/src/thread/components/ThreadFooter.tsx +77 -0
  229. package/src/thread/components/ThreadTree.stories.tsx +46 -0
  230. package/src/thread/components/ThreadTree.test.tsx +41 -0
  231. package/src/thread/components/ThreadTree.tsx +267 -0
  232. package/src/thread/components/thread-tree.test.ts +137 -0
  233. package/src/thread/components/thread-tree.ts +112 -0
  234. package/src/thread/thread-controller.curate.test.ts +502 -0
  235. package/src/thread/thread-controller.derivation.test.ts +62 -0
  236. package/src/thread/thread-controller.live-diff.test.ts +357 -0
  237. package/src/thread/thread-controller.optimistic.test.ts +69 -0
  238. package/src/thread/thread-controller.share.test.ts +573 -0
  239. package/src/thread/thread-controller.switch-cache.test.ts +174 -0
  240. package/src/thread/thread-controller.tree.test.ts +263 -0
  241. package/src/thread/thread-controller.ts +2018 -0
  242. package/src/thread/tree-view.test.ts +128 -0
  243. package/src/thread/tree-view.ts +146 -0
  244. package/src/thread/use-thread-agent.test.tsx +208 -0
  245. package/src/thread/use-thread-agent.ts +111 -0
  246. package/src/ui/components/CodeBlock.stories.tsx +42 -0
  247. package/src/ui/components/CodeBlock.tsx +80 -0
  248. package/src/ui/components/ConfirmCard.stories.tsx +37 -0
  249. package/src/ui/components/ConfirmCard.tsx +151 -0
  250. package/src/ui/components/ConfirmDialog.stories.tsx +21 -0
  251. package/src/ui/components/ConfirmDialog.tsx +72 -0
  252. package/src/ui/components/OverlayScrollbar.stories.tsx +33 -0
  253. package/src/ui/components/OverlayScrollbar.test.tsx +139 -0
  254. package/src/ui/components/OverlayScrollbar.tsx +129 -0
  255. package/src/ui/components/PromptDialog.stories.tsx +36 -0
  256. package/src/ui/components/PromptDialog.test.tsx +65 -0
  257. package/src/ui/components/PromptDialog.tsx +99 -0
  258. package/src/ui/components/RootOverlay.stories.tsx +45 -0
  259. package/src/ui/components/RootOverlay.tsx +49 -0
  260. package/src/ui/components/ScrollArea.stories.tsx +20 -0
  261. package/src/ui/components/ScrollArea.tsx +61 -0
  262. package/src/ui/components/Toast.stories.tsx +19 -0
  263. package/src/ui/components/Toast.tsx +54 -0
  264. package/src/ui/components/Tooltip.stories.tsx +26 -0
  265. package/src/ui/components/Tooltip.tsx +72 -0
  266. package/src/ui/components/menu-control.stories.tsx +35 -0
  267. package/src/ui/components/menu-control.tsx +43 -0
  268. package/src/ui/components/primitives/Button.stories.tsx +32 -0
  269. package/src/ui/components/primitives/Button.tsx +57 -0
  270. package/src/ui/components/primitives/Card.stories.tsx +25 -0
  271. package/src/ui/components/primitives/Card.tsx +62 -0
  272. package/src/ui/components/primitives/Dialog.stories.tsx +24 -0
  273. package/src/ui/components/primitives/Dialog.test.tsx +38 -0
  274. package/src/ui/components/primitives/Dialog.tsx +69 -0
  275. package/src/ui/components/primitives/DialogActions.stories.tsx +20 -0
  276. package/src/ui/components/primitives/DialogActions.tsx +39 -0
  277. package/src/ui/components/primitives/IconButton.stories.tsx +36 -0
  278. package/src/ui/components/primitives/IconButton.tsx +58 -0
  279. package/src/ui/components/primitives/StatusBar.stories.tsx +11 -0
  280. package/src/ui/components/primitives/StatusBar.tsx +23 -0
  281. package/src/ui/components/primitives/Switch.stories.tsx +17 -0
  282. package/src/ui/components/primitives/Switch.tsx +32 -0
  283. package/src/ui/components/primitives/Tabs.stories.tsx +29 -0
  284. package/src/ui/components/primitives/Tabs.tsx +101 -0
  285. package/src/ui/components/primitives/Toolbar.stories.tsx +17 -0
  286. package/src/ui/components/primitives/Toolbar.tsx +14 -0
  287. package/src/ui/components/primitives/Tree.stories.tsx +124 -0
  288. package/src/ui/components/primitives/Tree.tsx +133 -0
  289. package/src/ui/components/primitives/frame.test.ts +12 -0
  290. package/src/ui/components/primitives/frame.ts +17 -0
  291. package/src/ui/components/primitives/icons.ts +61 -0
  292. package/src/ui/components/primitives/tree-model.test.ts +99 -0
  293. package/src/ui/components/primitives/tree-model.ts +137 -0
  294. package/src/ui/components/syntax-highlight.ts +133 -0
  295. package/src/ui/components/truncate-text.ts +7 -0
  296. package/src/ui/components/truncate-title.test.ts +31 -0
  297. package/src/ui/components/truncate-title.ts +15 -0
  298. package/src/ui/use-frame-measure.ts +74 -0
  299. package/src/ui/use-terminal-virtualizer.ts +259 -0
  300. package/src/workbench/components/ChangesColumn.stories.tsx +44 -0
  301. package/src/workbench/components/ChangesColumn.tsx +203 -0
  302. package/src/workbench/components/ChangesFileTree.test.tsx +181 -0
  303. package/src/workbench/components/EditorGrid.split.test.tsx +118 -0
  304. package/src/workbench/components/EditorGrid.stories.tsx +86 -0
  305. package/src/workbench/components/EditorGrid.tabs.test.tsx +118 -0
  306. package/src/workbench/components/EditorGrid.tsx +544 -0
  307. package/src/workbench/components/EditorGrid.zoom.test.tsx +132 -0
  308. package/src/workbench/components/FileContentsView.stories.tsx +16 -0
  309. package/src/workbench/components/FileContentsView.test.tsx +29 -0
  310. package/src/workbench/components/FileContentsView.tsx +100 -0
  311. package/src/workbench/components/GridTabContent.actions.test.tsx +151 -0
  312. package/src/workbench/components/GridTabContent.stories.tsx +59 -0
  313. package/src/workbench/components/GridTabContent.tsx +193 -0
  314. package/src/workbench/components/ProjectTreeView.stories.tsx +25 -0
  315. package/src/workbench/components/ProjectTreeView.test.tsx +236 -0
  316. package/src/workbench/components/ProjectTreeView.tsx +151 -0
  317. package/src/workbench/components/TreePane.stories.tsx +106 -0
  318. package/src/workbench/components/TreePane.tsx +155 -0
  319. package/src/workbench/components/WelcomePlayground.stories.tsx +12 -0
  320. package/src/workbench/components/WelcomePlayground.test.tsx +83 -0
  321. package/src/workbench/components/WelcomePlayground.tsx +138 -0
  322. package/src/workbench/components/editor-grid.test.ts +184 -0
  323. package/src/workbench/components/editor-grid.ts +281 -0
  324. package/src/workbench/components/file-tree.test.ts +60 -0
  325. package/src/workbench/components/file-tree.ts +112 -0
  326. package/src/workbench/use-changes-workbench.ts +205 -0
  327. package/src/workbench/use-remember-layout.ts +31 -0
  328. package/src/workbench/use-repo-changes.ts +91 -0
  329. package/src/workbench/workbench-snapshot.test.ts +99 -0
  330. package/src/workbench/workbench-snapshot.ts +59 -0
  331. package/src/App.diff.test.tsx +0 -99
  332. package/src/App.export.test.tsx +0 -88
  333. package/src/App.plan-review.test.tsx +0 -259
  334. package/src/App.readonly.test.tsx +0 -148
  335. package/src/App.rendering.test.tsx +0 -108
  336. package/src/App.submit-confirm.test.tsx +0 -183
  337. package/src/App.test.tsx +0 -218
  338. package/src/App.tsx +0 -1249
  339. package/src/config.test.ts +0 -116
  340. package/src/config.ts +0 -150
  341. package/src/editor.ts +0 -34
  342. package/src/herdr.test.ts +0 -68
  343. package/src/herdr.ts +0 -34
  344. package/src/keymap.test.ts +0 -270
  345. package/src/keymap.ts +0 -173
  346. package/src/run.ts +0 -30
  347. package/src/serve.ts +0 -75
  348. package/src/session-controller.ts +0 -398
  349. package/src/syntax.test.ts +0 -41
  350. package/src/syntax.ts +0 -88
  351. package/src/theme.ts +0 -46
  352. package/src/view-diff.test.ts +0 -55
  353. package/src/view-diff.ts +0 -86
  354. package/src/view.test.ts +0 -157
  355. package/src/view.ts +0 -364
@@ -0,0 +1,119 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import {
3
+ buildOtlpTracePayload,
4
+ phaseMarksToSpans,
5
+ postOtlpTrace,
6
+ type TraceSpan,
7
+ } from "./otlp-trace";
8
+
9
+ afterEach(() => {
10
+ delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
11
+ });
12
+
13
+ const span: TraceSpan = {
14
+ traceId: "0123456789abcdef0123456789abcdef",
15
+ spanId: "0123456789abcdef",
16
+ name: "cueloop.interaction",
17
+ startTimeUnixNano: "1000",
18
+ endTimeUnixNano: "2000",
19
+ attributes: [
20
+ { key: "terminal.trigger", value: { stringValue: "key:j" } },
21
+ { key: "terminal.frame.budget_ms", value: { doubleValue: 16.7 } },
22
+ { key: "terminal.backpressure", value: { boolValue: true } },
23
+ ],
24
+ };
25
+
26
+ test("buildOtlpTracePayload carries service.name and maps scalar attributes to OTLP values", () => {
27
+ const payload = buildOtlpTracePayload("cueloop-client", [span]);
28
+
29
+ expect(payload).toEqual({
30
+ resourceSpans: [
31
+ {
32
+ resource: {
33
+ attributes: [{ key: "service.name", value: { stringValue: "cueloop-client" } }],
34
+ },
35
+ scopeSpans: [
36
+ {
37
+ spans: [
38
+ {
39
+ traceId: "0123456789abcdef0123456789abcdef",
40
+ spanId: "0123456789abcdef",
41
+ parentSpanId: "",
42
+ name: "cueloop.interaction",
43
+ startTimeUnixNano: "1000",
44
+ endTimeUnixNano: "2000",
45
+ attributes: [
46
+ { key: "terminal.trigger", value: { stringValue: "key:j" } },
47
+ { key: "terminal.frame.budget_ms", value: { doubleValue: 16.7 } },
48
+ { key: "terminal.backpressure", value: { boolValue: true } },
49
+ ],
50
+ },
51
+ ],
52
+ },
53
+ ],
54
+ },
55
+ ],
56
+ });
57
+ });
58
+
59
+ test("phaseMarksToSpans builds a scope root with one child span per phase under one trace", () => {
60
+ const spans = phaseMarksToSpans("startup", [
61
+ { label: "renderer", elapsedMs: 10 },
62
+ { label: "firstFrame", elapsedMs: 20 },
63
+ ]);
64
+
65
+ expect(spans).toHaveLength(3);
66
+ const [root, first, second] = spans;
67
+
68
+ expect(root!.name).toBe("cueloop.startup");
69
+ expect(root!.parentSpanId).toBeUndefined();
70
+ expect(first!.name).toBe("cueloop.startup.renderer");
71
+ expect(first!.parentSpanId).toBe(root!.spanId);
72
+ expect(second!.parentSpanId).toBe(root!.spanId);
73
+ expect(new Set(spans.map((each) => each.traceId)).size).toBe(1);
74
+ });
75
+
76
+ test("postOtlpTrace sends no request when the endpoint is unset", async () => {
77
+ const requests: string[] = [];
78
+ const server = Bun.serve({
79
+ port: 0,
80
+ fetch(request) {
81
+ requests.push(request.url);
82
+
83
+ return new Response("{}");
84
+ },
85
+ });
86
+
87
+ try {
88
+ await postOtlpTrace("cueloop-client", [span]);
89
+ } finally {
90
+ await server.stop(true);
91
+ }
92
+
93
+ expect(requests).toHaveLength(0);
94
+ });
95
+
96
+ test("postOtlpTrace POSTs OTLP JSON to /v1/traces when the endpoint is set", async () => {
97
+ const requests: string[] = [];
98
+ const server = Bun.serve({
99
+ port: 0,
100
+ async fetch(request) {
101
+ requests.push(`${request.method} ${new URL(request.url).pathname}\n${await request.text()}`);
102
+
103
+ return new Response("{}");
104
+ },
105
+ });
106
+
107
+ // a trailing slash must not double up into "//v1/traces"
108
+ process.env.OTEL_EXPORTER_OTLP_ENDPOINT = `http://127.0.0.1:${server.port}/`;
109
+ try {
110
+ await postOtlpTrace("cueloop-client", [span]);
111
+ } finally {
112
+ await server.stop(true);
113
+ }
114
+
115
+ expect(requests).toHaveLength(1);
116
+ expect(requests[0]).toContain("POST /v1/traces");
117
+ expect(requests[0]).toContain('"service.name"');
118
+ expect(requests[0]).toContain("cueloop.interaction");
119
+ });
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Ship perf spans to a local OTLP/HTTP trace viewer (kundi) so an interaction's phases read as a
3
+ * callstack. Every export is a no-op unless OTEL_EXPORTER_OTLP_ENDPOINT is set. Tracing never throws
4
+ * into the app: a failed POST is swallowed. Attributes are scalar-only, matching the viewer's decoder.
5
+ */
6
+
7
+ import type { PhaseMark } from "./perf-timings";
8
+
9
+ export type SpanAttributeValue =
10
+ | { stringValue: string }
11
+ | { doubleValue: number }
12
+ | { boolValue: boolean };
13
+
14
+ export interface SpanAttribute {
15
+ key: string;
16
+ value: SpanAttributeValue;
17
+ }
18
+
19
+ export interface TraceSpan {
20
+ traceId: string;
21
+ spanId: string;
22
+ parentSpanId?: string;
23
+ name: string;
24
+ startTimeUnixNano: string;
25
+ endTimeUnixNano: string;
26
+ attributes?: readonly SpanAttribute[];
27
+ }
28
+
29
+ function randomHex(bytes: number): string {
30
+ const buffer = new Uint8Array(bytes);
31
+
32
+ crypto.getRandomValues(buffer);
33
+
34
+ return Array.from(buffer, (byte) => byte.toString(16).padStart(2, "0")).join("");
35
+ }
36
+
37
+ /** A 16-byte trace id as 32 lowercase hex chars, per the OTLP wire format. */
38
+ export function newTraceId(): string {
39
+ return randomHex(16);
40
+ }
41
+
42
+ /** An 8-byte span id as 16 lowercase hex chars, per the OTLP wire format. */
43
+ export function newSpanId(): string {
44
+ return randomHex(8);
45
+ }
46
+
47
+ /** Build the OTLP/JSON `resourceSpans` body the trace viewer's `/v1/traces` decoder reads. */
48
+ export function buildOtlpTracePayload(serviceName: string, spans: readonly TraceSpan[]) {
49
+ return {
50
+ resourceSpans: [
51
+ {
52
+ resource: {
53
+ attributes: [{ key: "service.name", value: { stringValue: serviceName } }],
54
+ },
55
+ scopeSpans: [
56
+ {
57
+ spans: spans.map((span) => ({
58
+ traceId: span.traceId,
59
+ spanId: span.spanId,
60
+ // the viewer reads a missing parent as root; an empty string is the same, without a conditional key
61
+ parentSpanId: span.parentSpanId ?? "",
62
+ name: span.name,
63
+ startTimeUnixNano: span.startTimeUnixNano,
64
+ endTimeUnixNano: span.endTimeUnixNano,
65
+ attributes: (span.attributes ?? []).map((attribute) => ({
66
+ key: attribute.key,
67
+ value: attribute.value,
68
+ })),
69
+ })),
70
+ },
71
+ ],
72
+ },
73
+ ],
74
+ };
75
+ }
76
+
77
+ function nanosFromPerfMs(milliseconds: number): string {
78
+ const originNs = BigInt(Math.round(performance.timeOrigin * 1e6));
79
+
80
+ return String(originNs + BigInt(Math.round(milliseconds * 1e6)));
81
+ }
82
+
83
+ /** Turn drained phase marks into a span tree: a `scope` root with one child span per phase, in order. */
84
+ export function phaseMarksToSpans(scope: string, marks: readonly PhaseMark[]): TraceSpan[] {
85
+ const totalMs = marks.reduce((sum, mark) => sum + mark.elapsedMs, 0);
86
+ const endMs = performance.now();
87
+ const startMs = endMs - totalMs;
88
+ const traceId = newTraceId();
89
+ const rootSpanId = newSpanId();
90
+ const root: TraceSpan = {
91
+ traceId,
92
+ spanId: rootSpanId,
93
+ name: `cueloop.${scope}`,
94
+ startTimeUnixNano: nanosFromPerfMs(startMs),
95
+ endTimeUnixNano: nanosFromPerfMs(endMs),
96
+ };
97
+
98
+ let cursorMs = startMs;
99
+ const children = marks.map((mark) => {
100
+ const phaseStartMs = cursorMs;
101
+
102
+ cursorMs += mark.elapsedMs;
103
+
104
+ return {
105
+ traceId,
106
+ spanId: newSpanId(),
107
+ parentSpanId: rootSpanId,
108
+ name: `cueloop.${scope}.${mark.label}`,
109
+ startTimeUnixNano: nanosFromPerfMs(phaseStartMs),
110
+ endTimeUnixNano: nanosFromPerfMs(cursorMs),
111
+ };
112
+ });
113
+
114
+ return [root, ...children];
115
+ }
116
+
117
+ /** POST the spans to `${OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces` as OTLP/JSON; a no-op when the endpoint is unset. */
118
+ export async function postOtlpTrace(
119
+ serviceName: string,
120
+ spans: readonly TraceSpan[],
121
+ ): Promise<void> {
122
+ const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
123
+
124
+ if (endpoint === undefined || spans.length === 0) return;
125
+
126
+ // strip a trailing slash so an endpoint like "http://host:4318/" does not become "//v1/traces"
127
+ const base = endpoint.replace(/\/+$/, "");
128
+
129
+ await fetch(`${base}/v1/traces`, {
130
+ method: "POST",
131
+ headers: { "content-type": "application/json" },
132
+ body: JSON.stringify(buildOtlpTracePayload(serviceName, spans)),
133
+ }).catch(() => undefined);
134
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Report the drained perf marks for a scope two ways from one set of instrumentation: a phase block on
3
+ * stderr when CUELOOP_PERF=1, and a kundi span tree when the OTLP endpoint is set. No-op when neither is on.
4
+ */
5
+
6
+ import { formatPerfBlock, takePerfMarks } from "./perf-timings";
7
+ import { phaseMarksToSpans, postOtlpTrace } from "./otlp-trace";
8
+
9
+ const PERF_SERVICE_NAME = "cueloop-client";
10
+
11
+ export function reportPerfMarks(scope: string): void {
12
+ const marks = takePerfMarks();
13
+
14
+ if (marks.length === 0) return;
15
+
16
+ if (process.env.CUELOOP_PERF === "1") process.stderr.write(formatPerfBlock(scope, marks));
17
+ void postOtlpTrace(PERF_SERVICE_NAME, phaseMarksToSpans(scope, marks));
18
+ }
@@ -0,0 +1,18 @@
1
+ import { expect, test } from "bun:test";
2
+ import { formatPerfBlock } from "./perf-timings";
3
+
4
+ test("formatPerfBlock renders labeled rows and a summed TOTAL", () => {
5
+ const block = formatPerfBlock("startup", [
6
+ { label: "connect", elapsedMs: 12.34 },
7
+ { label: "firstRender", elapsedMs: 7.66 },
8
+ ]);
9
+
10
+ expect(block).toContain("--- perf startup ---");
11
+ expect(block).toContain("connect 12.3ms");
12
+ expect(block).toContain("firstRender 7.7ms");
13
+ expect(block).toContain("TOTAL 20.0ms");
14
+ });
15
+
16
+ test("formatPerfBlock totals to zero for no marks", () => {
17
+ expect(formatPerfBlock("startup", [])).toContain("TOTAL 0.0ms");
18
+ });
@@ -0,0 +1,39 @@
1
+ /** Startup phase timing for the perf harness: records elapsed ms between marks and flushes a
2
+ * labeled block to stderr. Every entry point is a no-op unless the CUELOOP_PERF env var is "1". */
3
+
4
+ export interface PhaseMark {
5
+ label: string;
6
+ elapsedMs: number;
7
+ }
8
+
9
+ const phaseMarks: PhaseMark[] = [];
10
+ let lastMarkAt: number | null = null;
11
+
12
+ /** Marks record when the stderr timer (CUELOOP_PERF=1) or the kundi exporter (OTEL endpoint) is on. */
13
+ function perfInstrumentationEnabled(): boolean {
14
+ return process.env.CUELOOP_PERF === "1" || process.env.OTEL_EXPORTER_OTLP_ENDPOINT !== undefined;
15
+ }
16
+
17
+ /** Record the elapsed ms since the previous mark (or process start) under `label`. */
18
+ export function perfMark(label: string): void {
19
+ if (!perfInstrumentationEnabled()) return;
20
+ const now = performance.now();
21
+
22
+ phaseMarks.push({ label, elapsedMs: now - (lastMarkAt ?? 0) });
23
+ lastMarkAt = now;
24
+ }
25
+
26
+ /** Drain the recorded marks for a reporter to format or export; empties the buffer for the next scope. */
27
+ export function takePerfMarks(): PhaseMark[] {
28
+ lastMarkAt = null;
29
+
30
+ return phaseMarks.splice(0, phaseMarks.length);
31
+ }
32
+
33
+ /** Render phase deltas as the stderr block: a header, one indented row per mark, a summed TOTAL. */
34
+ export function formatPerfBlock(scope: string, marks: readonly PhaseMark[]): string {
35
+ const totalMs = marks.reduce((sum, mark) => sum + mark.elapsedMs, 0);
36
+ const rows = marks.map((mark) => ` ${mark.label} ${mark.elapsedMs.toFixed(1)}ms`);
37
+
38
+ return [`--- perf ${scope} ---`, ...rows, ` TOTAL ${totalMs.toFixed(1)}ms`, ""].join("\n");
39
+ }
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { DARK } from "../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../stories/story";
4
+ import { PrototypeContentView } from "./PrototypeContentView";
5
+
6
+ export const meta: StoryMeta = { title: "Surfaces/PrototypeContentView" };
7
+
8
+ // The story renderer reports no kitty graphics, so the view shows its fallback.
9
+ export const Unsupported: Story = {
10
+ render: () => (
11
+ <PrototypeContentView
12
+ prototypePath="/tmp/example.html"
13
+ canComment={true}
14
+ onCommentElement={() => {}}
15
+ />
16
+ ),
17
+ expectedColors: [DARK.textDim],
18
+ size: { width: 80, height: 24 },
19
+ };