@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
@@ -29,8 +29,8 @@ export interface SelectionResult {
29
29
  export class SelectionModal {
30
30
  public active = false;
31
31
  public title = '';
32
- public query = ''; // fuzzy search query
33
- public searchFocused = false;
32
+ /** The always-live search row's query (fuzzy). Typing edits it directly; there is no search mode. */
33
+ public query = '';
34
34
  public items: SelectionItem[] = [];
35
35
  public filteredItems: SelectionItem[] = [];
36
36
  public selectedIndex = 0;
@@ -44,6 +44,9 @@ export class SelectionModal {
44
44
  * the generic "Select".
45
45
  */
46
46
  public primaryVerbLabel: string | undefined = undefined;
47
+ /** Live-preview hook: called when the highlighted row changes (see open()). */
48
+ private onHighlight: ((item: SelectionItem | null) => void) | undefined = undefined;
49
+ private lastHighlightedId: string | null | undefined = undefined;
47
50
 
48
51
  /** Open the modal with items and title */
49
52
  open(
@@ -54,13 +57,17 @@ export class SelectionModal {
54
57
  allowSearch?: boolean;
55
58
  customActions?: Map<string, SelectionAction>;
56
59
  primaryVerbLabel?: string;
60
+ /**
61
+ * Called with the highlighted row right after open and whenever the
62
+ * cursor lands on a different row (move, search). Used for live preview.
63
+ */
64
+ onHighlight?: (item: SelectionItem | null) => void;
57
65
  }
58
66
  ): void {
59
67
  this.title = title;
60
68
  this.items = items;
61
69
  this.query = '';
62
70
  this.allowSearch = opts?.allowSearch ?? true;
63
- this.searchFocused = false;
64
71
  this.customActions = opts?.customActions ?? new Map();
65
72
  this.primaryVerbLabel = opts?.primaryVerbLabel;
66
73
  this.active = true;
@@ -73,13 +80,17 @@ export class SelectionModal {
73
80
  } else {
74
81
  this.selectedIndex = 0;
75
82
  }
83
+ this.onHighlight = opts?.onHighlight;
84
+ this.lastHighlightedId = undefined;
85
+ this.emitHighlight();
76
86
  }
77
87
 
78
88
  close(): void {
89
+ this.onHighlight = undefined;
90
+ this.lastHighlightedId = undefined;
79
91
  this.active = false;
80
92
  this.title = '';
81
93
  this.query = '';
82
- this.searchFocused = false;
83
94
  this.items = [];
84
95
  this.filteredItems = [];
85
96
  this.selectedIndex = 0;
@@ -93,6 +104,7 @@ export class SelectionModal {
93
104
  this.selectedIndex = this.selectedIndex > 0
94
105
  ? this.selectedIndex - 1
95
106
  : this.filteredItems.length - 1;
107
+ this.emitHighlight();
96
108
  }
97
109
 
98
110
  moveDown(): void {
@@ -100,6 +112,7 @@ export class SelectionModal {
100
112
  this.selectedIndex = this.selectedIndex < this.filteredItems.length - 1
101
113
  ? this.selectedIndex + 1
102
114
  : 0;
115
+ this.emitHighlight();
103
116
  }
104
117
 
105
118
  /** Update fuzzy search filter */
@@ -107,18 +120,7 @@ export class SelectionModal {
107
120
  this.query = query;
108
121
  this.selectedIndex = 0;
109
122
  this.filterItems();
110
- }
111
-
112
- canFocusSearch(): boolean {
113
- return this.allowSearch;
114
- }
115
-
116
- focusSearch(): void {
117
- if (this.allowSearch) this.searchFocused = true;
118
- }
119
-
120
- blurSearch(): void {
121
- this.searchFocused = false;
123
+ this.emitHighlight();
122
124
  }
123
125
 
124
126
  /** Get currently highlighted item */
@@ -127,6 +129,16 @@ export class SelectionModal {
127
129
  return this.filteredItems[this.selectedIndex] ?? null;
128
130
  }
129
131
 
132
+ /** Notify onHighlight when the highlighted row differs from the last one reported. */
133
+ private emitHighlight(): void {
134
+ if (!this.onHighlight) return;
135
+ const item = this.getSelected();
136
+ const id = item?.id ?? null;
137
+ if (id === this.lastHighlightedId) return;
138
+ this.lastHighlightedId = id;
139
+ this.onHighlight(item);
140
+ }
141
+
130
142
  /** Fuzzy match items against query, resets filteredItems */
131
143
  private filterItems(): void {
132
144
  if (this.query.length === 0) {
@@ -82,6 +82,12 @@ export class SessionPickerModal {
82
82
  /** Last status message to show in the modal (e.g. error or success). */
83
83
  public statusMessage = '';
84
84
 
85
+ /**
86
+ * The always-live search query. Filters the local sessions by name and
87
+ * title; selectedIndex and scrollOffset index the filtered list.
88
+ */
89
+ public query = '';
90
+
85
91
  public constructor(
86
92
  private readonly sessionManager: SessionManager,
87
93
  private readonly sessionBroker?: SessionReadFacade,
@@ -122,9 +128,25 @@ export class SessionPickerModal {
122
128
  this.scrollOffset = 0;
123
129
  this.statusMessage = '';
124
130
  this.deleteConfirmationTarget = null;
131
+ this.query = '';
125
132
  this.active = true;
126
133
  }
127
134
 
135
+ /** The local sessions matching the query (all of them when it is empty). */
136
+ visibleSessions(): SessionInfo[] {
137
+ const q = this.query.trim().toLowerCase();
138
+ if (q.length === 0) return this.sessions;
139
+ return this.sessions.filter((s) => s.name.toLowerCase().includes(q) || (s.title ?? '').toLowerCase().includes(q));
140
+ }
141
+
142
+ /** Replace the query; the selection returns to the first match. */
143
+ setQuery(query: string): void {
144
+ this.query = query;
145
+ this.selectedIndex = 0;
146
+ this.scrollOffset = 0;
147
+ this.deleteConfirmationTarget = null;
148
+ }
149
+
128
150
  close(): void {
129
151
  this.active = false;
130
152
  this.statusMessage = '';
@@ -132,15 +154,17 @@ export class SessionPickerModal {
132
154
  }
133
155
 
134
156
  moveUp(): void {
135
- if (this.sessions.length === 0) return;
136
- this.selectedIndex = (this.selectedIndex - 1 + this.sessions.length) % this.sessions.length;
157
+ const count = this.visibleSessions().length;
158
+ if (count === 0) return;
159
+ this.selectedIndex = (this.selectedIndex - 1 + count) % count;
137
160
  this._clampScroll();
138
161
  this.deleteConfirmationTarget = null;
139
162
  }
140
163
 
141
164
  moveDown(): void {
142
- if (this.sessions.length === 0) return;
143
- this.selectedIndex = (this.selectedIndex + 1) % this.sessions.length;
165
+ const count = this.visibleSessions().length;
166
+ if (count === 0) return;
167
+ this.selectedIndex = (this.selectedIndex + 1) % count;
144
168
  this._clampScroll();
145
169
  this.deleteConfirmationTarget = null;
146
170
  }
@@ -151,7 +175,7 @@ export class SessionPickerModal {
151
175
  }
152
176
 
153
177
  getSelected(): SessionInfo | null {
154
- return this.sessions[this.selectedIndex] ?? null;
178
+ return this.visibleSessions()[this.selectedIndex] ?? null;
155
179
  }
156
180
 
157
181
  /**
@@ -195,8 +219,9 @@ export class SessionPickerModal {
195
219
  // Reload list from the global session manager (removes the deleted entry)
196
220
  this.sessions = this.sessionManager.list();
197
221
  // Adjust selection
198
- if (this.selectedIndex >= this.sessions.length) {
199
- this.selectedIndex = Math.max(0, this.sessions.length - 1);
222
+ const remaining = this.visibleSessions().length;
223
+ if (this.selectedIndex >= remaining) {
224
+ this.selectedIndex = Math.max(0, remaining - 1);
200
225
  }
201
226
  this._clampScroll();
202
227
  this.deleteConfirmationTarget = null;
@@ -216,7 +241,7 @@ export class SessionPickerModal {
216
241
  } else if (this.selectedIndex >= this.scrollOffset + visRows) {
217
242
  this.scrollOffset = this.selectedIndex - visRows + 1;
218
243
  }
219
- const maxOffset = Math.max(0, this.sessions.length - visRows);
244
+ const maxOffset = Math.max(0, this.visibleSessions().length - visRows);
220
245
  this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxOffset));
221
246
  }
222
247
  }
@@ -10,7 +10,7 @@
10
10
  * settings-modal-reset.ts and settings-modal-mutations.ts.
11
11
  */
12
12
 
13
- import { handleConfirmInput } from '../panels/confirm-state.ts';
13
+ import { handleConfirmInput } from '../views/confirm-state.ts';
14
14
  import type { FlagEntry, McpEntry, SettingEntry, SubscriptionEntry } from './settings-modal-types.ts';
15
15
  import { buildMcpEntries, buildSubscriptionEntries } from './settings-modal-data.ts';
16
16
  import { modelPickerLaunchForKey } from './settings-modal-behavior.ts';
@@ -34,7 +34,7 @@ export interface ActivateSelectedContext {
34
34
  setEditBuffer(value: string): void;
35
35
  setMcpAllowAllConfirmationTarget(value: string | null): void;
36
36
  setSubscriptionLogoutConfirmationTarget(value: string | null): void;
37
- setPendingSettingsPickerAction(value: 'tts-provider' | 'tts-voice' | null): void;
37
+ setPendingSettingsPickerAction(value: 'tts-provider' | 'tts-voice' | 'theme' | null): void;
38
38
  setPendingModelPickerTarget(value: ModelPickerTarget | null): void;
39
39
  setPendingProviderModelPickerTarget(value: ModelPickerTarget | null): void;
40
40
  }
@@ -74,6 +74,11 @@ export function activateSelected(ctx: ActivateSelectedContext): void {
74
74
  ctx.setPendingSettingsPickerAction('tts-voice');
75
75
  return;
76
76
  }
77
+ // The theme row opens a picker with live preview instead of cycling values.
78
+ if (setting.key === 'display.theme') {
79
+ ctx.setPendingSettingsPickerAction('theme');
80
+ return;
81
+ }
77
82
 
78
83
  const pickerLaunch = modelPickerLaunchForKey(setting.key);
79
84
  if (pickerLaunch !== null) {
@@ -14,7 +14,7 @@ import type { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp';
14
14
  import { buildSubscriptionEntries } from './settings-modal-subscriptions.ts';
15
15
  import type { SubscriptionManager } from '@pellux/goodvibes-sdk/platform/config';
16
16
  import type { ServiceInspectionQuery } from '@/runtime/index.ts';
17
- import { CODE_INDEX_ENABLED_CONFIG_KEY } from '@pellux/goodvibes-sdk/platform/runtime/operations';
17
+ import { CODE_INDEX_ENABLED_CONFIG_KEY, NOTIFICATIONS_METADATA_ONLY_KEY } from '@pellux/goodvibes-sdk/platform/runtime/operations';
18
18
  import {
19
19
  WORKTREE_SETUP_CARRY_OVER_GLOBS_CONFIG_KEY,
20
20
  WORKTREE_SETUP_COMMANDS_CONFIG_KEY,
@@ -167,7 +167,7 @@ export function buildSettingGroups(
167
167
 
168
168
  // Synthetic behavior.budgetAlertUsd (TUI-local): no schema surface previously,
169
169
  // never in /config; /settings-sync show rejects any key not
170
- // in CONFIG_KEYS. The Cost panel's 'b' key and /cost budget <usd> remain
170
+ // in CONFIG_KEYS. The Usage modal's 'b' key and /cost budget <usd> remain
171
171
  // the primary way to change it; this entry makes the current effective
172
172
  // value (and whether it's still the "no budget configured" default)
173
173
  // visible from /config behavior too, same rationale as notifyAfterSeconds.
@@ -190,6 +190,11 @@ export function buildSettingGroups(
190
190
  behaviorEntries.push(entry);
191
191
  }
192
192
  }
193
+ // behavior.notificationsMetadataOnly is a real SDK schema key (the loop
194
+ // above already added it); it governs the text of every notification row
195
+ // above (desktop, in-terminal, webhook), so it is listed right after them.
196
+ const privacyIndex = behaviorEntries.findIndex((e) => e.setting.key === (NOTIFICATIONS_METADATA_ONLY_KEY as ConfigKey));
197
+ if (privacyIndex >= 0) behaviorEntries.push(...behaviorEntries.splice(privacyIndex, 1));
193
198
  }
194
199
 
195
200
  // Inject the storage.codeIndexEnabled toggle into the
@@ -331,20 +336,20 @@ export function buildNotifyAfterSecondsSyntheticEntry(configManager: Pick<Config
331
336
  *
332
337
  * 0 = no budget configured (disabled). Any positive number = the USD
333
338
  * threshold. Default matches BUDGET_ALERT_USD_DEFAULT in cost-utils.ts, the
334
- * single source of truth CostTrackerPanel and budget-breach-notifier.ts share.
339
+ * single source of truth the Usage modal and budget-breach-notifier.ts share.
335
340
  */
336
341
  export const BUDGET_ALERT_USD_SYNTHETIC_SETTING: ConfigSetting = {
337
342
  key: BUDGET_ALERT_USD_CONFIG_KEY as ConfigKey,
338
343
  type: 'number',
339
344
  default: BUDGET_ALERT_USD_DEFAULT,
340
- description: 'Session cost-budget alert threshold in USD (0 = no budget configured). Set via the Cost panel\'s "b" key or /cost budget <usd>; this entry only displays the current effective value.',
345
+ description: 'Session cost-budget alert threshold in USD (0 = no budget configured). Set via the Usage modal\'s "b" key or /cost budget <usd>; this entry only displays the current effective value.',
341
346
  };
342
347
 
343
348
  /**
344
349
  * Build the synthetic SettingEntry for behavior.budgetAlertUsd.
345
350
  *
346
351
  * Delegates parsing/fallback to readBudgetAlertUsd (cost-utils.ts) so this
347
- * display entry can never disagree with what CostTrackerPanel and the
352
+ * display entry can never disagree with what the Usage modal and the
348
353
  * background budget-breach notifier actually read.
349
354
  */
350
355
  export function buildBudgetAlertUsdSyntheticEntry(configManager: Pick<ConfigManager, 'get'>): SettingEntry {
@@ -370,7 +375,7 @@ export function buildBudgetAlertUsdSyntheticEntry(configManager: Pick<ConfigMana
370
375
  const NOTIFY_ALERT_SYNTHETIC_SETTINGS: ReadonlyArray<{ readonly key: string; readonly description: string }> = [
371
376
  {
372
377
  key: 'behavior.notifyOnBudgetBreach',
373
- description: 'Alert when session cost crosses the configured budget (set via the Cost panel\'s "b" key).',
378
+ description: 'Alert when session cost crosses the configured budget (set via the Usage modal\'s "b" key).',
374
379
  },
375
380
  {
376
381
  key: 'behavior.notifyOnAgentFailure',
@@ -93,7 +93,7 @@ export type SettingsCategory =
93
93
  | 'google'
94
94
  | 'danger';
95
95
 
96
- export type SettingsFocusPane = 'categories' | 'settings';
96
+ export type SettingsFocusRegion = 'categories' | 'settings';
97
97
 
98
98
  export const SETTINGS_CATEGORY_GROUPS: ReadonlyArray<{
99
99
  readonly label: string;
@@ -37,7 +37,7 @@ import {
37
37
  type McpEntry,
38
38
  type SettingEntry,
39
39
  type SettingsCategory,
40
- type SettingsFocusPane,
40
+ type SettingsFocusRegion,
41
41
  type SubscriptionEntry,
42
42
  } from './settings-modal-types.ts';
43
43
  import {
@@ -113,7 +113,7 @@ export {
113
113
  type McpEntry,
114
114
  type SettingEntry,
115
115
  type SettingsCategory,
116
- type SettingsFocusPane,
116
+ type SettingsFocusRegion,
117
117
  type SubscriptionEntry,
118
118
  } from './settings-modal-types.ts';
119
119
 
@@ -130,8 +130,8 @@ export class SettingsModal {
130
130
  /** Selected setting index within the current category. */
131
131
  public selectedIndex = 0;
132
132
 
133
- /** Which pane receives up/down navigation and Enter/Space actions. */
134
- public focusPane: SettingsFocusPane = 'settings';
133
+ /** Which region (settings list or documentation) receives up/down navigation and Enter/Space actions. */
134
+ public focusRegion: SettingsFocusRegion = 'settings';
135
135
 
136
136
  /** Whether we're in inline edit mode for the selected string/number setting. */
137
137
  public editingMode = false;
@@ -139,8 +139,8 @@ export class SettingsModal {
139
139
  /** Current value of the inline edit buffer. */
140
140
  public editBuffer = '';
141
141
  /**
142
- * Scroll offset (in wrapped lines) of the documentation pane for the
143
- * selected row. The renderer clamps it to the pane's real content height
142
+ * Scroll offset (in wrapped lines) of the documentation region for the
143
+ * selected row. The renderer clamps it to the region's real content height
144
144
  * and shows honest more-above/below markers, long feature documentation
145
145
  * scrolls, it never clips. Reset whenever the selection changes.
146
146
  */
@@ -156,7 +156,7 @@ export class SettingsModal {
156
156
  /** Set when the highlighted setting should open provider selection before model selection. */
157
157
  public pendingProviderModelPickerTarget: ModelPickerTarget | null = null;
158
158
  /** Set when a highlighted setting needs an external picker owned by the shell route. */
159
- public pendingSettingsPickerAction: 'tts-provider' | 'tts-voice' | null = null;
159
+ public pendingSettingsPickerAction: 'tts-provider' | 'tts-voice' | 'theme' | null = null;
160
160
  /** Provider awaiting explicit logout confirmation, if any. */
161
161
  public subscriptionLogoutConfirmationTarget: string | null = null;
162
162
 
@@ -251,7 +251,7 @@ export class SettingsModal {
251
251
  this.connectionEntries = initialConnectionEntries();
252
252
  this.categoryIndex = 0;
253
253
  this.selectedIndex = 0;
254
- this.focusPane = 'categories';
254
+ this.focusRegion = 'categories';
255
255
  this.editingMode = false;
256
256
  this.editBuffer = '';
257
257
  this.pendingModelPickerTarget = null;
@@ -268,7 +268,7 @@ export class SettingsModal {
268
268
  this.active = true;
269
269
  }
270
270
 
271
- /** Scroll the documentation pane by `delta` wrapped lines (renderer clamps the top end). */
271
+ /** Scroll the documentation region by `delta` wrapped lines (renderer clamps the top end). */
272
272
  scrollContext(delta: number): void {
273
273
  this.contextScroll = Math.max(0, this.contextScroll + delta);
274
274
  }
@@ -296,7 +296,7 @@ export class SettingsModal {
296
296
  this.serviceRegistry = null;
297
297
  this.secretsManager = null;
298
298
  this.onSettingApplied = null;
299
- this.focusPane = 'settings';
299
+ this.focusRegion = 'settings';
300
300
  }
301
301
 
302
302
  /** Enter search mode (focus the search input bar). */
@@ -355,26 +355,26 @@ export class SettingsModal {
355
355
 
356
356
  focusCategories(): void {
357
357
  if (this.editingMode) return;
358
- this.focusPane = 'categories';
358
+ this.focusRegion = 'categories';
359
359
  }
360
360
 
361
361
  focusSettings(): void {
362
362
  if (this.editingMode) return;
363
- this.focusPane = 'settings';
363
+ this.focusRegion = 'settings';
364
364
  }
365
365
 
366
- toggleFocusPane(): void {
366
+ toggleFocusRegion(): void {
367
367
  if (this.editingMode) return;
368
- this.focusPane = this.focusPane === 'settings' ? 'categories' : 'settings';
368
+ this.focusRegion = this.focusRegion === 'settings' ? 'categories' : 'settings';
369
369
  }
370
370
 
371
371
  moveFocusedUp(): void {
372
- if (this.focusPane === 'categories') this.prevCategory();
372
+ if (this.focusRegion === 'categories') this.prevCategory();
373
373
  else this.moveUp();
374
374
  }
375
375
 
376
376
  moveFocusedDown(): void {
377
- if (this.focusPane === 'categories') this.nextCategory();
377
+ if (this.focusRegion === 'categories') this.nextCategory();
378
378
  else this.moveDown();
379
379
  }
380
380
 
@@ -476,7 +476,7 @@ export class SettingsModal {
476
476
  if (categoryIndex >= 0) {
477
477
  this.categoryIndex = categoryIndex;
478
478
  this.selectedIndex = 0;
479
- this.focusPane = 'settings';
479
+ this.focusRegion = 'settings';
480
480
  return;
481
481
  }
482
482
 
@@ -487,7 +487,7 @@ export class SettingsModal {
487
487
  if (entryIndex >= 0) {
488
488
  this.categoryIndex = index;
489
489
  this.selectedIndex = entryIndex;
490
- this.focusPane = 'settings';
490
+ this.focusRegion = 'settings';
491
491
  return;
492
492
  }
493
493
  }
@@ -709,7 +709,7 @@ export class SettingsModal {
709
709
  // has already applied the change to the gate manager (live flip for
710
710
  // runtime-toggleable features, pending-restart marker for startup-gated
711
711
  // ones). Re-read the manager for every header bound to this key so the
712
- // rows and the context pane show the honest state at the point of change.
712
+ // rows and the context region show the honest state at the point of change.
713
713
  this._syncFeatureHeadersForKey(key);
714
714
 
715
715
  if (result.restartDomain !== null) {
@@ -721,7 +721,7 @@ export class SettingsModal {
721
721
  // No-op (result.changed === false, effectMessage === null): leave lastSettingEffectMessage untouched.
722
722
 
723
723
  // Selecting 'prompt' for payments.cvvHandling states the tradeoff at the
724
- // moment of selection, not just in the documentation pane, see the
724
+ // moment of selection, not just in the documentation region, see the
725
725
  // SDK's own CVV_PROMPT_TRADEOFF_WARNING (platform/payments).
726
726
  if (key === PAYMENTS_CVV_HANDLING_CONFIG_KEY && value === 'prompt') {
727
727
  this.lastSettingEffectMessage = CVV_PROMPT_TRADEOFF_WARNING;
@@ -5,7 +5,7 @@ export type SubmissionIntentKind =
5
5
  | 'orchestration'
6
6
  | 'plan'
7
7
  | 'review'
8
- | 'panel-action'
8
+ | 'view-action'
9
9
  | 'shell'
10
10
  | 'memory-pin';
11
11
 
@@ -3,7 +3,6 @@ import type { SubmissionIntent } from './submission-intent.ts';
3
3
  export interface SubmissionRouterInput {
4
4
  readonly text: string;
5
5
  readonly commandMode?: boolean;
6
- readonly panelFocused?: boolean;
7
6
  readonly hasAttachments?: boolean;
8
7
  }
9
8
 
@@ -12,7 +11,7 @@ export interface SubmissionRouterInput {
12
11
  // 'plan' composer intent/label. The /plan mode-toggle is a plain slash command.
13
12
  const PLAN_COMMANDS = new Set(['project-plan', 'planning']);
14
13
  const REVIEW_COMMANDS = new Set(['review']);
15
- const PANEL_COMMANDS = new Set(['panel']);
14
+ const LEGACY_PANEL_COMMANDS = new Set(['panel']);
16
15
  const ORCHESTRATION_COMMANDS = new Set([
17
16
  'wrfc',
18
17
  'orchestration',
@@ -30,11 +29,7 @@ export function routeSubmissionIntent(input: SubmissionRouterInput): SubmissionI
30
29
  const hasAttachments = input.hasAttachments ?? false;
31
30
 
32
31
  if (!trimmed) {
33
- return {
34
- kind: input.panelFocused ? 'panel-action' : 'empty',
35
- label: input.panelFocused ? 'panel action' : 'prompt',
36
- hasAttachments,
37
- };
32
+ return { kind: 'empty', label: 'prompt', hasAttachments };
38
33
  }
39
34
 
40
35
  if (trimmed.startsWith('!#')) {
@@ -53,8 +48,8 @@ export function routeSubmissionIntent(input: SubmissionRouterInput): SubmissionI
53
48
  if (REVIEW_COMMANDS.has(commandName)) {
54
49
  return { kind: 'review', label: 'review', commandName, hasAttachments };
55
50
  }
56
- if (PANEL_COMMANDS.has(commandName)) {
57
- return { kind: 'panel-action', label: 'panel action', commandName, hasAttachments };
51
+ if (LEGACY_PANEL_COMMANDS.has(commandName)) {
52
+ return { kind: 'view-action', label: 'open view', commandName, hasAttachments };
58
53
  }
59
54
  if (ORCHESTRATION_COMMANDS.has(commandName)) {
60
55
  return { kind: 'orchestration', label: 'orchestration', commandName, hasAttachments };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * surface-modal-host.ts, a stack of self-contained kit modals.
3
+ *
4
+ * A SurfaceModal owns its keys, its own sub-levels and its rendering (drawn
5
+ * with the surface kit). The host keeps them in a stack on the InputHandler:
6
+ *
7
+ * - Keys go to the top modal only (focus always belongs to the top modal).
8
+ * - Esc pops exactly one level: the top modal's own sub-level first
9
+ * (escape() returns true), otherwise the top modal closes. The next Esc
10
+ * reaches the modal underneath, then the rest of the Esc chain
11
+ * (handler-modal-stack.ts: close modal, clear composer, cancel turn).
12
+ * - Every modal in the stack renders, bottom to top; each one dims what is
13
+ * underneath it.
14
+ *
15
+ * The command palette and the confirm dialog are SurfaceModals; any new modal
16
+ * can be one without touching the handler plumbing again.
17
+ */
18
+
19
+ import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
20
+ import type { SurfaceLayer } from '../renderer/surface-kit.ts';
21
+
22
+ export type SurfaceModalCloseReason = 'escape' | 'done' | 'cleared';
23
+
24
+ export interface SurfaceModal {
25
+ /** A short identifier (tests, diagnostics). */
26
+ readonly name: string;
27
+ /** Handle a key or text token while this modal is on top. */
28
+ handleToken(token: InputToken, host: SurfaceModalHost): void;
29
+ /** Esc: pop one of this modal's own sub-levels and return true, or return false to close the modal. */
30
+ escape?(): boolean;
31
+ /** Draw the modal for a screen of the given size. */
32
+ render(screenWidth: number, screenHeight: number): SurfaceLayer;
33
+ /** Called once when the modal leaves the stack. */
34
+ onClose?(reason: SurfaceModalCloseReason): void;
35
+ }
36
+
37
+ export class SurfaceModalHost {
38
+ private readonly stack: SurfaceModal[] = [];
39
+
40
+ /** Called after the stack changes (wired to the render request). */
41
+ onChange: (() => void) | null = null;
42
+
43
+ get active(): boolean {
44
+ return this.stack.length > 0;
45
+ }
46
+
47
+ get depth(): number {
48
+ return this.stack.length;
49
+ }
50
+
51
+ top(): SurfaceModal | undefined {
52
+ return this.stack[this.stack.length - 1];
53
+ }
54
+
55
+ /** The open modals, bottom first. */
56
+ modals(): readonly SurfaceModal[] {
57
+ return this.stack;
58
+ }
59
+
60
+ /** Open a modal on top of whatever is open. Opening one already in the stack moves it to the top. */
61
+ push(modal: SurfaceModal): void {
62
+ const existing = this.stack.indexOf(modal);
63
+ if (existing >= 0) this.stack.splice(existing, 1);
64
+ this.stack.push(modal);
65
+ this.onChange?.();
66
+ }
67
+
68
+ /** Remove a modal (usually the top one closing itself after acting). */
69
+ close(modal: SurfaceModal, reason: SurfaceModalCloseReason = 'done'): void {
70
+ const index = this.stack.indexOf(modal);
71
+ if (index < 0) return;
72
+ this.stack.splice(index, 1);
73
+ modal.onClose?.(reason);
74
+ this.onChange?.();
75
+ }
76
+
77
+ /** Esc: one level. Returns false when nothing was open. */
78
+ escape(): boolean {
79
+ const top = this.top();
80
+ if (!top) return false;
81
+ if (top.escape?.() === true) {
82
+ this.onChange?.();
83
+ return true;
84
+ }
85
+ this.close(top, 'escape');
86
+ return true;
87
+ }
88
+
89
+ /** Route a token to the top modal. Returns false when nothing was open. */
90
+ handleToken(token: InputToken): boolean {
91
+ const top = this.top();
92
+ if (!top) return false;
93
+ top.handleToken(token, this);
94
+ return true;
95
+ }
96
+
97
+ /** Close everything (e.g. when the whole modal stack is cleared). */
98
+ clear(): void {
99
+ while (this.stack.length > 0) {
100
+ const modal = this.stack.pop()!;
101
+ modal.onClose?.('cleared');
102
+ }
103
+ this.onChange?.();
104
+ }
105
+
106
+ /** Layers for the compositor, bottom first. */
107
+ render(screenWidth: number, screenHeight: number): SurfaceLayer[] {
108
+ return this.stack.map((modal) => modal.render(screenWidth, screenHeight));
109
+ }
110
+ }