@pellux/goodvibes-tui 2.0.21 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +16 -16
  3. package/docs/README.md +4 -4
  4. package/docs/foundation-artifacts/operator-contract.json +1 -1
  5. package/package.json +16 -6
  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-registry.ts +77 -35
  52. package/src/input/commands/acp-runtime.ts +19 -9
  53. package/src/input/commands/channel-runtime.ts +2 -2
  54. package/src/input/commands/ci-runtime.ts +1 -1
  55. package/src/input/commands/codebase-runtime.ts +1 -1
  56. package/src/input/commands/config.ts +7 -1
  57. package/src/input/commands/context-window.ts +25 -19
  58. package/src/input/commands/control-room-runtime.ts +18 -18
  59. package/src/input/commands/conversation-runtime.ts +0 -1
  60. package/src/input/commands/cost-runtime.ts +29 -20
  61. package/src/input/commands/diff-runtime.ts +35 -181
  62. package/src/input/commands/eval.ts +1 -1
  63. package/src/input/commands/experience-runtime.ts +3 -7
  64. package/src/input/commands/fresh-conversation.ts +86 -0
  65. package/src/input/commands/git-runtime.ts +5 -22
  66. package/src/input/commands/graph-runtime.ts +1 -1
  67. package/src/input/commands/guidance-runtime.ts +1 -1
  68. package/src/input/commands/health-runtime.ts +8 -9
  69. package/src/input/commands/hooks-runtime.ts +2 -2
  70. package/src/input/commands/hosted-runtime.ts +3 -3
  71. package/src/input/commands/incident-runtime.ts +3 -3
  72. package/src/input/commands/intelligence-runtime.ts +6 -9
  73. package/src/input/commands/knowledge.ts +2 -2
  74. package/src/input/commands/legacy-panel-command.ts +57 -0
  75. package/src/input/commands/local-auth-runtime.ts +8 -9
  76. package/src/input/commands/local-provider-runtime.ts +7 -4
  77. package/src/input/commands/local-runtime.ts +12 -15
  78. package/src/input/commands/local-setup-review.ts +1 -1
  79. package/src/input/commands/local-setup.ts +13 -19
  80. package/src/input/commands/memory.ts +1 -1
  81. package/src/input/commands/notify-runtime.ts +9 -0
  82. package/src/input/commands/operator-runtime.ts +10 -12
  83. package/src/input/commands/permissions-provenance.ts +3 -3
  84. package/src/input/commands/planning-runtime.ts +7 -7
  85. package/src/input/commands/platform-sandbox-runtime.ts +2 -2
  86. package/src/input/commands/platform-services-runtime.ts +1 -1
  87. package/src/input/commands/policy-dispatch.ts +2 -2
  88. package/src/input/commands/policy.ts +3 -3
  89. package/src/input/commands/provider.ts +1 -1
  90. package/src/input/commands/remote-runtime.ts +1 -1
  91. package/src/input/commands/review-runtime.ts +39 -94
  92. package/src/input/commands/rewind-runtime.ts +81 -108
  93. package/src/input/commands/runtime-services.ts +15 -17
  94. package/src/input/commands/services-runtime.ts +2 -2
  95. package/src/input/commands/session-content.ts +21 -5
  96. package/src/input/commands/session-workflow.ts +13 -38
  97. package/src/input/commands/settings-sync-runtime.ts +2 -2
  98. package/src/input/commands/shell-core.ts +19 -4
  99. package/src/input/commands/skills-runtime.ts +2 -2
  100. package/src/input/commands/status-runtime.ts +13 -0
  101. package/src/input/commands/subscription-runtime.ts +1 -1
  102. package/src/input/commands/tasks-runtime.ts +2 -8
  103. package/src/input/commands/work-plan-runtime.ts +4 -4
  104. package/src/input/commands/workstream-attempts.ts +24 -47
  105. package/src/input/commands/workstream-runtime.ts +2 -2
  106. package/src/input/commands/worktree-runtime.ts +3 -3
  107. package/src/input/commands.ts +3 -1
  108. package/src/input/composer-editor.ts +44 -0
  109. package/src/input/concealed-input.ts +1 -1
  110. package/src/input/config-modal-types.ts +20 -8
  111. package/src/input/config-modal.ts +106 -105
  112. package/src/input/confirm-dialog-types.ts +28 -0
  113. package/src/input/confirm-dialog.ts +70 -0
  114. package/src/input/delete-key-policy.ts +3 -3
  115. package/src/input/feed-context-factory.ts +15 -16
  116. package/src/input/handler-command-route.ts +7 -42
  117. package/src/input/handler-content-actions.ts +16 -15
  118. package/src/input/handler-feed-routes.ts +26 -211
  119. package/src/input/handler-feed.ts +68 -95
  120. package/src/input/handler-interactions.ts +5 -3
  121. package/src/input/handler-modal-routes.ts +215 -216
  122. package/src/input/handler-modal-stack.ts +81 -11
  123. package/src/input/handler-modal-token-routes.ts +4 -0
  124. package/src/input/handler-onboarding.ts +0 -1
  125. package/src/input/handler-picker-routes.ts +65 -107
  126. package/src/input/handler-session-view-route.ts +149 -0
  127. package/src/input/handler-shortcuts.ts +16 -154
  128. package/src/input/handler-types.ts +4 -2
  129. package/src/input/handler-ui-state.ts +45 -15
  130. package/src/input/handler-work-tree-route.ts +123 -0
  131. package/src/input/handler.ts +25 -70
  132. package/src/input/keybindings.ts +16 -71
  133. package/src/input/masked-entry-modal.ts +117 -0
  134. package/src/input/mcp-workspace.ts +48 -17
  135. package/src/input/model-picker-filter.ts +8 -0
  136. package/src/input/model-picker-open.ts +99 -0
  137. package/src/input/model-picker-types.ts +1 -1
  138. package/src/input/model-picker.ts +92 -9
  139. package/src/input/notifications-modal.ts +123 -0
  140. package/src/input/overlay-filter.ts +26 -0
  141. package/src/input/profile-picker-modal.ts +37 -8
  142. package/src/input/search.ts +3 -3
  143. package/src/input/selection-modal-queue.ts +2 -0
  144. package/src/input/selection-modal.ts +28 -16
  145. package/src/input/session-picker-modal.ts +33 -8
  146. package/src/input/settings-modal-activation.ts +7 -2
  147. package/src/input/settings-modal-data.ts +11 -6
  148. package/src/input/settings-modal-types.ts +1 -1
  149. package/src/input/settings-modal.ts +20 -20
  150. package/src/input/submission-intent.ts +1 -1
  151. package/src/input/submission-router.ts +4 -9
  152. package/src/input/surface-modal-host.ts +110 -0
  153. package/src/input/theme-settings-actions.ts +82 -0
  154. package/src/input/usage-modal.ts +183 -0
  155. package/src/input/views.ts +97 -0
  156. package/src/main.ts +175 -165
  157. package/src/permissions/hunk-selection.ts +1 -1
  158. package/src/permissions/prompt-card.ts +511 -0
  159. package/src/permissions/prompt.ts +59 -622
  160. package/src/renderer/agents-modal.ts +310 -0
  161. package/src/renderer/autocomplete-overlay.ts +42 -220
  162. package/src/renderer/back-to-bottom.ts +80 -0
  163. package/src/renderer/block-actions-overlay.ts +30 -48
  164. package/src/renderer/bookmark-modal.ts +48 -79
  165. package/src/renderer/changes-modal.ts +325 -0
  166. package/src/renderer/code-block.ts +184 -106
  167. package/src/renderer/command-palette.ts +93 -0
  168. package/src/renderer/composer-fragments.ts +4 -4
  169. package/src/renderer/composer.ts +146 -0
  170. package/src/renderer/compositor.ts +26 -160
  171. package/src/renderer/config-modal.ts +185 -81
  172. package/src/renderer/confirm-dialog.ts +50 -0
  173. package/src/renderer/context-inspector.ts +116 -154
  174. package/src/renderer/context-status-hint.ts +4 -4
  175. package/src/renderer/conversation-layout.ts +20 -0
  176. package/src/renderer/conversation-overlays.ts +94 -84
  177. package/src/renderer/conversation-surface.ts +19 -38
  178. package/src/renderer/diff-tint.ts +98 -0
  179. package/src/renderer/file-picker-overlay.ts +63 -90
  180. package/src/renderer/footer-targets.ts +38 -0
  181. package/src/renderer/git-status.ts +3 -4
  182. package/src/renderer/header-line.ts +140 -117
  183. package/src/renderer/help-overlay.ts +133 -219
  184. package/src/renderer/history-search-overlay.ts +15 -12
  185. package/src/renderer/lane-graph/bead.ts +800 -0
  186. package/src/renderer/lane-graph/glyphs.ts +134 -0
  187. package/src/renderer/lane-graph/layout.ts +248 -0
  188. package/src/renderer/lane-graph/paint.ts +537 -0
  189. package/src/renderer/lane-graph/semantic-memo.ts +77 -0
  190. package/src/renderer/layout-engine.ts +2 -57
  191. package/src/renderer/layout.ts +7 -13
  192. package/src/renderer/markdown-inline.ts +2 -1
  193. package/src/renderer/markdown-table.ts +17 -12
  194. package/src/renderer/markdown.ts +92 -34
  195. package/src/renderer/masked-entry-modal.ts +78 -0
  196. package/src/renderer/mcp-workspace.ts +166 -203
  197. package/src/renderer/model-workspace.ts +381 -436
  198. package/src/renderer/notifications-modal.ts +116 -0
  199. package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
  200. package/src/renderer/process-view.ts +192 -0
  201. package/src/renderer/profile-picker-modal.ts +58 -110
  202. package/src/renderer/search-overlay.ts +6 -3
  203. package/src/renderer/selection-modal-overlay.ts +87 -327
  204. package/src/renderer/semantic-diff.ts +2 -2
  205. package/src/renderer/session-chips.ts +102 -0
  206. package/src/renderer/session-picker-modal.ts +177 -297
  207. package/src/renderer/settings-modal-connections.ts +14 -23
  208. package/src/renderer/settings-modal-context.ts +367 -0
  209. package/src/renderer/settings-modal-helpers.ts +6 -11
  210. package/src/renderer/settings-modal.ts +322 -726
  211. package/src/renderer/shell-surface.ts +277 -175
  212. package/src/renderer/shortcuts-overlay.ts +217 -0
  213. package/src/renderer/status-line.ts +475 -0
  214. package/src/renderer/surface-compose.ts +174 -0
  215. package/src/renderer/surface-kit-extra.ts +303 -0
  216. package/src/renderer/surface-kit-list.ts +366 -0
  217. package/src/renderer/surface-kit-parts.ts +360 -0
  218. package/src/renderer/surface-kit.ts +568 -0
  219. package/src/renderer/syntax-highlighter.ts +280 -197
  220. package/src/renderer/syntax-theme.ts +51 -0
  221. package/src/renderer/system-message.ts +0 -1
  222. package/src/renderer/term-caps.ts +42 -1
  223. package/src/renderer/terminal-bg-probe.ts +116 -19
  224. package/src/renderer/terminal-palette-probe.ts +322 -0
  225. package/src/renderer/terminal-palette-reader.ts +183 -0
  226. package/src/renderer/terminal-palette.ts +58 -0
  227. package/src/renderer/theme-mode-config.ts +16 -1
  228. package/src/renderer/theme.ts +234 -207
  229. package/src/renderer/thinking.ts +0 -1
  230. package/src/renderer/throbber.ts +188 -0
  231. package/src/renderer/toast-center.ts +119 -0
  232. package/src/renderer/ui-factory.ts +39 -602
  233. package/src/renderer/ui-primitives.ts +8 -25
  234. package/src/renderer/usage-modal.ts +353 -0
  235. package/src/renderer/user-message.ts +58 -0
  236. package/src/renderer/voice-capture-chip.ts +53 -0
  237. package/src/runtime/bootstrap-command-context.ts +0 -5
  238. package/src/runtime/bootstrap-command-parts.ts +38 -89
  239. package/src/runtime/bootstrap-core.ts +13 -15
  240. package/src/runtime/bootstrap-hook-bridge.ts +12 -21
  241. package/src/runtime/bootstrap-notifier-sync.ts +17 -1
  242. package/src/runtime/bootstrap-shell.ts +16 -50
  243. package/src/runtime/bootstrap.ts +14 -14
  244. package/src/runtime/client/fleet-union.ts +5 -5
  245. package/src/runtime/client/hosted-exit.ts +2 -2
  246. package/src/runtime/client/hosted-sessions.ts +1 -1
  247. package/src/runtime/client/spine-adoption.ts +1 -1
  248. package/src/runtime/context-accounting-source.ts +5 -4
  249. package/src/runtime/index.ts +1 -0
  250. package/src/runtime/notification-dispatch.ts +102 -29
  251. package/src/runtime/recovery-offer-wiring.ts +4 -1
  252. package/src/runtime/recovery-prompt.ts +31 -15
  253. package/src/runtime/runtime-services-types.ts +10 -6
  254. package/src/runtime/services.ts +17 -12
  255. package/src/runtime/session-continuity-hints.ts +2 -5
  256. package/src/runtime/session-inbound-inputs.ts +1 -1
  257. package/src/runtime/session-resume-liveness-confirm.ts +14 -2
  258. package/src/runtime/startup-typeahead-gate.ts +103 -0
  259. package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
  260. package/src/runtime/store/domains/index.ts +0 -8
  261. package/src/runtime/store/selectors/index.ts +0 -28
  262. package/src/runtime/store/state.ts +0 -8
  263. package/src/runtime/ui-services.ts +4 -6
  264. package/src/runtime/usage-tracker.ts +348 -0
  265. package/src/runtime/wrfc-persistence.ts +4 -4
  266. package/src/shell/blocking-input.ts +92 -2
  267. package/src/shell/recovery-input-helpers.ts +1 -1
  268. package/src/shell/session-views.ts +539 -0
  269. package/src/shell/status-report.ts +86 -0
  270. package/src/shell/transcript-scroll.ts +68 -0
  271. package/src/shell/ui-openers.ts +84 -172
  272. package/src/shell/view-openers.ts +205 -0
  273. package/src/tools/wrfc-agent-guard.ts +22 -2
  274. package/src/utils/format-duration.ts +4 -8
  275. package/src/utils/splash-lines.ts +9 -1
  276. package/src/utils/terminal-width.ts +56 -0
  277. package/src/verification/verification-ledger.ts +18 -16
  278. package/src/{panels → views}/agent-inspector-shared.ts +2 -2
  279. package/src/{panels → views}/builtin-modals.ts +22 -21
  280. package/src/views/builtin-views.ts +135 -0
  281. package/src/{panels → views}/confirm-state.ts +9 -9
  282. package/src/{panels → views}/diff-review-model.ts +10 -3
  283. package/src/{panels → views}/eval-registry.ts +1 -1
  284. package/src/{panels → views}/fleet-acts.ts +49 -85
  285. package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
  286. package/src/{panels → views}/fleet-gateway.ts +5 -5
  287. package/src/{panels → views}/fleet-observed-render.ts +16 -16
  288. package/src/{panels → views}/fleet-read-model.ts +9 -9
  289. package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
  290. package/src/{panels → views}/fleet-spawn.ts +33 -49
  291. package/src/{panels → views}/fleet-steer.ts +12 -12
  292. package/src/{panels → views}/fleet-stop.ts +4 -4
  293. package/src/{panels → views}/fleet-transcript.ts +26 -27
  294. package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
  295. package/src/{panels → views}/hosted-session-feed.ts +6 -6
  296. package/src/views/modal-surface-registry.ts +41 -0
  297. package/src/{panels → views}/modals/devices-modal.ts +2 -2
  298. package/src/{panels → views}/modals/hooks-modal.ts +13 -13
  299. package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
  300. package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
  301. package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
  302. package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
  303. package/src/{panels → views}/modals/memory-modal.ts +8 -8
  304. package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
  305. package/src/{panels → views}/modals/pairing-modal.ts +9 -9
  306. package/src/{panels → views}/modals/planning-modal.ts +15 -20
  307. package/src/{panels → views}/modals/plugins-modal.ts +6 -6
  308. package/src/{panels → views}/modals/policy-modal.ts +13 -5
  309. package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
  310. package/src/{panels → views}/modals/remote-modal.ts +3 -3
  311. package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
  312. package/src/{panels → views}/modals/security-modal.ts +22 -16
  313. package/src/{panels → views}/modals/services-modal.ts +3 -3
  314. package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
  315. package/src/{panels → views}/modals/skills-modal.ts +9 -9
  316. package/src/{panels → views}/modals/subscription-modal.ts +3 -3
  317. package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
  318. package/src/views/notifications-feed.ts +314 -0
  319. package/src/{panels → views}/polish-core.ts +38 -36
  320. package/src/{panels → views}/polish-tables.ts +35 -26
  321. package/src/{panels → views}/polish.ts +101 -95
  322. package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
  323. package/src/{panels → views}/provider-health-routes.ts +14 -14
  324. package/src/{panels → views}/search-focus.ts +8 -8
  325. package/src/{panels → views}/session-maintenance.ts +11 -11
  326. package/src/views/skills-discovery.ts +159 -0
  327. package/src/views/view-deps.ts +80 -0
  328. package/src/views/view-panel-adapter.ts +53 -0
  329. package/src/{panels → views}/workstream-graph-render.ts +1 -1
  330. package/src/core/conversation-fold.ts +0 -101
  331. package/src/core/conversation-turn-structure.ts +0 -464
  332. package/src/core/conversation-user-receipts.ts +0 -42
  333. package/src/input/commands/operator-panel-runtime.ts +0 -186
  334. package/src/input/panel-integration-actions.ts +0 -33
  335. package/src/input/panel-mouse-geometry.ts +0 -97
  336. package/src/input/panel-paste-flood-guard.ts +0 -86
  337. package/src/panels/base-panel.ts +0 -301
  338. package/src/panels/builtin/agent.ts +0 -27
  339. package/src/panels/builtin/development.ts +0 -92
  340. package/src/panels/builtin/knowledge.ts +0 -12
  341. package/src/panels/builtin/operations.ts +0 -177
  342. package/src/panels/builtin/session.ts +0 -81
  343. package/src/panels/builtin/shared.ts +0 -357
  344. package/src/panels/builtin-panels.ts +0 -28
  345. package/src/panels/cost-tracker-panel.ts +0 -746
  346. package/src/panels/diff-panel.ts +0 -797
  347. package/src/panels/diff-review-panel.ts +0 -395
  348. package/src/panels/expandable-list-panel.ts +0 -189
  349. package/src/panels/fleet-deep-link.ts +0 -31
  350. package/src/panels/fleet-panel.ts +0 -799
  351. package/src/panels/git-panel.ts +0 -800
  352. package/src/panels/hosted-session-panel.ts +0 -221
  353. package/src/panels/index.ts +0 -21
  354. package/src/panels/local-auth-panel.ts +0 -478
  355. package/src/panels/notifications-feed.ts +0 -127
  356. package/src/panels/notifications-panel.ts +0 -160
  357. package/src/panels/panel-confirm-overlay.ts +0 -72
  358. package/src/panels/panel-manager.ts +0 -791
  359. package/src/panels/plugins-panel.ts +0 -371
  360. package/src/panels/scrollable-list-panel.ts +0 -498
  361. package/src/panels/skills-panel.ts +0 -454
  362. package/src/panels/token-budget-panel.ts +0 -688
  363. package/src/panels/types.ts +0 -191
  364. package/src/panels/wrfc-panel-format.ts +0 -133
  365. package/src/renderer/diff-view.ts +0 -134
  366. package/src/renderer/fleet-tab-strip.ts +0 -56
  367. package/src/renderer/footer-tips.ts +0 -53
  368. package/src/renderer/fullscreen-primitives.ts +0 -146
  369. package/src/renderer/fullscreen-workspace.ts +0 -194
  370. package/src/renderer/hint-grammar.ts +0 -52
  371. package/src/renderer/modal-factory.ts +0 -480
  372. package/src/renderer/overlay-box.ts +0 -154
  373. package/src/renderer/overlay-viewport.ts +0 -104
  374. package/src/renderer/panel-composite.ts +0 -145
  375. package/src/renderer/panel-workspace-bar.ts +0 -54
  376. package/src/renderer/process-indicator.ts +0 -163
  377. package/src/renderer/tab-strip.ts +0 -222
  378. package/src/renderer/text-layout.ts +0 -31
  379. package/src/renderer/tool-call.ts +0 -276
  380. package/src/renderer/tool-result-expanded-lines.ts +0 -48
  381. package/src/renderer/tool-result-summary.ts +0 -219
  382. package/src/runtime/diagnostics/panels/index.ts +0 -21
  383. package/src/runtime/diagnostics/panels/ops.ts +0 -156
  384. package/src/runtime/perf/panel-contracts.ts +0 -32
  385. package/src/runtime/store/domains/panels.ts +0 -117
  386. /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
  387. /package/src/{panels → views}/modals/modal-theme.ts +0 -0
  388. /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);