@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,42 @@
1
+ import { expect, test } from "bun:test";
2
+ import React from "react";
3
+ import { testRender } from "@opentui/react/test-utils";
4
+ import { CommentRow } from "./AnnotationCards";
5
+ import { DARK } from "../../appearance/theme";
6
+ import { TooltipProvider } from "../../ui/components/Tooltip";
7
+ import { locateText, settle } from "../../testing/test-support";
8
+
9
+ test("submitted comment checks stay visible on hover and navigate to the reply", async () => {
10
+ let navigations = 0;
11
+ const setup = await testRender(
12
+ <TooltipProvider>
13
+ <CommentRow
14
+ annotation={{
15
+ id: "submitted",
16
+ kind: "comment",
17
+ body: "Explain the change",
18
+ anchor: { quote: "change", prefix: "", suffix: "", blockIndex: 0, start: 0, end: 6 },
19
+ createdAt: "2026-10-04",
20
+ }}
21
+ tokens={DARK}
22
+ action={{ label: "View reply", run: () => navigations++ }}
23
+ />
24
+ </TooltipProvider>,
25
+ { width: 60, height: 8 },
26
+ );
27
+
28
+ try {
29
+ await settle(setup);
30
+ const checks = locateText(setup, "✓✓");
31
+
32
+ await setup.mockMouse.moveTo(checks.column, checks.row);
33
+ await settle(setup);
34
+ expect(setup.captureCharFrame()).toContain("✓✓");
35
+ expect(setup.captureCharFrame()).toContain("View reply");
36
+ expect(setup.captureCharFrame()).not.toContain("↓");
37
+ await setup.mockMouse.click(checks.column, checks.row);
38
+ expect(navigations).toBe(1);
39
+ } finally {
40
+ setup.renderer.destroy();
41
+ }
42
+ });
@@ -0,0 +1,364 @@
1
+ /**
2
+ * The inline comment pieces every annotated surface renders: the composer (a
3
+ * textarea in a card row), the "/" actions-and-skills palette below it, the
4
+ * edge-segmented discussion card, and one comment row. The plan thread view
5
+ * and the diff sheet both draw their comments with these, so a discussion looks
6
+ * and behaves the same on prose and on code.
7
+ */
8
+
9
+ import React, { useContext, useLayoutEffect, useRef, useState } from "react";
10
+ import { usePaste } from "@opentui/react";
11
+ import type {
12
+ BoxRenderable,
13
+ KeyBinding,
14
+ MouseEvent as TerminalMouseEvent,
15
+ TextareaRenderable,
16
+ } from "@opentui/core";
17
+ import type { Annotation } from "@cueloop/schema";
18
+ import type { Theme } from "../../appearance/theme";
19
+ import { useTooltip } from "../../ui/components/Tooltip";
20
+ import { lighten } from "../annotation-palette";
21
+ import { useFrameMeasure } from "../../ui/use-frame-measure";
22
+ import { imagePlaceholder, looksLikeBinaryPaste } from "../../integrations/pasted-image";
23
+ import { skillReferenceRanges, type SlashItem } from "../../keyboard/slash-palette";
24
+ import { PaletteNamesContext } from "../../keyboard/skills";
25
+ import { referenceStyleFor } from "../../ui/components/syntax-highlight";
26
+
27
+ /* -------------------------------------------------------------- composer */
28
+
29
+ // Option+Enter saves a comment; Ctrl+Enter may additionally invoke the agent.
30
+ // Plain Enter breaks the line. Command+Enter belongs to the terminal.
31
+ const COMPOSE_KEY_BINDINGS: KeyBinding[] = [
32
+ { name: "return", super: true, action: "submit" },
33
+ { name: "return", meta: true, action: "submit" },
34
+ { name: "return", ctrl: true, action: "submit" },
35
+ { name: "return", action: "newline" },
36
+ { name: "return", shift: true, action: "newline" },
37
+ ];
38
+
39
+ export function composeRowCount(text: string, contentWidth: number): number {
40
+ const usableWidth = contentWidth > 0 ? contentWidth : Number.MAX_SAFE_INTEGER;
41
+ let visualRowCount = 0;
42
+
43
+ for (const line of text.split("\n")) {
44
+ visualRowCount += Math.max(1, Math.ceil(line.length / usableWidth));
45
+ }
46
+
47
+ // no cap: the card is the discussion, and folding is its containment
48
+ return Math.max(1, visualRowCount);
49
+ }
50
+
51
+ export function Composer({
52
+ seed,
53
+ glyph,
54
+ tokens,
55
+ onSave,
56
+ onReady,
57
+ onInput,
58
+ placeholder,
59
+ agentEnabled = false,
60
+ }: {
61
+ seed: string;
62
+ glyph: string | null;
63
+ tokens: Theme;
64
+ onSave: (body: string) => void;
65
+ onReady: (readText: () => string) => void;
66
+ onInput: (text: string, caret: number) => void;
67
+ placeholder?: string;
68
+ agentEnabled?: boolean;
69
+ }): React.ReactNode {
70
+ const editorRef = useRef<TextareaRenderable | null>(null);
71
+ const pastedImageCount = useRef(0);
72
+ const [rows, setRows] = useState(1);
73
+ // action and skill names whose "/name" references paint in the accent color
74
+ const referenceNames = useContext(PaletteNamesContext);
75
+
76
+ usePaste((event) => {
77
+ const editor = editorRef.current;
78
+
79
+ if (!editor || !editor.focused || !looksLikeBinaryPaste(event.bytes)) return;
80
+ event.preventDefault();
81
+ pastedImageCount.current += 1;
82
+ editor.editBuffer.insertText(imagePlaceholder(pastedImageCount.current));
83
+ });
84
+
85
+ const paintReferences = (editor: TextareaRenderable): void => {
86
+ const { styleId } = referenceStyleFor(tokens);
87
+
88
+ editor.editBuffer.clearAllHighlights();
89
+ for (const range of skillReferenceRanges(editor.plainText, referenceNames)) {
90
+ editor.editBuffer.addHighlightByCharRange({ ...range, styleId });
91
+ }
92
+ };
93
+
94
+ // synchronously at commit, before the next tty key is read: signalling readiness in a
95
+ // post-paint effect lets a fast burst race the mount, splitting input between the seed
96
+ // buffer and this textarea out of order (#365)
97
+ useLayoutEffect(() => {
98
+ const editor = editorRef.current;
99
+
100
+ if (!editor) return;
101
+ editor.cursorOffset = seed.length;
102
+ editor.editBuffer.setSyntaxStyle(referenceStyleFor(tokens).style);
103
+ paintReferences(editor);
104
+ setRows(composeRowCount(editor.plainText, editor.width));
105
+ onReady(() => editorRef.current?.plainText ?? "");
106
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
+ }, []);
108
+
109
+ return (
110
+ <box style={{ flexDirection: "row" }}>
111
+ {glyph !== null ? (
112
+ <text selectable={false} fg={tokens.text} style={{ flexShrink: 0 }}>{`${glyph} `}</text>
113
+ ) : null}
114
+ <textarea
115
+ ref={editorRef}
116
+ focused
117
+ initialValue={seed}
118
+ placeholder={placeholder}
119
+ placeholderColor={tokens.textDim}
120
+ cursorStyle={{ style: "block", blinking: true }}
121
+ keyBindings={
122
+ agentEnabled
123
+ ? COMPOSE_KEY_BINDINGS.filter((binding) => !binding.super)
124
+ : COMPOSE_KEY_BINDINGS
125
+ }
126
+ onSubmit={() => onSave(editorRef.current?.plainText ?? "")}
127
+ onContentChange={() => {
128
+ const editor = editorRef.current;
129
+
130
+ if (!editor) return;
131
+ paintReferences(editor);
132
+ setRows(composeRowCount(editor.plainText, editor.width));
133
+ onInput(editor.plainText, editor.cursorOffset);
134
+ }}
135
+ style={{
136
+ height: rows,
137
+ flexGrow: 1,
138
+ backgroundColor: "transparent",
139
+ focusedBackgroundColor: "transparent",
140
+ textColor: tokens.text,
141
+ focusedTextColor: tokens.text,
142
+ }}
143
+ />
144
+ </box>
145
+ );
146
+ }
147
+
148
+ /* ---------------------------------------------------------- slash palette */
149
+
150
+ function SlashList({
151
+ items,
152
+ selected,
153
+ tokens,
154
+ }: {
155
+ items: SlashItem[];
156
+ selected: number;
157
+ tokens: Theme;
158
+ }): React.ReactNode {
159
+ const WINDOW = 5;
160
+ const start = Math.max(0, Math.min(selected - 2, items.length - WINDOW));
161
+ const visible = items.slice(start, start + WINDOW);
162
+
163
+ if (items.length === 0) return <text fg={tokens.textDim}>no matching actions</text>;
164
+
165
+ const nameColumn = Math.min(36, Math.max(16, ...visible.map((item) => item.name.length + 5)));
166
+
167
+ return (
168
+ <box style={{ flexDirection: "column" }}>
169
+ {visible.map((item, offset) => {
170
+ const index = start + offset;
171
+ const isSelected = index === selected;
172
+ const name = `${isSelected ? "→ " : " "}/${item.name}`.padEnd(nameColumn);
173
+
174
+ return (
175
+ <text key={item.name} style={{ flexShrink: 0, wrapMode: "none" }}>
176
+ <span fg={isSelected ? tokens.accent : tokens.textMuted}>{name}</span>
177
+ <span fg={tokens.textDim}>{item.description}</span>
178
+ </text>
179
+ );
180
+ })}
181
+ {items.length > WINDOW ? (
182
+ <text fg={tokens.textDim}>{`(${selected + 1}/${items.length})`}</text>
183
+ ) : null}
184
+ </box>
185
+ );
186
+ }
187
+
188
+ /** Below the composer: the actions/skills list while the caret is on a "/word", else nothing. */
189
+ export function ComposerPalette({
190
+ slashActive,
191
+ slashItems,
192
+ slashIndex,
193
+ tokens,
194
+ }: {
195
+ slashActive: boolean;
196
+ slashItems: SlashItem[];
197
+ slashIndex: number;
198
+ tokens: Theme;
199
+ }): React.ReactNode {
200
+ if (!slashActive) return null;
201
+
202
+ return (
203
+ <box style={{ flexDirection: "column", marginLeft: 3 }}>
204
+ <SlashList
205
+ items={slashItems}
206
+ selected={Math.min(slashIndex, Math.max(0, slashItems.length - 1))}
207
+ tokens={tokens}
208
+ />
209
+ </box>
210
+ );
211
+ }
212
+
213
+ /* ------------------------------------------------------------ discussion card */
214
+
215
+ /** One card row group with its own accent-edge color, one per voice. */
216
+ export interface EdgeSegment {
217
+ color: string;
218
+ node: React.ReactNode;
219
+ }
220
+
221
+ /**
222
+ * The comment-card frame. The accent edge is drawn by hand so that (a) its
223
+ * corner cells are half-height glyphs ("╷"/"╵") terminating flush with the
224
+ * hairline rows, and (b) each voice's rows carry their own edge color with
225
+ * zero gap. Focus lightens every segment; the hairlines never change.
226
+ */
227
+ export function DiscussionCard({
228
+ segments,
229
+ tokens,
230
+ focused = false,
231
+ onFocus,
232
+ }: {
233
+ segments: EdgeSegment[];
234
+ tokens: Theme;
235
+ focused?: boolean;
236
+ onFocus?: () => void;
237
+ }): React.ReactNode {
238
+ const segmentRefs = useRef<Array<BoxRenderable | null>>([]);
239
+ const noHeights: number[] = [];
240
+ const heights = useFrameMeasure(
241
+ () => segmentRefs.current.map((renderable) => renderable?.height ?? 0),
242
+ (left, right) => left.join() === right.join(),
243
+ noHeights,
244
+ );
245
+
246
+ const edgeColor = (color: string): string => (focused ? lighten(color) : color);
247
+ const glyphs = focused
248
+ ? { top: "╻", middle: "┃", bottom: "╹" }
249
+ : { top: "╷", middle: "│", bottom: "╵" };
250
+ const edgeRows: Array<{ glyph: string; color: string }> = [
251
+ {
252
+ glyph: glyphs.top,
253
+ color: edgeColor(segments[0]?.color ?? tokens.border),
254
+ },
255
+ ];
256
+
257
+ for (let index = 0; index < segments.length; index++) {
258
+ const segmentRows = Math.max(1, heights[index] ?? 1);
259
+
260
+ for (let row = 0; row < segmentRows; row++) {
261
+ edgeRows.push({
262
+ glyph: glyphs.middle,
263
+ color: edgeColor(segments[index]!.color),
264
+ });
265
+ }
266
+ }
267
+ edgeRows.push({
268
+ glyph: glyphs.bottom,
269
+ color: edgeColor(segments[segments.length - 1]?.color ?? tokens.border),
270
+ });
271
+
272
+ return (
273
+ <box style={{ flexDirection: "row", marginTop: 0, marginLeft: 2 }}>
274
+ <text selectable={false} style={{ flexShrink: 0, width: 1 }}>
275
+ {edgeRows.map((row, index) => (
276
+ <span key={index} fg={row.color}>
277
+ {index < edgeRows.length - 1 ? `${row.glyph}\n` : row.glyph}
278
+ </span>
279
+ ))}
280
+ </text>
281
+ <box
282
+ onMouseDown={onFocus}
283
+ style={{
284
+ flexGrow: 1,
285
+ flexDirection: "column",
286
+ border: ["top", "right", "bottom"],
287
+ borderStyle: "single",
288
+ borderColor: tokens.border,
289
+ backgroundColor: "transparent",
290
+ paddingLeft: 1,
291
+ paddingRight: 1,
292
+ }}
293
+ >
294
+ {segments.map((segment, index) => (
295
+ <box
296
+ key={index}
297
+ ref={(renderable: BoxRenderable | null) => {
298
+ segmentRefs.current[index] = renderable;
299
+ }}
300
+ style={{ flexDirection: "column" }}
301
+ >
302
+ {segment.node}
303
+ </box>
304
+ ))}
305
+ </box>
306
+ </box>
307
+ );
308
+ }
309
+
310
+ export function CommentRow({
311
+ annotation,
312
+ tokens,
313
+ authorLabel,
314
+ action,
315
+ }: {
316
+ annotation: Annotation;
317
+ tokens: Theme;
318
+ /** The author's resolved display name, shown as a tooltip when the dot is hovered. */
319
+ authorLabel?: string;
320
+ action?: { label: string; run: () => void };
321
+ }): React.ReactNode {
322
+ // own comments (no author) wear the filled dot, collaborators the outline
323
+ const own = annotation.author === undefined;
324
+ const glyph = own ? "●" : "○";
325
+ const glyphColor = annotation.orphan ? tokens.warning : own ? tokens.text : tokens.textMuted;
326
+ const { showTooltip, hideTooltip } = useTooltip();
327
+ const dotHover = authorLabel
328
+ ? {
329
+ onMouseOver: (event: TerminalMouseEvent) => showTooltip(authorLabel, event.x, event.y),
330
+ onMouseOut: hideTooltip,
331
+ }
332
+ : {};
333
+
334
+ return (
335
+ <box style={{ flexDirection: "column" }}>
336
+ {annotation.body.split("\n").map((line, lineIndex) => (
337
+ <box key={lineIndex} style={{ flexDirection: "row" }}>
338
+ <box style={{ flexShrink: 0 }} {...(lineIndex === 0 ? dotHover : {})}>
339
+ <text selectable={false} fg={glyphColor}>
340
+ {lineIndex === 0 ? `${glyph} ` : " "}
341
+ </text>
342
+ </box>
343
+ <text fg={tokens.text} style={{ wrapMode: "word", flexGrow: 1, flexShrink: 1 }}>
344
+ {line}
345
+ </text>
346
+ {lineIndex === 0 && action ? (
347
+ <text
348
+ fg={tokens.blue}
349
+ onMouseUp={action.run}
350
+ onMouseOver={(event: TerminalMouseEvent) => {
351
+ showTooltip(action.label, event.x, event.y);
352
+ }}
353
+ onMouseOut={() => {
354
+ hideTooltip();
355
+ }}
356
+ >
357
+ {action.label === "Retry" ? "Retry" : "✓✓"}
358
+ </text>
359
+ ) : null}
360
+ </box>
361
+ ))}
362
+ </box>
363
+ );
364
+ }
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import type { Annotation } from "@cueloop/schema";
3
+ import { DARK } from "../../appearance/theme";
4
+ import type { Discussion } from "../discussions";
5
+ import type { TextSpan } from "../thread-selection";
6
+ import { RootOverlayProvider } from "../../ui/components/RootOverlay";
7
+ import { DiscussionMarkerRail } from "./DiscussionMarkerRail";
8
+ import type { Story, StoryMeta } from "../../stories/story";
9
+
10
+ export const meta: StoryMeta = { title: "Chrome/DiscussionMarkerRail" };
11
+
12
+ function comment(id: string, body: string): Annotation {
13
+ return {
14
+ id,
15
+ kind: "comment",
16
+ anchor: { quote: body, prefix: "", suffix: "" },
17
+ body,
18
+ createdAt: "2026-09-08T00:00:00.000Z",
19
+ };
20
+ }
21
+
22
+ function discussion(row: number, body: string): Discussion {
23
+ const span: TextSpan = { start: { blockIndex: row, char: 0 }, end: { blockIndex: row, char: 4 } };
24
+
25
+ return {
26
+ key: `${row}`,
27
+ rootId: `a${row}`,
28
+ blockIndex: row,
29
+ span,
30
+ annotations: [comment(`a${row}`, body)],
31
+ };
32
+ }
33
+
34
+ const discussions = [
35
+ discussion(2, "Map needs an eviction story."),
36
+ discussion(5, "why a Map here?"),
37
+ discussion(9, "name this constant"),
38
+ ];
39
+
40
+ // The centered dot column: one dot per discussion, drawn on a surface's right edge.
41
+ export const Dots: Story = {
42
+ render: () => (
43
+ <RootOverlayProvider>
44
+ <box
45
+ style={{ width: 24, height: 10, flexDirection: "row", backgroundColor: DARK.background }}
46
+ >
47
+ <box style={{ flexGrow: 1 }} />
48
+ <DiscussionMarkerRail discussions={discussions} spanQuote={() => "x"} onJump={() => {}} />
49
+ </box>
50
+ </RootOverlayProvider>
51
+ ),
52
+ expectedColors: [DARK.textDim],
53
+ size: { width: 24, height: 10 },
54
+ };
@@ -0,0 +1,186 @@
1
+ /**
2
+ * The right-edge discussion navigator: a vertically-centered column of dots, one
3
+ * per discussion, that highlights on hover, shows a floating preview, and jumps
4
+ * on click. The plan thread view and each diff editor group render one on their
5
+ * right edge, so comment navigation reads the same on prose and on code. The
6
+ * preview draws at the app root (OpenTUI has no z-index) so no pane border slices it.
7
+ */
8
+
9
+ import React, { useEffect, useRef, useState, type RefObject } from "react";
10
+ import type { BoxRenderable, ScrollBoxRenderable } from "@opentui/core";
11
+ import type { Theme } from "../../appearance/theme";
12
+ import type { TextSpan } from "../thread-selection";
13
+ import type { Discussion } from "../discussions";
14
+ import { useFrameMeasure } from "../../ui/use-frame-measure";
15
+ import { useComponentTheme } from "../../appearance/components/theme-context";
16
+ import { useRootOverlay } from "../../ui/components/RootOverlay";
17
+ import { OverlayScrollbar } from "../../ui/components/OverlayScrollbar";
18
+
19
+ interface HoveredMarker {
20
+ key: string;
21
+ screenY: number;
22
+ anchorX: number;
23
+ }
24
+
25
+ function ScrollMarkers({
26
+ discussions,
27
+ hovered,
28
+ focused,
29
+ tokens,
30
+ onHover,
31
+ onJump,
32
+ }: {
33
+ discussions: Discussion[];
34
+ hovered: string | null;
35
+ focused: string | null;
36
+ tokens: Theme;
37
+ onHover: (marker: HoveredMarker | null) => void;
38
+ onJump: (key: string) => void;
39
+ }): React.ReactNode {
40
+ const railRef = useRef<BoxRenderable | null>(null);
41
+ const height = useFrameMeasure(
42
+ () => railRef.current?.height ?? 0,
43
+ (left, right) => left === right,
44
+ 0,
45
+ );
46
+
47
+ const firstRow = Math.max(0, Math.floor((height - discussions.length) / 2));
48
+ const rowFor = (index: number): number => firstRow + index;
49
+ const indexAt = (row: number): number | null => {
50
+ const index = row - firstRow;
51
+
52
+ return index >= 0 && index < discussions.length ? index : null;
53
+ };
54
+ const hoveredIndex = discussions.findIndex((discussion) => discussion.key === hovered);
55
+ const focusedIndex = discussions.findIndex((discussion) => discussion.key === focused);
56
+ const isActive = (index: number): boolean => index === hoveredIndex || index === focusedIndex;
57
+ const colorFor = (index: number): string => {
58
+ if (isActive(index)) return tokens.text;
59
+ if (hoveredIndex >= 0 && discussions.length >= 5 && Math.abs(index - hoveredIndex) === 1) {
60
+ return tokens.textMuted;
61
+ }
62
+
63
+ return tokens.textDim;
64
+ };
65
+
66
+ return (
67
+ <box
68
+ ref={railRef}
69
+ style={{ width: 3, flexShrink: 0, flexDirection: "column" }}
70
+ onMouseMove={(event) => {
71
+ const railY = railRef.current?.y ?? 0;
72
+ const index = indexAt(event.y - railY);
73
+
74
+ onHover(
75
+ index === null
76
+ ? null
77
+ : {
78
+ key: discussions[index]!.key,
79
+ // screen coordinates so the preview can render at the app root, above every pane rule
80
+ screenY: railY + rowFor(index),
81
+ anchorX: railRef.current?.x ?? 0,
82
+ },
83
+ );
84
+ }}
85
+ onMouseOut={() => onHover(null)}
86
+ onMouseDown={(event) => {
87
+ const index = indexAt(event.y - (railRef.current?.y ?? 0));
88
+
89
+ if (index !== null) onJump(discussions[index]!.key);
90
+ }}
91
+ >
92
+ {discussions.map((discussion, index) => (
93
+ <text
94
+ key={discussion.key}
95
+ selectable={false}
96
+ style={{ position: "absolute", top: rowFor(index), left: 0 }}
97
+ fg={colorFor(index)}
98
+ >
99
+ {(isActive(index) ? "●" : "○").padStart(2)}
100
+ </text>
101
+ ))}
102
+ </box>
103
+ );
104
+ }
105
+
106
+ export interface DiscussionMarkerRailProps {
107
+ discussions: Discussion[];
108
+ spanQuote: (span: TextSpan) => string;
109
+ focusedKey?: string | null;
110
+ onJump: (key: string) => void;
111
+ scrollbox?: RefObject<ScrollBoxRenderable | null>;
112
+ theme?: Theme;
113
+ }
114
+
115
+ export function DiscussionMarkerRail({
116
+ discussions,
117
+ spanQuote,
118
+ focusedKey = null,
119
+ onJump,
120
+ scrollbox,
121
+ theme,
122
+ }: DiscussionMarkerRailProps): React.ReactNode {
123
+ const tokens = useComponentTheme(theme);
124
+ const [hovered, setHovered] = useState<HoveredMarker | null>(null);
125
+ const { setOverlay, clearOverlay } = useRootOverlay();
126
+
127
+ const preview = (): React.ReactNode => {
128
+ if (hovered === null) return null;
129
+ const discussion = discussions.find((candidate) => candidate.key === hovered.key);
130
+
131
+ if (!discussion) return null;
132
+ const quote = spanQuote(discussion.span);
133
+ const lastComment = discussion.annotations.at(-1)!;
134
+
135
+ return (
136
+ <box
137
+ style={{
138
+ position: "absolute",
139
+ top: Math.max(0, hovered.screenY - 1),
140
+ left: Math.max(0, hovered.anchorX - 48),
141
+ width: 48,
142
+ flexDirection: "column",
143
+ border: true,
144
+ borderStyle: "single",
145
+ borderColor: tokens.border,
146
+ backgroundColor: tokens.elevated,
147
+ paddingLeft: 1,
148
+ paddingRight: 1,
149
+ }}
150
+ >
151
+ <text fg={tokens.textDim}>{`"${quote.slice(0, 42)}"`}</text>
152
+ <text fg={tokens.text}>
153
+ {`${lastComment.author === undefined ? "●" : "○"} ${lastComment.body}`.slice(0, 44)}
154
+ </text>
155
+ <text fg={tokens.textDim}>
156
+ {`${discussion.annotations.length} comment${
157
+ discussion.annotations.length === 1 ? "" : "s"
158
+ } · click to jump`}
159
+ </text>
160
+ </box>
161
+ );
162
+ };
163
+
164
+ // render the preview at the app root (above every pane rule); OpenTUI has no z-index, so an
165
+ // absolute box inside this pane would be sliced by the next pane's border
166
+ useEffect(() => {
167
+ setOverlay("discussion-preview", preview());
168
+
169
+ return () => clearOverlay("discussion-preview");
170
+ // eslint-disable-next-line react-hooks/exhaustive-deps
171
+ }, [hovered, discussions, tokens]);
172
+
173
+ return (
174
+ <>
175
+ <ScrollMarkers
176
+ discussions={discussions}
177
+ hovered={hovered?.key ?? null}
178
+ focused={focusedKey}
179
+ tokens={tokens}
180
+ onHover={setHovered}
181
+ onJump={onJump}
182
+ />
183
+ {scrollbox ? <OverlayScrollbar scrollbox={scrollbox} theme={theme} /> : null}
184
+ </>
185
+ );
186
+ }