@pellux/goodvibes-tui 2.0.21 → 2.1.1

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 (391) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/README.md +30 -29
  3. package/docs/README.md +8 -5
  4. package/docs/foundation-artifacts/operator-contract.json +1 -1
  5. package/package.json +28 -19
  6. package/src/cli/management.ts +5 -2
  7. package/src/cli/tui-startup.ts +34 -39
  8. package/src/core/agent-view-render.ts +157 -0
  9. package/src/core/approval-alert.ts +49 -11
  10. package/src/core/bookmark-navigation.ts +7 -1
  11. package/src/core/budget-breach-notifier.ts +27 -8
  12. package/src/core/compaction-receipt.ts +1 -1
  13. package/src/core/context-usage.ts +1 -1
  14. package/src/core/conversation-compaction-render.ts +3 -3
  15. package/src/core/conversation-display-only.ts +86 -0
  16. package/src/core/conversation-event-navigation.ts +27 -0
  17. package/src/core/conversation-line-cache.ts +216 -447
  18. package/src/core/conversation-render-context.ts +20 -27
  19. package/src/core/conversation-rendering.ts +157 -575
  20. package/src/core/conversation-splash-state.ts +6 -1
  21. package/src/core/conversation-types.ts +35 -0
  22. package/src/core/conversation-usage.ts +12 -0
  23. package/src/core/conversation.ts +130 -131
  24. package/src/core/long-task-notifier.ts +100 -31
  25. package/src/core/notices.ts +75 -0
  26. package/src/core/pairing-tailscale-gateway.ts +1 -1
  27. package/src/core/process-output-log.ts +143 -0
  28. package/src/core/rewind-receipt.ts +1 -1
  29. package/src/core/session-focus.ts +190 -0
  30. package/src/core/session-resume-core.ts +23 -81
  31. package/src/core/stream-event-wiring.ts +31 -5
  32. package/src/core/system-message-noise.ts +2 -2
  33. package/src/core/system-message-router.ts +16 -18
  34. package/src/core/turn-event-wiring.ts +272 -33
  35. package/src/core/work-tree-focus.ts +290 -0
  36. package/src/core/work-tree-fold-store.ts +143 -0
  37. package/src/core/work-tree-model.ts +799 -0
  38. package/src/core/work-tree-render.ts +384 -0
  39. package/src/core/work-tree-sources.ts +184 -0
  40. package/src/core/work-tree-wiring.ts +176 -0
  41. package/src/core/workstream-notification.ts +36 -6
  42. package/src/core/wrfc-notice-titles.ts +37 -0
  43. package/src/input/agents-modal-text.ts +119 -0
  44. package/src/input/agents-modal.ts +699 -0
  45. package/src/input/bookmark-modal.ts +33 -9
  46. package/src/input/changes-git.ts +251 -0
  47. package/src/input/changes-modal.ts +629 -0
  48. package/src/input/changes-model.ts +125 -0
  49. package/src/input/command-palette-types.ts +41 -0
  50. package/src/input/command-palette.ts +272 -0
  51. package/src/input/command-reference.ts +2 -2
  52. package/src/input/command-registry.ts +77 -35
  53. package/src/input/commands/acp-runtime.ts +19 -9
  54. package/src/input/commands/channel-runtime.ts +2 -2
  55. package/src/input/commands/ci-runtime.ts +1 -1
  56. package/src/input/commands/codebase-runtime.ts +1 -1
  57. package/src/input/commands/config.ts +7 -1
  58. package/src/input/commands/context-window.ts +25 -19
  59. package/src/input/commands/control-room-runtime.ts +18 -18
  60. package/src/input/commands/conversation-runtime.ts +0 -1
  61. package/src/input/commands/cost-runtime.ts +29 -20
  62. package/src/input/commands/diff-runtime.ts +35 -181
  63. package/src/input/commands/eval.ts +1 -1
  64. package/src/input/commands/experience-runtime.ts +3 -7
  65. package/src/input/commands/fresh-conversation.ts +86 -0
  66. package/src/input/commands/git-runtime.ts +5 -22
  67. package/src/input/commands/graph-runtime.ts +1 -1
  68. package/src/input/commands/guidance-runtime.ts +1 -1
  69. package/src/input/commands/health-runtime.ts +8 -9
  70. package/src/input/commands/hooks-runtime.ts +2 -2
  71. package/src/input/commands/hosted-runtime.ts +3 -3
  72. package/src/input/commands/incident-runtime.ts +3 -3
  73. package/src/input/commands/intelligence-runtime.ts +6 -9
  74. package/src/input/commands/knowledge.ts +2 -2
  75. package/src/input/commands/legacy-panel-command.ts +57 -0
  76. package/src/input/commands/local-auth-runtime.ts +8 -9
  77. package/src/input/commands/local-provider-runtime.ts +7 -4
  78. package/src/input/commands/local-runtime.ts +12 -15
  79. package/src/input/commands/local-setup-review.ts +1 -1
  80. package/src/input/commands/local-setup.ts +13 -19
  81. package/src/input/commands/memory.ts +1 -1
  82. package/src/input/commands/notify-runtime.ts +9 -0
  83. package/src/input/commands/operator-runtime.ts +10 -12
  84. package/src/input/commands/permissions-provenance.ts +3 -3
  85. package/src/input/commands/planning-runtime.ts +7 -7
  86. package/src/input/commands/platform-sandbox-runtime.ts +2 -2
  87. package/src/input/commands/platform-services-runtime.ts +1 -1
  88. package/src/input/commands/policy-dispatch.ts +2 -2
  89. package/src/input/commands/policy.ts +3 -3
  90. package/src/input/commands/provider.ts +1 -1
  91. package/src/input/commands/remote-runtime.ts +1 -1
  92. package/src/input/commands/review-runtime.ts +39 -94
  93. package/src/input/commands/rewind-runtime.ts +81 -108
  94. package/src/input/commands/runtime-services.ts +15 -17
  95. package/src/input/commands/services-runtime.ts +2 -2
  96. package/src/input/commands/session-content.ts +21 -5
  97. package/src/input/commands/session-workflow.ts +13 -38
  98. package/src/input/commands/settings-sync-runtime.ts +2 -2
  99. package/src/input/commands/shell-core.ts +19 -4
  100. package/src/input/commands/skills-runtime.ts +2 -2
  101. package/src/input/commands/status-runtime.ts +13 -0
  102. package/src/input/commands/subscription-runtime.ts +1 -1
  103. package/src/input/commands/tasks-runtime.ts +2 -8
  104. package/src/input/commands/work-plan-runtime.ts +4 -4
  105. package/src/input/commands/workstream-attempts.ts +24 -47
  106. package/src/input/commands/workstream-runtime.ts +2 -2
  107. package/src/input/commands/worktree-runtime.ts +3 -3
  108. package/src/input/commands.ts +3 -1
  109. package/src/input/composer-editor.ts +44 -0
  110. package/src/input/concealed-input.ts +1 -1
  111. package/src/input/config-modal-types.ts +20 -8
  112. package/src/input/config-modal.ts +106 -105
  113. package/src/input/confirm-dialog-types.ts +28 -0
  114. package/src/input/confirm-dialog.ts +70 -0
  115. package/src/input/delete-key-policy.ts +3 -3
  116. package/src/input/feed-context-factory.ts +15 -16
  117. package/src/input/handler-command-route.ts +7 -42
  118. package/src/input/handler-content-actions.ts +16 -15
  119. package/src/input/handler-feed-routes.ts +26 -211
  120. package/src/input/handler-feed.ts +68 -95
  121. package/src/input/handler-interactions.ts +5 -3
  122. package/src/input/handler-modal-routes.ts +215 -216
  123. package/src/input/handler-modal-stack.ts +81 -11
  124. package/src/input/handler-modal-token-routes.ts +4 -0
  125. package/src/input/handler-onboarding.ts +0 -1
  126. package/src/input/handler-picker-routes.ts +65 -107
  127. package/src/input/handler-session-view-route.ts +149 -0
  128. package/src/input/handler-shortcuts.ts +16 -154
  129. package/src/input/handler-types.ts +4 -2
  130. package/src/input/handler-ui-state.ts +45 -15
  131. package/src/input/handler-work-tree-route.ts +123 -0
  132. package/src/input/handler.ts +25 -70
  133. package/src/input/keybindings.ts +16 -71
  134. package/src/input/masked-entry-modal.ts +117 -0
  135. package/src/input/mcp-workspace.ts +48 -17
  136. package/src/input/model-picker-filter.ts +8 -0
  137. package/src/input/model-picker-open.ts +99 -0
  138. package/src/input/model-picker-types.ts +1 -1
  139. package/src/input/model-picker.ts +92 -9
  140. package/src/input/notifications-modal.ts +123 -0
  141. package/src/input/overlay-filter.ts +26 -0
  142. package/src/input/profile-picker-modal.ts +37 -8
  143. package/src/input/search.ts +3 -3
  144. package/src/input/selection-modal-queue.ts +2 -0
  145. package/src/input/selection-modal.ts +28 -16
  146. package/src/input/session-picker-modal.ts +33 -8
  147. package/src/input/settings-modal-activation.ts +7 -2
  148. package/src/input/settings-modal-data.ts +11 -6
  149. package/src/input/settings-modal-types.ts +1 -1
  150. package/src/input/settings-modal.ts +20 -20
  151. package/src/input/submission-intent.ts +1 -1
  152. package/src/input/submission-router.ts +4 -9
  153. package/src/input/surface-modal-host.ts +110 -0
  154. package/src/input/theme-settings-actions.ts +82 -0
  155. package/src/input/usage-modal.ts +183 -0
  156. package/src/input/views.ts +97 -0
  157. package/src/main.ts +175 -165
  158. package/src/permissions/hunk-selection.ts +1 -1
  159. package/src/permissions/prompt-card.ts +511 -0
  160. package/src/permissions/prompt.ts +59 -622
  161. package/src/renderer/agents-modal.ts +310 -0
  162. package/src/renderer/autocomplete-overlay.ts +42 -220
  163. package/src/renderer/back-to-bottom.ts +80 -0
  164. package/src/renderer/block-actions-overlay.ts +30 -48
  165. package/src/renderer/bookmark-modal.ts +48 -79
  166. package/src/renderer/changes-modal.ts +325 -0
  167. package/src/renderer/code-block.ts +184 -106
  168. package/src/renderer/command-palette.ts +93 -0
  169. package/src/renderer/composer-fragments.ts +4 -4
  170. package/src/renderer/composer.ts +146 -0
  171. package/src/renderer/compositor.ts +26 -160
  172. package/src/renderer/config-modal.ts +185 -81
  173. package/src/renderer/confirm-dialog.ts +50 -0
  174. package/src/renderer/context-inspector.ts +116 -154
  175. package/src/renderer/context-status-hint.ts +4 -4
  176. package/src/renderer/conversation-layout.ts +20 -0
  177. package/src/renderer/conversation-overlays.ts +94 -84
  178. package/src/renderer/conversation-surface.ts +19 -38
  179. package/src/renderer/diff-tint.ts +98 -0
  180. package/src/renderer/file-picker-overlay.ts +63 -90
  181. package/src/renderer/footer-targets.ts +38 -0
  182. package/src/renderer/git-status.ts +3 -4
  183. package/src/renderer/header-line.ts +140 -117
  184. package/src/renderer/help-overlay.ts +133 -219
  185. package/src/renderer/history-search-overlay.ts +15 -12
  186. package/src/renderer/lane-graph/bead.ts +800 -0
  187. package/src/renderer/lane-graph/glyphs.ts +134 -0
  188. package/src/renderer/lane-graph/layout.ts +248 -0
  189. package/src/renderer/lane-graph/paint.ts +537 -0
  190. package/src/renderer/lane-graph/semantic-memo.ts +77 -0
  191. package/src/renderer/layout-engine.ts +2 -57
  192. package/src/renderer/layout.ts +7 -13
  193. package/src/renderer/markdown-inline.ts +2 -1
  194. package/src/renderer/markdown-table.ts +17 -12
  195. package/src/renderer/markdown.ts +92 -34
  196. package/src/renderer/masked-entry-modal.ts +78 -0
  197. package/src/renderer/mcp-workspace.ts +166 -203
  198. package/src/renderer/model-workspace.ts +381 -436
  199. package/src/renderer/notifications-modal.ts +116 -0
  200. package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
  201. package/src/renderer/process-view.ts +192 -0
  202. package/src/renderer/profile-picker-modal.ts +58 -110
  203. package/src/renderer/search-overlay.ts +6 -3
  204. package/src/renderer/selection-modal-overlay.ts +87 -327
  205. package/src/renderer/semantic-diff.ts +2 -2
  206. package/src/renderer/session-chips.ts +102 -0
  207. package/src/renderer/session-picker-modal.ts +177 -297
  208. package/src/renderer/settings-modal-connections.ts +14 -23
  209. package/src/renderer/settings-modal-context.ts +367 -0
  210. package/src/renderer/settings-modal-helpers.ts +6 -11
  211. package/src/renderer/settings-modal.ts +322 -726
  212. package/src/renderer/shell-surface.ts +277 -175
  213. package/src/renderer/shortcuts-overlay.ts +217 -0
  214. package/src/renderer/status-line.ts +475 -0
  215. package/src/renderer/surface-compose.ts +174 -0
  216. package/src/renderer/surface-kit-extra.ts +303 -0
  217. package/src/renderer/surface-kit-list.ts +366 -0
  218. package/src/renderer/surface-kit-parts.ts +360 -0
  219. package/src/renderer/surface-kit.ts +568 -0
  220. package/src/renderer/syntax-highlighter.ts +280 -197
  221. package/src/renderer/syntax-theme.ts +51 -0
  222. package/src/renderer/system-message.ts +0 -1
  223. package/src/renderer/term-caps.ts +42 -1
  224. package/src/renderer/terminal-bg-probe.ts +116 -19
  225. package/src/renderer/terminal-palette-probe.ts +322 -0
  226. package/src/renderer/terminal-palette-reader.ts +183 -0
  227. package/src/renderer/terminal-palette.ts +58 -0
  228. package/src/renderer/theme-mode-config.ts +16 -1
  229. package/src/renderer/theme.ts +234 -207
  230. package/src/renderer/thinking.ts +0 -1
  231. package/src/renderer/throbber.ts +188 -0
  232. package/src/renderer/toast-center.ts +119 -0
  233. package/src/renderer/ui-factory.ts +39 -602
  234. package/src/renderer/ui-primitives.ts +8 -25
  235. package/src/renderer/usage-modal.ts +353 -0
  236. package/src/renderer/user-message.ts +58 -0
  237. package/src/renderer/voice-capture-chip.ts +53 -0
  238. package/src/runtime/bootstrap-command-context.ts +0 -5
  239. package/src/runtime/bootstrap-command-parts.ts +38 -89
  240. package/src/runtime/bootstrap-core.ts +13 -15
  241. package/src/runtime/bootstrap-hook-bridge.ts +12 -21
  242. package/src/runtime/bootstrap-notifier-sync.ts +17 -1
  243. package/src/runtime/bootstrap-shell.ts +16 -50
  244. package/src/runtime/bootstrap.ts +14 -14
  245. package/src/runtime/client/fleet-union.ts +5 -5
  246. package/src/runtime/client/hosted-exit.ts +2 -2
  247. package/src/runtime/client/hosted-sessions.ts +1 -1
  248. package/src/runtime/client/spine-adoption.ts +1 -1
  249. package/src/runtime/context-accounting-source.ts +5 -4
  250. package/src/runtime/index.ts +1 -0
  251. package/src/runtime/notification-dispatch.ts +102 -29
  252. package/src/runtime/recovery-offer-wiring.ts +4 -1
  253. package/src/runtime/recovery-prompt.ts +31 -15
  254. package/src/runtime/runtime-services-types.ts +10 -6
  255. package/src/runtime/services.ts +17 -12
  256. package/src/runtime/session-continuity-hints.ts +2 -5
  257. package/src/runtime/session-inbound-inputs.ts +1 -1
  258. package/src/runtime/session-resume-liveness-confirm.ts +14 -2
  259. package/src/runtime/startup-typeahead-gate.ts +103 -0
  260. package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
  261. package/src/runtime/store/domains/index.ts +0 -8
  262. package/src/runtime/store/selectors/index.ts +0 -28
  263. package/src/runtime/store/state.ts +0 -8
  264. package/src/runtime/ui-services.ts +4 -6
  265. package/src/runtime/usage-tracker.ts +348 -0
  266. package/src/runtime/wrfc-persistence.ts +4 -4
  267. package/src/shell/blocking-input.ts +92 -2
  268. package/src/shell/recovery-input-helpers.ts +1 -1
  269. package/src/shell/session-views.ts +539 -0
  270. package/src/shell/status-report.ts +86 -0
  271. package/src/shell/transcript-scroll.ts +68 -0
  272. package/src/shell/ui-openers.ts +84 -172
  273. package/src/shell/view-openers.ts +205 -0
  274. package/src/tools/wrfc-agent-guard.ts +22 -2
  275. package/src/utils/format-duration.ts +4 -8
  276. package/src/utils/splash-lines.ts +9 -1
  277. package/src/utils/terminal-width.ts +56 -0
  278. package/src/verification/live-verifier.ts +0 -10
  279. package/src/version.ts +1 -1
  280. package/src/{panels → views}/agent-inspector-shared.ts +2 -2
  281. package/src/{panels → views}/builtin-modals.ts +22 -21
  282. package/src/views/builtin-views.ts +135 -0
  283. package/src/{panels → views}/confirm-state.ts +9 -9
  284. package/src/{panels → views}/diff-review-model.ts +10 -3
  285. package/src/{panels → views}/eval-registry.ts +1 -1
  286. package/src/{panels → views}/fleet-acts.ts +49 -85
  287. package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
  288. package/src/{panels → views}/fleet-gateway.ts +5 -5
  289. package/src/{panels → views}/fleet-observed-render.ts +16 -16
  290. package/src/{panels → views}/fleet-read-model.ts +9 -9
  291. package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
  292. package/src/{panels → views}/fleet-spawn.ts +33 -49
  293. package/src/{panels → views}/fleet-steer.ts +12 -12
  294. package/src/{panels → views}/fleet-stop.ts +4 -4
  295. package/src/{panels → views}/fleet-transcript.ts +26 -27
  296. package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
  297. package/src/{panels → views}/hosted-session-feed.ts +6 -6
  298. package/src/views/modal-surface-registry.ts +41 -0
  299. package/src/{panels → views}/modals/devices-modal.ts +2 -2
  300. package/src/{panels → views}/modals/hooks-modal.ts +13 -13
  301. package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
  302. package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
  303. package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
  304. package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
  305. package/src/{panels → views}/modals/memory-modal.ts +8 -8
  306. package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
  307. package/src/{panels → views}/modals/pairing-modal.ts +9 -9
  308. package/src/{panels → views}/modals/planning-modal.ts +15 -20
  309. package/src/{panels → views}/modals/plugins-modal.ts +6 -6
  310. package/src/{panels → views}/modals/policy-modal.ts +13 -5
  311. package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
  312. package/src/{panels → views}/modals/remote-modal.ts +3 -3
  313. package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
  314. package/src/{panels → views}/modals/security-modal.ts +22 -16
  315. package/src/{panels → views}/modals/services-modal.ts +3 -3
  316. package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
  317. package/src/{panels → views}/modals/skills-modal.ts +9 -9
  318. package/src/{panels → views}/modals/subscription-modal.ts +3 -3
  319. package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
  320. package/src/views/notifications-feed.ts +314 -0
  321. package/src/{panels → views}/polish-core.ts +38 -36
  322. package/src/{panels → views}/polish-tables.ts +35 -26
  323. package/src/{panels → views}/polish.ts +101 -95
  324. package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
  325. package/src/{panels → views}/provider-health-routes.ts +14 -14
  326. package/src/{panels → views}/search-focus.ts +8 -8
  327. package/src/{panels → views}/session-maintenance.ts +11 -11
  328. package/src/views/skills-discovery.ts +159 -0
  329. package/src/views/view-deps.ts +80 -0
  330. package/src/views/view-panel-adapter.ts +53 -0
  331. package/src/{panels → views}/workstream-graph-render.ts +1 -1
  332. package/src/core/conversation-fold.ts +0 -101
  333. package/src/core/conversation-turn-structure.ts +0 -464
  334. package/src/core/conversation-user-receipts.ts +0 -42
  335. package/src/input/commands/operator-panel-runtime.ts +0 -186
  336. package/src/input/panel-integration-actions.ts +0 -33
  337. package/src/input/panel-mouse-geometry.ts +0 -97
  338. package/src/input/panel-paste-flood-guard.ts +0 -86
  339. package/src/panels/base-panel.ts +0 -301
  340. package/src/panels/builtin/agent.ts +0 -27
  341. package/src/panels/builtin/development.ts +0 -92
  342. package/src/panels/builtin/knowledge.ts +0 -12
  343. package/src/panels/builtin/operations.ts +0 -177
  344. package/src/panels/builtin/session.ts +0 -81
  345. package/src/panels/builtin/shared.ts +0 -357
  346. package/src/panels/builtin-panels.ts +0 -28
  347. package/src/panels/cost-tracker-panel.ts +0 -746
  348. package/src/panels/diff-panel.ts +0 -797
  349. package/src/panels/diff-review-panel.ts +0 -395
  350. package/src/panels/expandable-list-panel.ts +0 -189
  351. package/src/panels/fleet-deep-link.ts +0 -31
  352. package/src/panels/fleet-panel.ts +0 -799
  353. package/src/panels/git-panel.ts +0 -800
  354. package/src/panels/hosted-session-panel.ts +0 -221
  355. package/src/panels/index.ts +0 -21
  356. package/src/panels/local-auth-panel.ts +0 -478
  357. package/src/panels/notifications-feed.ts +0 -127
  358. package/src/panels/notifications-panel.ts +0 -160
  359. package/src/panels/panel-confirm-overlay.ts +0 -72
  360. package/src/panels/panel-manager.ts +0 -791
  361. package/src/panels/plugins-panel.ts +0 -371
  362. package/src/panels/scrollable-list-panel.ts +0 -498
  363. package/src/panels/skills-panel.ts +0 -454
  364. package/src/panels/token-budget-panel.ts +0 -688
  365. package/src/panels/types.ts +0 -191
  366. package/src/panels/wrfc-panel-format.ts +0 -133
  367. package/src/renderer/diff-view.ts +0 -134
  368. package/src/renderer/fleet-tab-strip.ts +0 -56
  369. package/src/renderer/footer-tips.ts +0 -53
  370. package/src/renderer/fullscreen-primitives.ts +0 -146
  371. package/src/renderer/fullscreen-workspace.ts +0 -194
  372. package/src/renderer/hint-grammar.ts +0 -52
  373. package/src/renderer/modal-factory.ts +0 -480
  374. package/src/renderer/overlay-box.ts +0 -154
  375. package/src/renderer/overlay-viewport.ts +0 -104
  376. package/src/renderer/panel-composite.ts +0 -145
  377. package/src/renderer/panel-workspace-bar.ts +0 -54
  378. package/src/renderer/process-indicator.ts +0 -163
  379. package/src/renderer/tab-strip.ts +0 -222
  380. package/src/renderer/text-layout.ts +0 -31
  381. package/src/renderer/tool-call.ts +0 -276
  382. package/src/renderer/tool-result-expanded-lines.ts +0 -48
  383. package/src/renderer/tool-result-summary.ts +0 -219
  384. package/src/runtime/diagnostics/panels/index.ts +0 -21
  385. package/src/runtime/diagnostics/panels/ops.ts +0 -156
  386. package/src/runtime/perf/panel-contracts.ts +0 -32
  387. package/src/runtime/store/domains/panels.ts +0 -117
  388. package/src/verification/verification-ledger.ts +0 -707
  389. /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
  390. /package/src/{panels → views}/modals/modal-theme.ts +0 -0
  391. /package/src/{panels → views}/provider-health-domains.ts +0 -0
