@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,395 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // DiffReviewPanel, comment-on-hunk review loop.
3
- //
4
- // Shows the current session's file changes per-file and hunk-boundaried, lets
5
- // the user select a hunk, attach a comment, and submit that comment to the
6
- // session as a steering message carrying structured context (file path, new-file
7
- // line range, and a short patch excerpt) so the model knows exactly what the
8
- // comment targets.
9
- //
10
- // Data source (labelled honestly in the panel): `git diff <base> -- <files>`,
11
- // where <files> is the set the SDK SessionChangeTracker recorded this session.
12
- // That is CURRENT working-tree content vs the base ref, not a turn-start
13
- // snapshot. The panel never presents a stale diff as current.
14
- // ---------------------------------------------------------------------------
15
-
16
- import type { Line } from '@pellux/goodvibes-sdk/platform/types';
17
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types';
18
- import { truncateDisplay } from '../utils/terminal-width.ts';
19
- import { GitService } from '@pellux/goodvibes-sdk/platform/git';
20
- import { BasePanel } from './base-panel.ts';
21
- import { DIFF_TONES, UI_TONES } from '../renderer/ui-primitives.ts';
22
- import {
23
- buildEmptyState,
24
- buildPanelWorkspace,
25
- buildStyledPanelLine,
26
- resolveScrollablePanelSection,
27
- DEFAULT_PANEL_PALETTE,
28
- extendPalette,
29
- } from './polish.ts';
30
- import { isPanelSearchBackspace, isPanelSearchCancel, isPanelSearchCommit, isPanelSearchPrintable } from './search-focus.ts';
31
- import { appendSteerText } from './fleet-tabs.ts';
32
- import {
33
- parseReviewDiff,
34
- flattenHunks,
35
- hunkLineRange,
36
- buildSteerMessage,
37
- type ReviewFile,
38
- type ReviewHunk,
39
- type HunkComment,
40
- } from './diff-review-model.ts';
41
-
42
- const COLOR = extendPalette(DEFAULT_PANEL_PALETTE, {
43
- addition: DIFF_TONES.add,
44
- deletion: DIFF_TONES.del,
45
- hunk: DIFF_TONES.hunk,
46
- } as const);
47
-
48
- /** A comment attached to a hunk plus whether it has already been steered to the session. */
49
- interface CommentState {
50
- comment: string;
51
- submitted: boolean;
52
- }
53
-
54
- function basename(p: string): string {
55
- const parts = p.replace(/\\/g, '/').split('/');
56
- return parts[parts.length - 1] ?? p;
57
- }
58
-
59
- export class DiffReviewPanel extends BasePanel {
60
- public override isTransient = true;
61
-
62
- private readonly workingDirectory: string;
63
- private readonly getChangedFiles: (() => string[]) | undefined;
64
- private files: ReviewFile[] = [];
65
- private hunks: ReviewHunk[] = [];
66
- private cursor = 0;
67
- private scrollOffset = 0;
68
- /** Comments keyed by `${fileIndex}:${hunkIndex}`. */
69
- private comments = new Map<string, CommentState>();
70
- private composing = false;
71
- private draft = '';
72
- private submitSteer: ((text: string) => void) | null = null;
73
- /** Injected by the /review command: reverse-apply (reject) the given hunk via checkpoints.revertHunk. */
74
- private revertHunk: ((hunk: ReviewHunk) => void) | null = null;
75
- private sourceLabel = 'working tree vs HEAD (files edited this session)';
76
- private status: string | null = null;
77
- /** True once a load has completed (so the empty state can distinguish "no changes" from "not loaded"). */
78
- private loaded = false;
79
-
80
- constructor(
81
- workingDirectory: string,
82
- private readonly requestRender: () => void = () => {},
83
- getChangedFiles?: () => string[],
84
- ) {
85
- super('review', 'Review', 'R', 'development');
86
- this.workingDirectory = workingDirectory;
87
- this.getChangedFiles = getChangedFiles;
88
- }
89
-
90
- // -------------------------------------------------------------------------
91
- // Public API, driven by the /review command
92
- // -------------------------------------------------------------------------
93
-
94
- /** Wire the steering submit path (the /review command passes ctx.submitInput). */
95
- setSubmit(fn: (text: string) => void): void {
96
- this.submitSteer = fn;
97
- }
98
-
99
- /** Wire the hunk-revert path (the /review command drives checkpoints.revertHunk). */
100
- setRevertHandler(fn: (hunk: ReviewHunk) => void): void {
101
- this.revertHunk = fn;
102
- }
103
-
104
- /** Surface a status line (the revert flow reports conflicts/outcomes here). */
105
- note(message: string): void {
106
- this.status = message;
107
- this.markDirty();
108
- this.requestRender();
109
- }
110
-
111
- /** Re-load the session diff, used after a revert mutates the working tree. */
112
- async refresh(): Promise<void> {
113
- await this.loadSessionReview();
114
- this.requestRender();
115
- }
116
-
117
- private keyFor(hunk: ReviewHunk): string {
118
- return `${hunk.fileIndex}:${hunk.hunkIndex}`;
119
- }
120
-
121
- private currentHunk(): ReviewHunk | null {
122
- return this.hunks[this.cursor] ?? null;
123
- }
124
-
125
- /** Adopt a parsed review set with an explicit provenance label. */
126
- loadReview(files: ReviewFile[], sourceLabel: string): void {
127
- this.files = files;
128
- this.hunks = flattenHunks(files);
129
- this.cursor = 0;
130
- this.scrollOffset = 0;
131
- this.comments.clear();
132
- this.composing = false;
133
- this.draft = '';
134
- this.sourceLabel = sourceLabel;
135
- this.loaded = true;
136
- this.markDirty();
137
- }
138
-
139
- /**
140
- * Load the session's changed files as a git diff against `ref` (default HEAD)
141
- * and parse it into per-file hunks. Honest source: current working-tree
142
- * content vs the base ref, restricted to files this session edited.
143
- */
144
- async loadSessionReview(ref = 'HEAD'): Promise<void> {
145
- if (!GitService.isGitRepo(this.workingDirectory)) {
146
- this.status = 'Not a git repository: no working-tree diff to review.';
147
- this.loaded = true;
148
- this.markDirty();
149
- return;
150
- }
151
- const files = (this.getChangedFiles?.() ?? []).filter(Boolean);
152
- const args = ['diff', ref, '--', ...files];
153
- const wholeRepo = files.length === 0;
154
- const proc = Bun.spawn(['git', ...(wholeRepo ? ['diff', ref] : args)], { stdout: 'pipe', stderr: 'pipe', cwd: this.workingDirectory });
155
- const [raw, errText] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
156
- const exitCode = await proc.exited;
157
- if (exitCode !== 0) {
158
- this.status = `git diff failed: ${errText.trim() || 'unknown error'}`;
159
- this.loaded = true;
160
- this.markDirty();
161
- return;
162
- }
163
- const label = wholeRepo
164
- ? `working tree vs ${ref} (no session-tracked files; showing whole working tree)`
165
- : `working tree vs ${ref} (${files.length} file${files.length === 1 ? '' : 's'} edited this session)`;
166
- this.loadReview(parseReviewDiff(raw), label);
167
- }
168
-
169
- // -------------------------------------------------------------------------
170
- // Input
171
- // -------------------------------------------------------------------------
172
-
173
- handleInput(key: string): boolean {
174
- if (this.composing) return this.handleComposerInput(key);
175
- switch (key) {
176
- case 'up': case 'k': this.moveCursor(-1); return true;
177
- case 'down': case 'j': this.moveCursor(1); return true;
178
- case 'tab': this.jumpFile(1); return true;
179
- case '\x1b[Z': case 'shift-tab': case 'backtab': this.jumpFile(-1); return true;
180
- case 'pageup': this.moveCursor(-5); return true;
181
- case 'pagedown': this.moveCursor(5); return true;
182
- case 'c': this.openComposer(); return true;
183
- case 'x': this.clearComment(); return true;
184
- case 'a': this.submitAll(); return true;
185
- case 'r': this.rejectCurrent(); return true;
186
- case 'enter': case 'return': this.submitCurrent(); return true;
187
- default: return false;
188
- }
189
- }
190
-
191
- private handleComposerInput(key: string): boolean {
192
- if (isPanelSearchCancel(key)) {
193
- this.composing = false;
194
- this.draft = '';
195
- this.status = 'Comment cancelled.';
196
- this.markDirty();
197
- return true;
198
- }
199
- if (isPanelSearchCommit(key)) {
200
- const hunk = this.currentHunk();
201
- const text = this.draft.trim();
202
- if (hunk && text.length > 0) {
203
- this.comments.set(this.keyFor(hunk), { comment: text, submitted: false });
204
- this.status = 'Comment attached. Press Enter to send it, or a to send all.';
205
- } else {
206
- this.status = 'Empty comment discarded.';
207
- }
208
- this.composing = false;
209
- this.draft = '';
210
- this.markDirty();
211
- return true;
212
- }
213
- if (isPanelSearchBackspace(key)) {
214
- this.draft = this.draft.slice(0, -1);
215
- this.markDirty();
216
- return true;
217
- }
218
- if (key.length === 1 && (isPanelSearchPrintable(key) || key === '\r' || key === '\n')) {
219
- this.draft = appendSteerText(this.draft, key);
220
- this.markDirty();
221
- return true;
222
- }
223
- return true; // swallow other keys while composing
224
- }
225
-
226
- private moveCursor(delta: number): void {
227
- if (this.hunks.length === 0) return;
228
- this.cursor = Math.max(0, Math.min(this.hunks.length - 1, this.cursor + delta));
229
- this.markDirty();
230
- }
231
-
232
- private jumpFile(direction: number): void {
233
- const cur = this.currentHunk();
234
- if (!cur) return;
235
- const targetFile = cur.fileIndex + direction;
236
- const idx = this.hunks.findIndex((h) => h.fileIndex === targetFile);
237
- if (idx >= 0) {
238
- this.cursor = idx;
239
- this.markDirty();
240
- }
241
- }
242
-
243
- private openComposer(): void {
244
- const hunk = this.currentHunk();
245
- if (!hunk) return;
246
- this.composing = true;
247
- this.draft = this.comments.get(this.keyFor(hunk))?.comment ?? '';
248
- this.status = null;
249
- this.markDirty();
250
- }
251
-
252
- private clearComment(): void {
253
- const hunk = this.currentHunk();
254
- if (!hunk) return;
255
- if (this.comments.delete(this.keyFor(hunk))) {
256
- this.status = 'Comment removed.';
257
- this.markDirty();
258
- }
259
- }
260
-
261
- private steer(items: HunkComment[]): boolean {
262
- if (!this.submitSteer) {
263
- this.status = 'No session is wired to receive steering here.';
264
- this.markDirty();
265
- return false;
266
- }
267
- if (items.length === 0) {
268
- this.status = 'No comment to send. Press c to write one.';
269
- this.markDirty();
270
- return false;
271
- }
272
- this.submitSteer(buildSteerMessage(items, this.sourceLabel));
273
- for (const { hunk } of items) {
274
- const state = this.comments.get(this.keyFor(hunk));
275
- if (state) state.submitted = true;
276
- }
277
- this.status = items.length === 1
278
- ? 'Comment sent to the session as a steering message.'
279
- : `${items.length} comments sent to the session as one steering message.`;
280
- this.markDirty();
281
- this.requestRender();
282
- return true;
283
- }
284
-
285
- private submitCurrent(): void {
286
- const hunk = this.currentHunk();
287
- if (!hunk) return;
288
- const state = this.comments.get(this.keyFor(hunk));
289
- if (!state || state.comment.length === 0) {
290
- this.status = 'This hunk has no comment yet. Press c to write one.';
291
- this.markDirty();
292
- return;
293
- }
294
- this.steer([{ hunk, comment: state.comment }]);
295
- }
296
-
297
- /** Reject the selected hunk: hand it to the revert flow (preview → confirm → revertHunk). */
298
- private rejectCurrent(): void {
299
- const hunk = this.currentHunk();
300
- if (!hunk) return;
301
- if (!this.revertHunk) {
302
- this.note('Hunk revert is not wired in this session.');
303
- return;
304
- }
305
- this.revertHunk(hunk);
306
- }
307
-
308
- private submitAll(): void {
309
- const items: HunkComment[] = [];
310
- for (const hunk of this.hunks) {
311
- const state = this.comments.get(this.keyFor(hunk));
312
- if (state && !state.submitted && state.comment.length > 0) items.push({ hunk, comment: state.comment });
313
- }
314
- this.steer(items);
315
- }
316
-
317
- // -------------------------------------------------------------------------
318
- // Render
319
- // -------------------------------------------------------------------------
320
-
321
- private hunkRow(hunk: ReviewHunk, isCursor: boolean, width: number): Line {
322
- const state = this.comments.get(this.keyFor(hunk));
323
- const marker = !state ? ' ' : state.submitted ? '[✓]' : '[c]';
324
- const range = hunkLineRange(hunk);
325
- const label = `${marker} ${basename(hunk.filePath)}:${range.start}-${range.end} +${hunk.added} -${hunk.removed}`;
326
- return buildStyledPanelLine(width, [{
327
- text: truncateDisplay(`${isCursor ? '▸ ' : ' '}${label}`, width),
328
- fg: state?.submitted ? COLOR.addition : isCursor ? COLOR.value : COLOR.dim,
329
- bg: isCursor ? UI_TONES.bg.selected : undefined,
330
- bold: isCursor,
331
- }]);
332
- }
333
-
334
- private detailLines(hunk: ReviewHunk, width: number): Line[] {
335
- const lines: Line[] = [];
336
- lines.push(buildStyledPanelLine(width, [{ text: truncateDisplay(` ${hunk.filePath}`, width), fg: COLOR.value, bold: true }]));
337
- lines.push(buildStyledPanelLine(width, [{ text: truncateDisplay(` ${hunk.header}`, width), fg: COLOR.hunk }]));
338
- for (const raw of hunk.bodyLines.slice(0, 8)) {
339
- const fg = raw.startsWith('+') ? COLOR.addition : raw.startsWith('-') ? COLOR.deletion : COLOR.dim;
340
- lines.push(buildStyledPanelLine(width, [{ text: truncateDisplay(` ${raw}`, width), fg }]));
341
- }
342
- const state = this.comments.get(this.keyFor(hunk));
343
- if (this.composing) {
344
- lines.push(buildStyledPanelLine(width, [{ text: truncateDisplay(` comment> ${this.draft}_`, width), fg: COLOR.value, bold: true }]));
345
- } else if (state) {
346
- const tag = state.submitted ? '(sent)' : '(unsent)';
347
- lines.push(buildStyledPanelLine(width, [{ text: truncateDisplay(` comment ${tag}: ${state.comment}`, width), fg: state.submitted ? COLOR.addition : COLOR.value }]));
348
- }
349
- return lines;
350
- }
351
-
352
- private footer(width: number): Line {
353
- const hints = this.composing
354
- ? ' Enter attach Esc cancel'
355
- : ' ↑/↓ hunk Tab file c comment Enter send a send all r revert x clear';
356
- const text = this.status ? `${hints} • ${this.status}` : hints;
357
- return buildStyledPanelLine(width, [{ text: truncateDisplay(text, width), fg: COLOR.dim, bg: COLOR.sectionBg }], { fillBg: COLOR.sectionBg });
358
- }
359
-
360
- render(width: number, height: number): Line[] {
361
- return this.trackedRender(() => {
362
- if (height <= 0 || width <= 0) return [];
363
- if (this.hunks.length === 0) {
364
- const detail = this.loaded
365
- ? (this.status ?? 'No changes to review. Files edited this session appear here as hunks.')
366
- : 'Loading session changes…';
367
- return buildPanelWorkspace(width, height, {
368
- title: 'Review',
369
- palette: COLOR,
370
- sections: [{ title: 'Review', lines: buildEmptyState(width, ' Nothing to review yet.', detail, [{ command: '/review', summary: 'load this session\'s file changes as commentable hunks' }], COLOR) }],
371
- });
372
- }
373
-
374
- const hunk = this.currentHunk();
375
- const listLines = this.hunks.map((h, i) => this.hunkRow(h, i === this.cursor, width));
376
- const section = resolveScrollablePanelSection(width, height, {
377
- palette: COLOR,
378
- footerLines: [this.footer(width)],
379
- beforeSections: [{ title: `Selected hunk (${this.sourceLabel})`, lines: hunk ? this.detailLines(hunk, width) : [] }],
380
- section: { title: `Hunks (${this.cursor + 1}/${this.hunks.length})`, scrollableLines: listLines, scrollOffset: this.scrollOffset, selectedIndex: this.cursor, minRows: 1 },
381
- });
382
- this.scrollOffset = section.scrollOffset;
383
-
384
- return buildPanelWorkspace(width, height, {
385
- title: 'Review',
386
- palette: COLOR,
387
- footerLines: [this.footer(width)],
388
- sections: [
389
- { title: `Selected hunk (${this.sourceLabel})`, lines: hunk ? this.detailLines(hunk, width) : [createEmptyLine(width)] },
390
- { title: section.section.title, lines: section.section.lines },
391
- ],
392
- });
393
- });
394
- }
395
- }
@@ -1,189 +0,0 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types';
3
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
- import {
5
- buildKeyboardHints,
6
- buildPanelWorkspace,
7
- resolveScrollablePanelSection,
8
- } from './polish.ts';
9
-
10
- // ---------------------------------------------------------------------------
11
- // ExpandableListPanel<T>
12
- // ---------------------------------------------------------------------------
13
-
14
- /**
15
- * A list panel with a built-in master → detail drill-down.
16
- *
17
- * Encapsulates the collapse/expand + detail-view pattern that was previously
18
- * hand-rolled in git, tool-inspector, agent-inspector, approval, and wrfc
19
- * panels: a scrollable list where pressing Enter/→ on a row opens a scrollable
20
- * detail view for that item, and Esc/← returns to the list.
21
- *
22
- * Subclasses implement (in addition to the `ScrollableListPanel` contract):
23
- * - `getDetailLines(item, width)`, the detail body for the expanded item.
24
- *
25
- * Optionally override:
26
- * - `getDetailTitle(item)`, detail-view title (defaults to `this.name`).
27
- * - `getListHints()` / `getDetailHints()`, footer keyboard hints per mode.
28
- * - `renderListView(width, height)`, the list-mode body (defaults to
29
- * `renderList` with the list hints footer).
30
- *
31
- * Do NOT override `render()`; it dispatches between list and detail modes.
32
- */
33
- export abstract class ExpandableListPanel<T> extends ScrollableListPanel<T> {
34
- /** The item currently expanded into the detail view, or null in list mode. */
35
- protected expandedItem: T | null = null;
36
- /** First visible row of the detail view. */
37
- protected detailScroll = 0;
38
-
39
- /** Render the detail body lines for an expanded item. */
40
- protected abstract getDetailLines(item: T, width: number): readonly Line[];
41
-
42
- /** Title shown above the detail view. */
43
- protected getDetailTitle(_item: T): string {
44
- return this.name;
45
- }
46
-
47
- /** Keyboard hints shown in list mode. */
48
- protected getListHints(): ReadonlyArray<{ keys: string; label: string }> {
49
- return [
50
- { keys: '↑/↓', label: 'move' },
51
- { keys: 'Enter', label: 'open' },
52
- ];
53
- }
54
-
55
- /** Keyboard hints shown in detail mode. */
56
- protected getDetailHints(): ReadonlyArray<{ keys: string; label: string }> {
57
- return [
58
- { keys: '↑/↓', label: 'scroll' },
59
- { keys: 'Esc', label: 'back' },
60
- ];
61
- }
62
-
63
- protected isExpanded(): boolean {
64
- return this.expandedItem !== null;
65
- }
66
-
67
- /** Expand the currently selected item into the detail view. */
68
- protected expandSelected(): void {
69
- const items = this.getItems();
70
- const item = items[this.selectedIndex];
71
- if (item === undefined) return;
72
- this.expandedItem = item;
73
- this.detailScroll = 0;
74
- this.needsRender = true;
75
- }
76
-
77
- /** Collapse the detail view back to the list. */
78
- protected collapse(): void {
79
- if (this.expandedItem === null) return;
80
- this.expandedItem = null;
81
- this.detailScroll = 0;
82
- this.needsRender = true;
83
- }
84
-
85
- // -------------------------------------------------------------------------
86
- // Input
87
- // -------------------------------------------------------------------------
88
-
89
- handleInput(key: string): boolean {
90
- if (this.expandedItem !== null) {
91
- // Detail mode: scroll the detail body; Esc/← collapses.
92
- if (this.lastError !== null) this.clearError();
93
- switch (key) {
94
- case 'escape':
95
- case 'left':
96
- case 'h':
97
- this.collapse();
98
- return true;
99
- case 'up':
100
- case 'k':
101
- this.detailScroll = Math.max(0, this.detailScroll - 1);
102
- this.needsRender = true;
103
- return true;
104
- case 'down':
105
- case 'j':
106
- this.detailScroll += 1;
107
- this.needsRender = true;
108
- return true;
109
- case 'pageup':
110
- this.detailScroll = Math.max(0, this.detailScroll - this.getPageSize());
111
- this.needsRender = true;
112
- return true;
113
- case 'pagedown':
114
- this.detailScroll += this.getPageSize();
115
- this.needsRender = true;
116
- return true;
117
- case 'home':
118
- case 'g':
119
- this.detailScroll = 0;
120
- this.needsRender = true;
121
- return true;
122
- default:
123
- return false;
124
- }
125
- }
126
-
127
- // List mode: Enter/→ expands; everything else is list navigation.
128
- if (key === 'enter' || key === 'return' || key === 'right' || key === 'l') {
129
- if (this.getItems().length === 0) return false;
130
- this.expandSelected();
131
- return true;
132
- }
133
- return super.handleInput(key);
134
- }
135
-
136
- handleScroll(deltaRows: number): boolean {
137
- if (this.expandedItem !== null) {
138
- const rows = Math.trunc(deltaRows);
139
- if (rows === 0) return false;
140
- this.detailScroll = Math.max(0, this.detailScroll + rows);
141
- this.needsRender = true;
142
- return true;
143
- }
144
- return super.handleScroll(deltaRows);
145
- }
146
-
147
- // -------------------------------------------------------------------------
148
- // Render
149
- // -------------------------------------------------------------------------
150
-
151
- render(width: number, height: number): Line[] {
152
- if (this.expandedItem !== null) {
153
- return this.renderDetailView(width, height, this.expandedItem);
154
- }
155
- return this.renderListView(width, height);
156
- }
157
-
158
- /** List-mode body. Override for a custom header; defaults to renderList. */
159
- protected renderListView(width: number, height: number): Line[] {
160
- return this.renderList(width, height, { hints: this.getListHints() });
161
- }
162
-
163
- /** Detail-mode body, scrollable detail lines + standardized chrome. */
164
- protected renderDetailView(width: number, height: number, item: T): Line[] {
165
- this.needsRender = false;
166
- const palette = this.getPalette();
167
- const detailLines = [...this.getDetailLines(item, width)];
168
- const footer = buildKeyboardHints(width, this.getDetailHints(), palette);
169
-
170
- const resolved = resolveScrollablePanelSection(width, height, {
171
- palette,
172
- afterSections: [{ lines: [footer] }],
173
- section: {
174
- scrollableLines: detailLines,
175
- scrollOffset: this.detailScroll,
176
- appendWindowSummary: detailLines.length > 5 ? { dimColor: palette.dim } : undefined,
177
- },
178
- });
179
- this.detailScroll = resolved.scrollOffset;
180
-
181
- const lines = buildPanelWorkspace(width, height, {
182
- title: this.getDetailTitle(item),
183
- sections: [resolved.section, { lines: [footer] }],
184
- palette,
185
- });
186
- while (lines.length < height) lines.push(createEmptyLine(width));
187
- return lines.slice(0, height);
188
- }
189
- }
@@ -1,31 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // fleet-deep-link.ts, item 4.
3
- //
4
- // PanelManager.open('fleet', pane, target) hands FleetPanel a
5
- // PanelDeepLinkTarget (a process id + optional ProcessKind) so a cross-panel
6
- // jump (the work-plan modal's 'i'/'w' agent/WRFC-chain jumps, the docs
7
- // modal's tools-tab Enter) can select + reveal a SPECIFIC node instead of
8
- // just opening Fleet generically. Extracted from fleet-panel.ts (already at
9
- // the 800-line architecture cap) so the panel stays a thin caller.
10
- //
11
- // `kind`, when given, must also match: ProcessNode ids are unique per the
12
- // SDK's own contract, so this is a cheap extra honesty check, not the
13
- // primary lookup key. Reveal is "free" once selectedIndex is set,
14
- // ScrollableListPanel's render-time scroll-window resolution keeps the
15
- // selected row visible, so FleetPanel.receiveDeepLink does not need its own
16
- // scroll math.
17
- //
18
- // A target that isn't found (pruned, killed, never existed, or a domain,
19
- // like the docs Tools tab's static tool definitions, that has no
20
- // corresponding fleet node at all) resolves to -1; the caller degrades
21
- // honestly (FleetPanel.setError('node no longer present.')) rather than
22
- // silently landing on an unrelated row.
23
- // ---------------------------------------------------------------------------
24
-
25
- import type { PanelDeepLinkTarget } from './types.ts';
26
- import type { FleetTreeRow } from './fleet-read-model.ts';
27
-
28
- /** The row index matching `target`'s id (+ kind, if given), or -1. Pure, no I/O, no mutation. */
29
- export function resolveFleetDeepLinkIndex(rows: readonly FleetTreeRow[], target: PanelDeepLinkTarget): number {
30
- return rows.findIndex((row) => row.node.id === target.id && (!target.kind || row.node.kind === target.kind));
31
- }