@@ -1,10 +1,10 @@
1
+ import { activeTokens } from '../renderer/theme.ts';
1
2
  import { loadSkillByTrigger } from '@pellux/goodvibes-sdk/platform/tools';
2
3
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
3
4
  import type { CommandContext, CommandRegistry } from './command-registry.ts';
4
5
  import type { AutocompleteEngine } from './autocomplete.ts';
5
6
  import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
6
7
  import type { ConversationManager } from '../core/conversation';
7
- import type { PanelManager } from '../panels/panel-manager.ts';
8
8
  import { handleClipboardPaste, type ClipboardPasteSource } from './handler-content-actions.ts';
9
9
 
10
10
  export type CommandModeRouteState = {
@@ -15,8 +15,6 @@ export type CommandModeRouteState = {
15
15
  modalStack: string[];
16
16
  commandRegistry: CommandRegistry | null;
17
17
  commandContext?: CommandContext;
18
- panelFocused: boolean;
19
- panelManager: PanelManager;
20
18
  conversationManager: ConversationManager | null;
21
19
  requestRender: () => void;
22
20
  handleEscape: () => void;
@@ -93,7 +91,7 @@ export function handleCommandModeToken(state: CommandModeRouteState, token: Inpu
93
91
  const parts = raw.slice(1).trim().split(/\s+/);
94
92
  const name = parts[0];
95
93
  const args = parts.slice(1);
96
- const ctx = withPanelFocusSync(state.commandContext, state);
94
+ const ctx = withComposerBindings(state.commandContext, state);
97
95
  const commandPromise = state.commandRegistry.get(name)
98
96
  ? state.commandRegistry.execute(name, args, ctx)
99
97
  : (ctx.executeCommand?.(name, args) ?? Promise.resolve(false));
@@ -112,7 +110,7 @@ export function handleCommandModeToken(state: CommandModeRouteState, token: Inpu
112
110
  if (skillContent) {
113
111
  state.commandContext?.submitInput?.(skillContent);
114
112
  } else {
115
- state.conversationManager?.log(`Unknown command: /${name}. Type /help for available commands.`, { fg: '#ef4444' });
113
+ state.conversationManager?.log(`Unknown command: /${name}. Type /help for available commands.`, { fg: activeTokens().error });
116
114
  state.requestRender();
117
115
  }
118
116
  }
@@ -122,7 +120,7 @@ export function handleCommandModeToken(state: CommandModeRouteState, token: Inpu
122
120
  // already guard their own internals: a handler that throws or
123
121
  // awaits a rejected promise must never become a silent unhandled
124
122
  // rejection, it renders the same way an unknown command does.
125
- state.conversationManager?.log(`Command /${name} failed: ${summarizeError(err)}`, { fg: '#ef4444' });
123
+ state.conversationManager?.log(`Command /${name} failed: ${summarizeError(err)}`, { fg: activeTokens().error });
126
124
  state.requestRender();
127
125
  });
128
126
  } else {
@@ -134,43 +132,10 @@ export function handleCommandModeToken(state: CommandModeRouteState, token: Inpu
134
132
  return token.logicalName !== 'left' && token.logicalName !== 'right' && token.logicalName !== 'home' && token.logicalName !== 'end';
135
133
  }
136
134
 
137
- function withPanelFocusSync(context: CommandContext, state: CommandModeRouteState): CommandContext {
138
- const panelIsFocusable = (): boolean =>
139
- state.panelManager.isVisible() && state.panelManager.getAllOpen().length > 0;
140
-
135
+ /** The command context with the composer-bound actions (clipboard paste, nested commands) bound to this route's state. */
136
+ function withComposerBindings(context: CommandContext, state: CommandModeRouteState): CommandContext {
141
137
  return {
142
138
  ...context,
143
- openPanelPicker: context.openPanelPicker
144
- ? () => {
145
- context.openPanelPicker?.();
146
- state.panelFocused = panelIsFocusable();
147
- }
148
- : undefined,
149
- showPanel: context.showPanel
150
- ? (panelId, pane, target, opts) => {
151
- context.showPanel?.(panelId, pane, target, opts);
152
- // showPanel no longer transfers focus by default, the command
153
- // path leaves the composer focused (ui-openers.ts's showPanel only
154
- // calls focusPanels() when opts.focus is set). Only flip the local
155
- // panelFocused mirror when the caller explicitly asked for focus;
156
- // otherwise leave it exactly as it was (the composer, since you can
157
- // only type a command from there in the first place). `target` is the
158
- // deep-link jump target, forwarded verbatim.
159
- if (opts?.focus) state.panelFocused = true;
160
- }
161
- : undefined,
162
- focusPanels: context.focusPanels
163
- ? () => {
164
- context.focusPanels?.();
165
- state.panelFocused = panelIsFocusable();
166
- }
167
- : undefined,
168
- focusPrompt: context.focusPrompt
169
- ? () => {
170
- context.focusPrompt?.();
171
- state.panelFocused = false;
172
- }
173
- : undefined,
174
139
  pasteFromClipboard: () => {
175
140
  const result = handleClipboardPaste({
176
141
  prompt: state.prompt,
@@ -190,7 +155,7 @@ function withPanelFocusSync(context: CommandContext, state: CommandModeRouteStat
190
155
  return result;
191
156
  },
192
157
  executeCommand: async (name, args) => {
193
- const wrapped = withPanelFocusSync(context, state);
158
+ const wrapped = withComposerBindings(context, state);
194
159
  const handled = state.commandRegistry?.get(name)
195
160
  ? await state.commandRegistry.execute(name, args, wrapped)
196
161
  : false;
@@ -1,3 +1,4 @@
1
+ import { activeTokens } from '../renderer/theme.ts';
1
2
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
2
3
  import { copyToClipboard, pasteFromClipboard, pasteImageFromClipboard } from '../utils/clipboard.ts';
3
4
  import type { InfiniteBuffer } from '@pellux/goodvibes-terminal-shell';
@@ -299,7 +300,7 @@ export function handleBlockCopy(
299
300
  const lineIndex = getAnchorLine();
300
301
  const nearest = conversationManager.findNearestBlock(lineIndex);
301
302
  if (!nearest) {
302
- conversationManager.log('[Ctrl+Y: No block found nearby]', { fg: '240' });
303
+ conversationManager.log('[Ctrl+Y: No block found nearby]', { fg: activeTokens().textFaint });
303
304
  requestRender();
304
305
  return;
305
306
  }
@@ -310,7 +311,7 @@ export function handleBlockCopy(
310
311
  // copying content the user just had search reveal is a deliberate choice
311
312
  // to keep it visible, not an accident of typing a query.
312
313
  conversationManager.searchExpansion.noteUserTouch(nearest.collapseKey);
313
- conversationManager.log(`[Copied ${describeBlockForReceipt(nearest)}]`, { fg: '#22c55e' });
314
+ conversationManager.log(`[Copied ${describeBlockForReceipt(nearest)}]`, { fg: activeTokens().success });
314
315
  requestRender();
315
316
  setTimeout(() => requestRender(), 2005);
316
317
  }
@@ -325,7 +326,7 @@ export function handleBookmark(
325
326
  const lineIndex = getAnchorLine();
326
327
  const nearest = conversationManager.findNearestBlock(lineIndex);
327
328
  if (!nearest) {
328
- conversationManager.log('[Ctrl+B: No block found nearby]', { fg: '240' });
329
+ conversationManager.log('[Ctrl+B: No block found nearby]', { fg: activeTokens().textFaint });
329
330
  requestRender();
330
331
  return;
331
332
  }
@@ -337,7 +338,7 @@ export function handleBookmark(
337
338
  const msg = added
338
339
  ? `[Bookmarked: ${target}]`
339
340
  : `[Bookmark removed: ${target}]`;
340
- conversationManager.log(msg, { fg: added ? '#22c55e' : '244' });
341
+ conversationManager.log(msg, { fg: added ? activeTokens().success : activeTokens().textMuted });
341
342
  requestRender();
342
343
  }
343
344
 
@@ -351,7 +352,7 @@ export function handleBlockSave(
351
352
  const lineIndex = getAnchorLine();
352
353
  const nearest = conversationManager.findNearestBlock(lineIndex);
353
354
  if (!nearest) {
354
- conversationManager.log('[Ctrl+S: No block found nearby]', { fg: '240' });
355
+ conversationManager.log('[Ctrl+S: No block found nearby]', { fg: activeTokens().textFaint });
355
356
  requestRender();
356
357
  return;
357
358
  }
@@ -362,10 +363,10 @@ export function handleBlockSave(
362
363
  const filePath = bookmarkManager.saveToFile(nearest.rawContent, nearest.type);
363
364
  const homePath = process.env.HOME || process.env.USERPROFILE || '';
364
365
  const displayPath = homePath ? filePath.replace(homePath, '~') : filePath;
365
- conversationManager.log(`[Saved ${target} to: ${displayPath}]`, { fg: '#22c55e' });
366
+ conversationManager.log(`[Saved ${target} to: ${displayPath}]`, { fg: activeTokens().success });
366
367
  } catch (err) {
367
368
  const msg = summarizeError(err);
368
- conversationManager.log(`[Save failed (${target}): ${msg}]`, { fg: '#ef4444' });
369
+ conversationManager.log(`[Save failed (${target}): ${msg}]`, { fg: activeTokens().error });
369
370
  }
370
371
  requestRender();
371
372
  }
@@ -382,7 +383,7 @@ export function handleBlockToggle(
382
383
  const wasCollapsed = conversationManager.isCollapsed(nearest.blockIndex);
383
384
  const blockIdx = conversationManager.toggleCollapseAtLine(lineIndex);
384
385
  if (blockIdx >= 0) {
385
- conversationManager.log(`[${wasCollapsed ? 'Expanded' : 'Collapsed'} ${describeBlockForReceipt(nearest)}]`, { fg: '244' });
386
+ conversationManager.log(`[${wasCollapsed ? 'Expanded' : 'Collapsed'} ${describeBlockForReceipt(nearest)}]`, { fg: activeTokens().textMuted });
386
387
  requestRender();
387
388
  }
388
389
  }
@@ -415,14 +416,14 @@ export function handleDiffApply(
415
416
  }).then(({ approved }) => {
416
417
  if (!approved) return;
417
418
  if (!projectRoot) {
418
- conversationManager.log('[Diff apply failed: missing working directory]', { fg: '#ef4444' });
419
+ conversationManager.log('[Diff apply failed: missing working directory]', { fg: activeTokens().error });
419
420
  return;
420
421
  }
421
422
  let resolvedPath: string;
422
423
  try {
423
424
  resolvedPath = resolveAndValidatePath(diff.filePath!, projectRoot);
424
425
  } catch (err) {
425
- conversationManager.log(`[Diff apply failed: ${err instanceof Error ? err.message : err}]`, { fg: '#ef4444' });
426
+ conversationManager.log(`[Diff apply failed: ${err instanceof Error ? err.message : err}]`, { fg: activeTokens().error });
426
427
  return;
427
428
  }
428
429
  try {
@@ -430,22 +431,22 @@ export function handleDiffApply(
430
431
  if (diff.original && content.includes(diff.original)) {
431
432
  const occurrenceCount = content.split(diff.original).length - 1;
432
433
  if (occurrenceCount > 1) {
433
- conversationManager.log(`[Diff apply failed: pattern found ${occurrenceCount} times in ${diff.filePath} - ambiguous]`, { fg: '#ef4444' });
434
+ conversationManager.log(`[Diff apply failed: pattern found ${occurrenceCount} times in ${diff.filePath} - ambiguous]`, { fg: activeTokens().error });
434
435
  } else {
435
436
  const newContent = content.replace(diff.original, diff.updated);
436
437
  writeFileSync(resolvedPath, newContent, 'utf-8');
437
- conversationManager.log(`[Applied diff to ${diff.filePath}]`, { fg: '#22c55e' });
438
+ conversationManager.log(`[Applied diff to ${diff.filePath}]`, { fg: activeTokens().success });
438
439
  }
439
440
  } else {
440
- conversationManager.log(`[Diff apply failed: original text not found in ${diff.filePath}]`, { fg: '#ef4444' });
441
+ conversationManager.log(`[Diff apply failed: original text not found in ${diff.filePath}]`, { fg: activeTokens().error });
441
442
  }
442
443
  } catch (err) {
443
444
  const msg = summarizeError(err);
444
- conversationManager.log(`[Diff apply error: ${msg}]`, { fg: '#ef4444' });
445
+ conversationManager.log(`[Diff apply error: ${msg}]`, { fg: activeTokens().error });
445
446
  }
446
447
  requestRender();
447
448
  }).catch((err) => {
448
- conversationManager.log(`[Diff apply error: ${summarizeError(err)}]`, { fg: '#ef4444' });
449
+ conversationManager.log(`[Diff apply error: ${summarizeError(err)}]`, { fg: activeTokens().error });
449
450
  requestRender();
450
451
  });
451
452
  return true;
@@ -1,4 +1,5 @@
1
1
  import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
2
+ import type { FooterTarget } from '../renderer/footer-targets.ts';
2
3
  import type { BlockMeta, ConversationManager } from '../core/conversation';
3
4
  import type { InputHistory } from './input-history.ts';
4
5
  import type { ContentPart } from '@pellux/goodvibes-sdk/platform/providers';
@@ -12,185 +13,14 @@ import {
12
13
  moveCursorVertical as computeCursorVerticalMove,
13
14
  } from './handler-prompt-buffer.ts';
14
15
  import { cleanupMarkerRegistry, expandPrompt, findMarkerAtPos, registerPaste } from './handler-content-actions.ts';
15
- import type { PanelManager } from '../panels/panel-manager.ts';
16
- import { getPanelUnderMouse, workspaceTabAtMouse } from './panel-mouse-geometry.ts';
17
- import type { PanelMouseLayout } from './panel-mouse-geometry.ts';
18
- export type { PanelMouseLayout };
19
- import type { KeybindingsManager } from './keybindings.ts';
20
16
  import type { KillRing } from './kill-ring.ts';
21
17
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
22
- import { trackPanelPasteFloodGuard, type PanelBurstGuardState } from './panel-paste-flood-guard.ts';
23
-
24
- export type PanelFocusRouteState = {
25
- panelManager: PanelManager;
26
- keybindingsManager: KeybindingsManager;
27
- panelFocused: boolean;
28
- commandMode: boolean;
29
- searchActive: boolean;
30
- autocompleteActive: boolean;
31
- requestRender: () => void;
32
- handlePathCompletion: () => boolean;
33
- cyclePanelTab: (direction: 'next' | 'prev') => void;
34
- onPanelInputConsumed?: (activePanel: import('../panels/types.ts').Panel | null, key: string) => void;
35
- /**
36
- * True when THIS token is a paste: a single 'text' token whose value holds
37
- * more than one character. The SDK tokenizer emits a bracketed paste
38
- * (\x1b[?2004h, enabled in main.ts init) as exactly one such token; discrete
39
- * keystrokes, even several batched into one feed() by render-tick latency,
40
- * arrive as separate 1-char tokens. See the text-token branch below.
41
- */
42
- isPasteToken: boolean;
43
- /**
44
- * One-shot hint emitted when a paste is dropped because the focused panel has
45
- * no text capture (Invariant A: focus must not silently flip to the composer).
46
- */
47
- onPasteDropped?: (panelName: string) => void;
48
- /** Wall-clock time (ms) for this token; `burstGuard` is item 5's flood-guard state, mutated in place across tokens (see panel-paste-flood-guard.ts). */
49
- now: number;
50
- burstGuard: PanelBurstGuardState;
51
- /**
52
- * True while a turn is actively streaming; gates Escape's cancel-first
53
- * precedence (I6.1) ahead of the panel's own two-stage escape contract
54
- * below.
55
- */
56
- isTurnActive: () => boolean;
57
- /** Cancel the active turn. Wired to commandContext.cancelGeneration. */
58
- cancelGeneration: () => void;
59
- };
60
-
61
- export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputToken): {
62
- handled: boolean;
63
- panelFocused: boolean;
64
- } {
65
- let panelFocused = state.panelFocused;
66
-
67
- if (
68
- token.type === 'key' &&
69
- token.logicalName === 'tab' &&
70
- !state.commandMode &&
71
- !state.searchActive &&
72
- !state.autocompleteActive
73
- ) {
74
- const pm = state.panelManager;
75
- if (pm.isVisible() && pm.getAllOpen().length > 0) {
76
- if (panelFocused) {
77
- panelFocused = false;
78
- } else if (!state.handlePathCompletion()) {
79
- panelFocused = true;
80
- }
81
- state.requestRender();
82
- return { handled: true, panelFocused };
83
- }
84
- }
85
-
86
- if (!panelFocused) {
87
- return { handled: false, panelFocused };
88
- }
89
-
90
- if (token.type === 'key') {
91
- // I6: two-stage Escape, give the panel a chance to consume escape first
92
- // (e.g. dismiss a confirm dialog or clear search). Only unfocus if the
93
- // panel returns false (unconsumed) or there is no active panel.
94
- if (token.logicalName === 'escape') {
95
- // While streaming, a focused panel must not swallow the
96
- // only way to cancel, Escape's first job is always cancel-turn; the
97
- // panel's own two-stage consume-or-unfocus contract only runs once no
98
- // turn is active, so a *second* Escape falls through to it normally
99
- // (panelFocused stays unchanged here). Calls cancelGeneration()
100
- // directly rather than the shared handleEscape() (handler-modal-stack.ts),
101
- // whose fallthrough order doesn't guarantee reaching it first.
102
- if (state.isTurnActive()) {
103
- state.cancelGeneration();
104
- state.requestRender();
105
- return { handled: true, panelFocused };
106
- }
107
- const activePanel = state.panelManager.getActive();
108
- const panelConsumedEscape = activePanel?.handleInput?.('escape') ?? false;
109
- if (panelConsumedEscape) {
110
- state.onPanelInputConsumed?.(activePanel!, 'escape');
111
- state.requestRender();
112
- return { handled: true, panelFocused };
113
- }
114
- panelFocused = false;
115
- state.requestRender();
116
- return { handled: true, panelFocused };
117
- }
118
- const kb = state.keybindingsManager;
119
- // NOTE: panel-tab-next/prev, panel-close, and panel-close-all are handled
120
- // globally in handleGlobalShortcutToken (runs earlier in the feed loop),
121
- // their old, drifted copies here were unreachable and are gone. Only
122
- // panel-focus-toggle stays, since it's meaningful only once the panel
123
- // workspace already owns focus (it swaps between the two panes).
124
- if (kb.matches('panel-focus-toggle', token)) {
125
- // Switch keyboard focus between the top and bottom panes (no-op when
126
- // there is no visible, non-empty bottom pane).
127
- state.panelManager.togglePaneFocus();
128
- state.requestRender();
129
- return { handled: true, panelFocused };
130
- }
131
- // Alt+1..9 (panel-tab-1..9) is a global KeyAction consumed earlier by
132
- // handleGlobalShortcutToken, so those meta+digit tokens never reach here.
133
- // Any other ctrl/meta combo this route does not own falls through so the
134
- // global handler (which ran first) or the prompt routes can act on it.
135
- if (token.ctrl || token.meta) {
136
- return { handled: false, panelFocused };
137
- }
138
- const activePanel = state.panelManager.getActive();
139
- if (activePanel?.handleInput) {
140
- const consumed = activePanel.handleInput(token.logicalName);
141
- if (consumed) {
142
- state.onPanelInputConsumed?.(activePanel, token.logicalName);
143
- state.requestRender();
144
- return { handled: true, panelFocused };
145
- }
146
- }
147
- }
148
-
149
- if (token.type === 'text' && token.value) {
150
- const activePanel = state.panelManager.getActive();
151
- // '/' is an explicit transfer verb back to the composer (a capturing panel, isCapturingTextBurst, keeps '/' for itself).
152
- if (token.value === '/' && !activePanel?.isCapturingTextBurst?.()) {
153
- panelFocused = false;
154
- return { handled: false, panelFocused };
155
- }
156
- // Invariant A/B: a paste (isPasteToken, one multi-char token) into
157
- // a capturing panel forwards verbatim below; elsewhere it is DROPPED with
158
- // a one-shot hint (never exploded into hotkeys, never a silent focus
159
- // flip, focus moves only on an explicit verb). Discrete keystrokes fall
160
- // through to the per-char dispatch below.
161
- if (state.isPasteToken && !activePanel?.isCapturingTextBurst?.()) {
162
- state.onPasteDropped?.(activePanel?.name ?? 'the panel');
163
- state.requestRender();
164
- return { handled: true, panelFocused };
165
- }
166
-
167
- // item 5, paste flood guard (rate-based; see panel-paste-flood-guard.ts).
168
- if (!state.isPasteToken && !activePanel?.isCapturingTextBurst?.()) {
169
- const guard = trackPanelPasteFloodGuard(state.burstGuard, state.now);
170
- if (!guard.dispatch) {
171
- if (guard.showHintNow) state.onPasteDropped?.(activePanel?.name ?? 'the panel');
172
- state.requestRender();
173
- return { handled: true, panelFocused };
174
- }
175
- }
176
-
177
- if (activePanel?.handleInput) {
178
- for (const ch of token.value) {
179
- activePanel.handleInput(ch);
180
- }
181
- state.requestRender();
182
- }
183
- return { handled: true, panelFocused };
184
- }
185
-
186
- return { handled: true, panelFocused };
187
- }
188
18
 
189
19
  export type IndicatorFocusRouteState = {
190
20
  indicatorFocused: boolean;
191
21
  modalOpened: (name: string) => void;
192
- /** The footer process indicator's [Enter] opens the Fleet panel (F2 also opens it; the process modal was retired). */
193
- openFleetPanel: () => void;
22
+ /** The footer process indicator's [Enter] opens the Agents modal (F2 also opens it). */
23
+ openAgentsView: () => void;
194
24
  requestRender: () => void;
195
25
  };
196
26
 
@@ -211,8 +41,7 @@ export function handleIndicatorFocusToken(state: IndicatorFocusRouteState, token
211
41
  }
212
42
  if (token.logicalName === 'enter') {
213
43
  indicatorFocused = false;
214
- // opens the Fleet panel (see openFleetPanel's own doc, line ~352).
215
- state.openFleetPanel();
44
+ state.openAgentsView();
216
45
  state.requestRender();
217
46
  return { handled: true, indicatorFocused };
218
47
  }
@@ -349,8 +178,8 @@ export type KeyRouteState = {
349
178
  * Anchors Enter-on-empty-composer's block-actions menu to that block
350
179
  * instead of an arbitrary fixed line. */
351
180
  getBlockAnchorLine: () => number;
352
- /** F2 opens+focuses the Fleet panel (which subsumes the retired process modal). */
353
- openFleetPanel: () => void;
181
+ /** F2 opens the Agents modal. */
182
+ openAgentsView: () => void;
354
183
  modalOpened: (name: string) => void;
355
184
  saveUndoState: () => void;
356
185
  /** Break the undo coalescing group (call on cursor moves). */
@@ -641,12 +470,11 @@ export function handlePromptKeyToken(state: KeyRouteState, token: InputToken): {
641
470
  return { handled: true, prompt, cursorPos, inputScrollTop, commandMode, indicatorFocused };
642
471
  }
643
472
 
644
- // F2 is now handled globally with toggle semantics (handler-shortcuts.ts)
645
- // BEFORE it ever reaches this route; this branch is dead in the real
646
- // pipeline but stays for panel-entry-points-reachable.test.ts's direct test.
473
+ // F2 is handled globally (handler-shortcuts.ts) before it reaches this
474
+ // route; this branch keeps the route correct when it is driven directly.
647
475
  if (token.logicalName === 'f2') {
648
476
  indicatorFocused = false;
649
- state.openFleetPanel();
477
+ state.openAgentsView();
650
478
  return { handled: true, prompt, cursorPos, inputScrollTop, commandMode, indicatorFocused };
651
479
  }
652
480
 
@@ -656,8 +484,6 @@ export function handlePromptKeyToken(state: KeyRouteState, token: InputToken): {
656
484
  export type MouseRouteState = {
657
485
  conversationManager: ConversationManager | null;
658
486
  selection: SelectionManager;
659
- panelManager: PanelManager;
660
- panelMouseLayout: PanelMouseLayout | null;
661
487
  mouseDownRow: number;
662
488
  mouseDownCol: number;
663
489
  scrollTop: number;
@@ -667,20 +493,13 @@ export type MouseRouteState = {
667
493
  requestRender: () => void;
668
494
  handlePaste: () => void;
669
495
  handleCopy: () => void;
496
+ /** The view a footer screen row opens on a click (the usage rows open Usage). */
497
+ footerTargetAt?: (row: number) => FooterTarget | undefined;
498
+ openFooterTarget?: (target: FooterTarget) => void;
499
+ /** Screen rows above the transcript (the header, the chips, the gap under them); 2 when not given. */
500
+ bodyTopRow?: () => number;
670
501
  };
671
502
 
672
- function scrollPanelUnderMouse(
673
- state: MouseRouteState,
674
- token: Extract<InputToken, { type: 'mouse' }>,
675
- deltaRows: number,
676
- ): boolean {
677
- const panel = getPanelUnderMouse(state.panelManager, state.panelMouseLayout, token.row, token.col);
678
- if (!panel?.handleScroll) return false;
679
- const consumed = panel.handleScroll(deltaRows);
680
- if (consumed) state.requestRender();
681
- return true;
682
- }
683
-
684
503
  export function handleMouseToken(state: MouseRouteState, token: InputToken): {
685
504
  handled: boolean;
686
505
  mouseDownRow: number;
@@ -692,20 +511,14 @@ export function handleMouseToken(state: MouseRouteState, token: InputToken): {
692
511
  return { handled: false, mouseDownRow, mouseDownCol };
693
512
  }
694
513
 
695
- const headerH = 2;
696
- const viewportRow = token.row - headerH;
514
+ // Mouse rows are 1-based; the transcript's first row is the one after the header block.
515
+ const viewportRow = token.row - 1 - (state.bodyTopRow?.() ?? 2);
697
516
 
698
517
  if (token.button === 64) {
699
- if (scrollPanelUnderMouse(state, token, -3)) {
700
- return { handled: true, mouseDownRow, mouseDownCol };
701
- }
702
518
  state.scroll(-3);
703
519
  return { handled: true, mouseDownRow, mouseDownCol };
704
520
  }
705
521
  if (token.button === 65) {
706
- if (scrollPanelUnderMouse(state, token, 3)) {
707
- return { handled: true, mouseDownRow, mouseDownCol };
708
- }
709
522
  state.scroll(3);
710
523
  return { handled: true, mouseDownRow, mouseDownCol };
711
524
  }
@@ -713,15 +526,17 @@ export function handleMouseToken(state: MouseRouteState, token: InputToken): {
713
526
  state.handlePaste();
714
527
  return { handled: true, mouseDownRow, mouseDownCol };
715
528
  }
529
+ // A click on a clickable footer row (press and release on the same row)
530
+ // opens its view; it never starts a text selection.
531
+ const footerTarget = state.footerTargetAt?.(token.row);
532
+ if (footerTarget && token.button === 0 && token.action === 'press') {
533
+ return { handled: true, mouseDownRow: token.row, mouseDownCol: token.col };
534
+ }
535
+ if (footerTarget && token.action === 'release' && mouseDownRow === token.row) {
536
+ state.openFooterTarget?.(footerTarget);
537
+ return { handled: true, mouseDownRow: -1, mouseDownCol: -1 };
538
+ }
716
539
  if (token.button === 0 && token.action === 'press') {
717
- // Click on a workspace tab activates it (checked before starting a text
718
- // selection so a tab click doesn't begin a drag-select).
719
- const tabIndex = workspaceTabAtMouse(state.panelManager, state.panelMouseLayout, token.row, token.col);
720
- if (tabIndex !== null) {
721
- state.panelManager.activateWorkspaceIndex(tabIndex);
722
- state.requestRender();
723
- return { handled: true, mouseDownRow, mouseDownCol };
724
- }
725
540
  mouseDownRow = token.row;
726
541
  mouseDownCol = token.col;
727
542
  state.selection.startSelection(token.col, viewportRow, state.scrollTop, state.viewportHeight, state.lineCount);