@pellux/goodvibes-tui 2.0.20 → 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 +70 -0
  2. package/README.md +16 -16
  3. package/docs/README.md +4 -4
  4. package/docs/foundation-artifacts/operator-contract.json +59 -4
  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
@@ -6,7 +6,7 @@ import type {
6
6
  ConfigModalTab,
7
7
  ConfigModalView,
8
8
  } from './config-modal-types.ts';
9
- import type { ModalSectionStyle } from '../renderer/modal-factory.ts';
9
+ import type { ModalSectionStyle } from './config-modal-types.ts';
10
10
  import { truncateDisplay, wrapText } from '../utils/terminal-width.ts';
11
11
 
12
12
  export type {
@@ -35,6 +35,8 @@ export interface ConfigModalRenderRow {
35
35
  /** True when this frozen row has no live counterpart this tick (value went
36
36
  * stale, kept in place, dimmed, until the next interaction boundary). */
37
37
  readonly stale: boolean;
38
+ /** A group header row (see ConfigModalRow.header). */
39
+ readonly header: boolean;
38
40
  }
39
41
 
40
42
  /** Everything renderConfigModal needs, computed by overlaying live values onto
@@ -47,18 +49,20 @@ export interface ConfigModalRenderModel {
47
49
  readonly emptyText?: string;
48
50
  readonly degraded?: string;
49
51
  readonly status?: string;
52
+ /** True while a destructive action waits for its second key press. */
53
+ readonly confirmPending: boolean;
54
+ /** Surface, tab and action hints as "key action" strings (the renderer draws keycaps). */
50
55
  readonly hints: readonly string[];
51
56
  readonly scroll: { readonly offset: number; readonly total: number; readonly visible: number };
57
+ /** The always-live search row: the query and a truthful match count over the active tab's selectable rows. */
58
+ readonly search: { readonly query: string; readonly matched: number; readonly total: number };
52
59
  }
53
60
 
54
61
  const DEFAULT_VISIBLE_ROWS = 10;
55
62
 
56
63
  /**
57
64
  * Default label wrap width for getRenderModel() callers that don't pass one
58
- * (tests, mainly). Matches the renderer's real list-item wrap width at the
59
- * common terminal sizes this codebase tests against: default box width 76,
60
- * minus the 4-column text margin and 2-column selection indicator (see
61
- * ModalFactory._renderListSection's `contentW - 2`). renderConfigModal()
65
+ * (tests, mainly). renderConfigModal()
62
66
  * itself always computes and passes the ACTUAL width for the current
63
67
  * terminal size, this constant only matters when getRenderModel() is
64
68
  * called directly without going through the renderer.
@@ -97,31 +101,15 @@ export class ConfigModal {
97
101
  private pendingConfirmKey: string | null = null;
98
102
 
99
103
  /**
100
- * item 1, the host's own type-to-filter, armed with '/' (matching
101
- * the pre-existing "'/' to filter" convention: scrollable-list-panel.ts's
102
- * opt-in filter, and SettingsModal's own '/'-armed search). `filterActive`
103
- * gates whether printable keys go to the query instead of nav/actions;
104
- * `filterQuery` is the TEXT-CAPTURE buffer itself (a multi-char paste token
105
- * appends in one shot, see handleConfigModalToken). Filtering only ever
106
- * narrows the ACTIVE tab's rows and is reset on tab switch (a query typed
107
- * against one tab's rows has no defined meaning against another's).
108
- *
109
- * FILTER-CONVENTION RULING (batch integration, where '/'-armed vs instant
110
- * filtering is decided across the TUI's list UIs):
111
- * - '/'-ARMED here (config-modal host surfaces): these surfaces bind PLAIN
112
- * single keys to ACTION HOTKEYS (e.g. 'r' refresh, 'd' delete). A key
113
- * can't be both an action and a filter character, so filtering must be
114
- * explicitly armed with '/' first. This is a deliberate design choice.
115
- * - INSTANT filtering (pure pickers, help overlay, command palette,
116
- * selection-modal): these have no single-key actions, so every printable
117
- * key is unambiguously a filter character and narrows the list on the
118
- * first keystroke. This is a deliberate design choice (selection-modal-overlay.ts).
119
- * - Both keep single-Esc-close semantics. The only extra step is here: a
120
- * two-stage Esc (first Esc clears a NON-EMPTY query, second Esc closes)
121
- * , see the Esc branch in handleConfigModalToken. With an empty query,
122
- * one Esc closes, exactly like the instant pickers.
104
+ * The always-live search row. Every printable key the modal receives is
105
+ * typed here, except a key the surface claims as an action, which fires
106
+ * the action while the query is empty (handleConfigModalToken). The query
107
+ * is the TEXT-CAPTURE buffer itself (a multi-char paste token appends in
108
+ * one shot). Filtering only ever narrows the ACTIVE tab's rows and is reset
109
+ * on tab switch (a query typed against one tab's rows has no defined
110
+ * meaning against another's). Esc never clears it as a separate step: Esc
111
+ * cancels an armed destructive confirm first, otherwise it closes.
123
112
  */
124
- private filterActive = false;
125
113
  private filterQuery = '';
126
114
 
127
115
  /**
@@ -131,6 +119,8 @@ export class ConfigModal {
131
119
  private frozenView: ConfigModalView | null = null;
132
120
 
133
121
  private scrollOffset = 0;
122
+ /** The label wrap width of the last render, so scrolling can count wrapped lines. */
123
+ private lastWrapWidth = DEFAULT_LABEL_WRAP_WIDTH;
134
124
  private visibleRows = DEFAULT_VISIBLE_ROWS;
135
125
 
136
126
  /**
@@ -153,7 +143,6 @@ export class ConfigModal {
153
143
  this.statusMessage = '';
154
144
  this.pendingConfirmKey = null;
155
145
  this.scrollOffset = 0;
156
- this.filterActive = false;
157
146
  this.filterQuery = '';
158
147
  this.interactedSinceOpen = false;
159
148
  const view = surface.buildView();
@@ -171,7 +160,6 @@ export class ConfigModal {
171
160
  this.frozenView = null;
172
161
  this.statusMessage = '';
173
162
  this.pendingConfirmKey = null;
174
- this.filterActive = false;
175
163
  this.filterQuery = '';
176
164
  }
177
165
 
@@ -221,55 +209,48 @@ export class ConfigModal {
221
209
  this._clampScroll();
222
210
  }
223
211
 
224
- // ── Filter (item 1, each mutation is an interaction boundary) ──────
212
+ // ── Search row (each mutation is an interaction boundary) ─────────────────
225
213
 
214
+ /** True while the search row holds a query. */
226
215
  isFilterActive(): boolean {
227
- return this.filterActive;
216
+ return this.filterQuery.length > 0;
228
217
  }
229
218
 
230
219
  getFilterQuery(): string {
231
220
  return this.filterQuery;
232
221
  }
233
222
 
234
- /** Arm the filter (handleConfigModalToken calls this on '/'). Idempotent. */
235
- activateFilter(): void {
236
- if (this.filterActive) return;
237
- this._clearConfirm();
238
- this.filterActive = true;
239
- this.syncStructure();
240
- }
241
-
242
223
  /**
243
224
  * Append text to the query, the WHOLE token value in one call, so a
244
225
  * multi-char paste token lands in the filter atomically rather than being
245
- * split into per-char nav/action dispatch (the text-capture invariant this
246
- * item's brief calls out). A no-op when the filter isn't armed.
226
+ * split into per-char nav/action dispatch.
247
227
  */
248
228
  appendFilterText(text: string): void {
249
- if (!this.filterActive || text.length === 0) return;
229
+ if (text.length === 0) return;
230
+ this._clearConfirm();
250
231
  this.filterQuery += text;
251
232
  this.syncStructure();
252
233
  }
253
234
 
254
235
  backspaceFilter(): void {
255
- if (!this.filterActive || this.filterQuery.length === 0) return;
236
+ if (this.filterQuery.length === 0) return;
256
237
  this.filterQuery = this.filterQuery.slice(0, -1);
257
238
  this.syncStructure();
258
239
  }
259
240
 
241
+ /** True while a destructive action waits for its second key press. */
242
+ hasPendingConfirm(): boolean {
243
+ return this.pendingConfirmKey !== null;
244
+ }
245
+
260
246
  /**
261
- * Esc while filtering: a non-empty query is CLEARED (stays armed, ready to
262
- * retype without pressing '/' again) and this returns true so the caller
263
- * (handleConfigModalToken) stops here instead of closing the modal. An
264
- * empty query returns false, the caller falls through to the ordinary
265
- * close path, preserving single-Esc-close for the no-filter case (the one
266
- * documented exception: two-stage Esc only when there is something to
267
- * clear).
247
+ * Esc's first level inside the modal: an armed destructive confirm is a
248
+ * sub-state of the modal, so Esc cancels it (returns true) before a second
249
+ * Esc closes the modal. Returns false when nothing was armed.
268
250
  */
269
- clearFilterOrFallthrough(): boolean {
270
- if (!this.filterActive || this.filterQuery.length === 0) return false;
271
- this.filterQuery = '';
272
- this.syncStructure();
251
+ cancelPendingConfirm(): boolean {
252
+ if (this.pendingConfirmKey === null) return false;
253
+ this._clearConfirm();
273
254
  return true;
274
255
  }
275
256
 
@@ -279,7 +260,13 @@ export class ConfigModal {
279
260
  this._clearConfirm();
280
261
  this.syncStructure();
281
262
  const ids = this._selectableIds(this._frozenTab());
282
- if (ids.length === 0) return;
263
+ if (ids.length === 0) {
264
+ // A tab of informational rows only (pairing, planning): the arrows
265
+ // scroll it instead, so nothing below the fold is out of reach.
266
+ this.scrollOffset++;
267
+ this._clampScroll();
268
+ return;
269
+ }
283
270
  const i = ids.indexOf(this.selectedRowId);
284
271
  this.selectedRowId = ids[(i + 1) % ids.length]!;
285
272
  this._clampScroll();
@@ -289,7 +276,11 @@ export class ConfigModal {
289
276
  this._clearConfirm();
290
277
  this.syncStructure();
291
278
  const ids = this._selectableIds(this._frozenTab());
292
- if (ids.length === 0) return;
279
+ if (ids.length === 0) {
280
+ this.scrollOffset = Math.max(0, this.scrollOffset - 1);
281
+ this._clampScroll();
282
+ return;
283
+ }
293
284
  const i = ids.indexOf(this.selectedRowId);
294
285
  this.selectedRowId = ids[(i - 1 + ids.length) % ids.length]!;
295
286
  this._clampScroll();
@@ -306,8 +297,7 @@ export class ConfigModal {
306
297
  private _switchTab(dir: 1 | -1): void {
307
298
  this._clearConfirm();
308
299
  // A filter query is scoped to the tab it was typed against, switching
309
- // tabs resets it (item 1), same as statusMessage below.
310
- this.filterActive = false;
300
+ // tabs resets it, same as statusMessage below.
311
301
  this.filterQuery = '';
312
302
  this.syncStructure();
313
303
  const tabs = this.frozenView?.tabs ?? [];
@@ -376,7 +366,6 @@ export class ConfigModal {
376
366
  */
377
367
  jumpToRow(tabId: string, rowId: string): void {
378
368
  this._clearConfirm();
379
- this.filterActive = false;
380
369
  this.filterQuery = '';
381
370
  this.syncStructure();
382
371
  const tab = this.frozenView?.tabs.find((t) => t.id === tabId);
@@ -418,9 +407,10 @@ export class ConfigModal {
418
407
  * does NOT sync structure, so calling it repeatedly with only value mutations
419
408
  * yields byte-identical layout (the liveness contract).
420
409
  *
421
- * `labelWrapWidth` is the wrap column ModalFactory's list section will use
422
- * for row labels (renderConfigModal computes and passes the real one for
423
- * the current terminal size). It exists so the wrap-clamp below (* item 2) can pre-empt a live label growing past ModalFactory's wrap width
410
+ * `labelWrapWidth` is the wrap column the kit list uses for row labels
411
+ * (renderConfigModal computes and passes the real one for the current
412
+ * terminal size). It exists so the wrap-clamp below can pre-empt a live
413
+ * label growing past that wrap width
424
414
  * mid-tick, a structural change (an extra visible line) without an
425
415
  * interaction, by measuring wrapped line counts here, before the label
426
416
  * ever reaches the renderer.
@@ -429,11 +419,12 @@ export class ConfigModal {
429
419
  // Pre-first-interaction: async onOpen loads may restructure freely (the
430
420
  // user hasn't engaged a cursor yet), sync so "Loading…" is replaced by
431
421
  // real content on the load's own requestRender, not the next keypress.
422
+ this.lastWrapWidth = labelWrapWidth;
432
423
  if (this.active && !this.interactedSinceOpen) this.syncStructure();
433
424
  const live = this.surface?.buildView() ?? null;
434
425
  const frozen = this.frozenView;
435
426
  if (!frozen) {
436
- return { title: '', tabs: [], header: [], rows: [], hints: [], scroll: { offset: 0, total: 0, visible: this.visibleRows } };
427
+ return { title: '', tabs: [], header: [], rows: [], hints: [], confirmPending: false, scroll: { offset: 0, total: 0, visible: this.visibleRows }, search: { query: this.filterQuery, matched: 0, total: 0 } };
437
428
  }
438
429
 
439
430
  const tabs: ConfigModalRenderTab[] = frozen.tabs.map((ft) => {
@@ -461,35 +452,25 @@ export class ConfigModal {
461
452
  label: this._clampRowLabel(fr.label, src.label, labelWrapWidth),
462
453
  style: src.style,
463
454
  selected: fr.id === this.selectedRowId,
464
- selectable: fr.selectable !== false,
455
+ selectable: fr.selectable !== false && fr.header !== true,
465
456
  stale: lr === undefined,
457
+ header: fr.header === true,
466
458
  };
467
459
  });
468
460
 
469
461
  const visible = this.visibleRows;
470
462
  const windowed = this._windowRowsByLineBudget(allRows, this.scrollOffset, visible, labelWrapWidth);
471
463
 
472
- // item 1: while filtering, the surface's own action/tab hints are
473
- // unreliable (their printable-letter keys are captured by the filter
474
- // instead of firing, see handleConfigModalToken), so the footer swaps
475
- // to just the filter status (query + a truthful match count) and the
476
- // Esc contract for this mode. See getFilterQuery/isFilterActive callers.
477
- const filtering = this.filterActive;
478
- const hasQuery = filtering && this.filterQuery.length > 0;
479
- const hints = filtering
480
- ? [
481
- this._filterStatusHint(
482
- frozenRows.filter((r) => r.selectable !== false).length,
483
- (liveTab?.rows ?? []).filter((r) => r.selectable !== false).length,
484
- ),
485
- hasQuery ? 'Esc clear · Esc close' : 'Esc close',
486
- ]
487
- : [
488
- ...(frozen.hints ?? []),
489
- ...(frozenTab?.hints ?? []),
490
- ...this._actionHints(),
491
- 'Esc close',
492
- ];
464
+ // The search row is always live, so the surface, tab and action hints
465
+ // always show; Esc sits on the title row as a keycap, not in the hints.
466
+ const hints = [
467
+ ...(frozen.hints ?? []),
468
+ ...(frozenTab?.hints ?? []),
469
+ ...this._actionHints(),
470
+ ];
471
+ const unfilteredTab = this.surface?.buildView().tabs.find((t) => t.id === this.activeTabId);
472
+ const countSelectable = (rows: readonly ConfigModalRow[] | undefined): number =>
473
+ (rows ?? []).filter((r) => r.selectable !== false && r.header !== true).length;
493
474
 
494
475
  return {
495
476
  title: live?.title ?? frozen.title,
@@ -499,8 +480,14 @@ export class ConfigModal {
499
480
  emptyText: frozenRows.length === 0 ? (frozenTab?.emptyText ?? 'Nothing to show.') : undefined,
500
481
  degraded: live?.degraded ?? frozen.degraded,
501
482
  status: this.statusMessage || undefined,
483
+ confirmPending: this.pendingConfirmKey !== null,
502
484
  hints,
503
485
  scroll: { offset: this.scrollOffset, total: allRows.length, visible },
486
+ search: {
487
+ query: this.filterQuery,
488
+ matched: countSelectable(frozenRows),
489
+ total: countSelectable(unfilteredTab?.rows),
490
+ },
504
491
  };
505
492
  }
506
493
 
@@ -534,9 +521,9 @@ export class ConfigModal {
534
521
  * Modal sizing rule (owner, zero tolerance: a modal/list must never clip
535
522
  * its full descriptive text, size to content or scroll, never clip).
536
523
  * `scrollOffset`/`visible` are ROW-count based, which is exactly right when
537
- * every row wraps to one line, but ModalFactory renders each row's WRAPPED
538
- * lines and then bounds the whole section to a fixed content-row budget
539
- * computed from `visible` (see renderConfigModal's targetContentRows). A
524
+ * every row wraps to one line, but the kit list renders each row's WRAPPED
525
+ * lines inside a fixed list height (renderConfigModal passes that height
526
+ * to setViewportRows). A
540
527
  * row whose label wraps to MULTIPLE lines can therefore push the total past
541
528
  * that budget, and the LAST row handed to the renderer gets cut off
542
529
  * mid-line instead of being deferred to the next scroll page, the
@@ -568,16 +555,9 @@ export class ConfigModal {
568
555
  return out;
569
556
  }
570
557
 
571
- /** Footer text for the active filter: the query + a truthful match count. */
572
- private _filterStatusHint(matched: number, total: number): string {
573
- const q = this.filterQuery;
574
- if (q.length === 0) return `/ type to filter (${total} row${total === 1 ? '' : 's'})`;
575
- return `/${q}: ${matched} of ${total} match${matched === 1 ? '' : 'es'}`;
576
- }
577
-
578
558
  /** Apply the active filter query to every tab's rows. A no-op passthrough when not filtering. */
579
559
  private _applyFilter(view: ConfigModalView): ConfigModalView {
580
- if (!this.filterActive || this.filterQuery === '') return view;
560
+ if (this.filterQuery === '') return view;
581
561
  const query = this.filterQuery.toLowerCase();
582
562
  return { ...view, tabs: view.tabs.map((tab) => this._filterTab(tab, query)) };
583
563
  }
@@ -593,9 +573,9 @@ export class ConfigModal {
593
573
  * describes "no data at all", not "no data matches your filter").
594
574
  */
595
575
  private _filterTab(tab: ConfigModalTab, query: string): ConfigModalTab {
596
- const kept = tab.rows.filter((r) => r.selectable === false || r.label.toLowerCase().includes(query));
597
- const hadSelectable = tab.rows.some((r) => r.selectable !== false);
598
- const stillMatched = kept.some((r) => r.selectable !== false);
576
+ const kept = tab.rows.filter((r) => r.selectable === false || r.header === true || r.label.toLowerCase().includes(query));
577
+ const hadSelectable = tab.rows.some((r) => r.selectable !== false && r.header !== true);
578
+ const stillMatched = kept.some((r) => r.selectable !== false && r.header !== true);
599
579
  if (hadSelectable && !stillMatched) {
600
580
  return {
601
581
  ...tab,
@@ -632,23 +612,44 @@ export class ConfigModal {
632
612
  }
633
613
 
634
614
  private _selectableIds(tab: ConfigModalTab | undefined): string[] {
635
- return (tab?.rows ?? []).filter((r) => r.selectable !== false).map((r) => r.id);
615
+ return (tab?.rows ?? []).filter((r) => r.selectable !== false && r.header !== true).map((r) => r.id);
636
616
  }
637
617
 
638
618
  private _firstSelectableId(tab: ConfigModalTab | undefined): string {
639
619
  return this._selectableIds(tab)[0] ?? '';
640
620
  }
641
621
 
622
+ /**
623
+ * Keep the selected row inside the visible line budget and never scroll
624
+ * past the point where the last rows fill the list. Counts wrapped lines
625
+ * (at the last render's wrap width), matching _windowRowsByLineBudget.
626
+ */
642
627
  private _clampScroll(): void {
643
628
  const tab = this._frozenTab();
644
629
  const rows = tab?.rows ?? [];
645
- const idx = rows.findIndex((r) => r.id === this.selectedRowId);
630
+ const lines = rows.map((r) => Math.max(1, wrapText(r.label, this.lastWrapWidth).length));
631
+ const span = (from: number, to: number): number => {
632
+ let n = 0;
633
+ for (let k = from; k <= to; k++) n += lines[k] ?? 0;
634
+ return n;
635
+ };
646
636
  const visible = Math.max(3, this.visibleRows);
637
+ const idx = rows.findIndex((r) => r.id === this.selectedRowId);
647
638
  if (idx >= 0) {
648
639
  if (idx < this.scrollOffset) this.scrollOffset = idx;
649
- else if (idx >= this.scrollOffset + visible) this.scrollOffset = idx - visible + 1;
640
+ while (this.scrollOffset < idx && span(this.scrollOffset, idx) > visible) this.scrollOffset++;
650
641
  }
651
- const maxOffset = Math.max(0, rows.length - visible);
642
+ let maxOffset = rows.length;
643
+ while (maxOffset > 0 && span(maxOffset - 1, rows.length - 1) <= visible) maxOffset--;
644
+ // Informational rows before the first or after the last selectable row
645
+ // would otherwise never scroll into view: selecting the first selectable
646
+ // row shows the rows above it, the last one shows the rows below it.
647
+ const selectable = rows.map((r, k) => (r.selectable !== false && r.header !== true ? k : -1)).filter((k) => k >= 0);
648
+ if (idx >= 0 && idx === selectable[selectable.length - 1]) {
649
+ this.scrollOffset = Math.max(this.scrollOffset, Math.min(maxOffset, idx));
650
+ }
651
+ if (idx >= 0 && idx === selectable[0] && span(0, idx) <= visible) this.scrollOffset = 0;
652
652
  this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxOffset));
653
653
  }
654
+
654
655
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * confirm-dialog-types.ts, the shapes the confirm dialog and its renderer
3
+ * share (kept apart so the renderer never imports the modal it draws).
4
+ */
5
+
6
+ export type ConfirmTone = 'danger' | 'warning' | 'primary';
7
+
8
+ export interface ConfirmOptions {
9
+ readonly title: string;
10
+ /** Wrapped in full; paragraphs split on '\n'. */
11
+ readonly body: string;
12
+ readonly confirmLabel: string;
13
+ readonly cancelLabel?: string;
14
+ /** danger: a red confirm chip; warning: amber; primary: the brand color. Default primary. */
15
+ readonly tone?: ConfirmTone;
16
+ /**
17
+ * A key chord that, pressed again while the dialog is open, confirms (the
18
+ * chord that opened it: ctrl+x twice stops an agent). Enter and y still work.
19
+ */
20
+ readonly confirmChord?: { readonly key: string; readonly ctrl?: boolean };
21
+ }
22
+
23
+ /** What the confirm renderer reads from the dialog. */
24
+ export interface ConfirmView {
25
+ readonly options: ConfirmOptions;
26
+ /** 0 = cancel (left), 1 = confirm (right). */
27
+ readonly chosen: 0 | 1;
28
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * confirm-dialog.ts, a small centered yes/no dialog on the kit surface.
3
+ *
4
+ * The safe choice sits on the left and is chosen by default for destructive
5
+ * and warning dialogs; the confirming button carries its tone (a red chip for
6
+ * destructive actions). Arrows or tab move between the buttons, Enter presses
7
+ * the chosen one, y and n answer directly, Esc cancels. An optional confirm
8
+ * chord (the chord that opened the dialog) pressed again also confirms.
9
+ */
10
+
11
+ import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
12
+ import type { SurfaceModal, SurfaceModalCloseReason, SurfaceModalHost } from './surface-modal-host.ts';
13
+ import type { SurfaceLayer } from '../renderer/surface-kit.ts';
14
+ import { renderConfirmDialog } from '../renderer/confirm-dialog.ts';
15
+ import type { ConfirmOptions, ConfirmView } from './confirm-dialog-types.ts';
16
+ export type { ConfirmOptions, ConfirmTone } from './confirm-dialog-types.ts';
17
+
18
+ export class ConfirmDialog implements SurfaceModal, ConfirmView {
19
+ readonly name = 'confirm';
20
+ /** 0 = cancel (left), 1 = confirm (right). */
21
+ chosen: 0 | 1;
22
+ private answered = false;
23
+
24
+ constructor(
25
+ readonly options: ConfirmOptions,
26
+ private readonly onResult: (confirmed: boolean) => void,
27
+ ) {
28
+ this.chosen = options.tone === 'danger' || options.tone === 'warning' ? 0 : 1;
29
+ }
30
+
31
+ private answer(host: SurfaceModalHost, confirmed: boolean): void {
32
+ this.answered = true;
33
+ host.close(this, 'done');
34
+ this.onResult(confirmed);
35
+ }
36
+
37
+ handleToken(token: InputToken, host: SurfaceModalHost): void {
38
+ if (token.type === 'text') {
39
+ const ch = token.value.toLowerCase();
40
+ if (ch === 'y') this.answer(host, true);
41
+ else if (ch === 'n') this.answer(host, false);
42
+ return;
43
+ }
44
+ if (token.type !== 'key') return;
45
+ const key = token.logicalName ?? '';
46
+ const chord = this.options.confirmChord;
47
+ if (chord && key === chord.key && Boolean(token.ctrl) === Boolean(chord.ctrl)) { this.answer(host, true); return; }
48
+ if (key === 'left' || key === 'right' || key === 'tab') this.chosen = this.chosen === 0 ? 1 : 0;
49
+ else if (key === 'enter') this.answer(host, this.chosen === 1);
50
+ else if (key === 'y') this.answer(host, true);
51
+ else if (key === 'n') this.answer(host, false);
52
+ }
53
+
54
+ onClose(_reason: SurfaceModalCloseReason): void {
55
+ // Esc or a cleared stack is a "no": the safe answer to an unanswered question.
56
+ if (!this.answered) {
57
+ this.answered = true;
58
+ this.onResult(false);
59
+ }
60
+ }
61
+
62
+ render(screenWidth: number, screenHeight: number): SurfaceLayer {
63
+ return renderConfirmDialog(this, screenWidth, screenHeight);
64
+ }
65
+ }
66
+
67
+ /** Ask through a host; resolves true only when the confirming button was pressed. */
68
+ export function confirmThrough(host: SurfaceModalHost, options: ConfirmOptions): Promise<boolean> {
69
+ return new Promise<boolean>((resolve) => host.push(new ConfirmDialog(options, resolve)));
70
+ }
@@ -7,11 +7,11 @@
7
7
  // 'delete' → forward-delete (the character after cursor) when a moveable
8
8
  // cursor exists; a no-op in cursorless/end-anchored contexts,
9
9
  // EXCEPT where it opens a confirmation-gated clear action
10
- // (see planning panel: Delete opens the ConfirmState gate that
10
+ // (see the planning modal: Delete opens the ConfirmState gate that
11
11
  // lets the user clear their entire draft answer).
12
12
  //
13
13
  // Consequences for each surface:
14
- // Panel search filters (end-anchored, no cursor)
14
+ // View search filters (end-anchored, no cursor)
15
15
  // 'backspace' → remove last char ✓
16
16
  // 'delete' → no-op ✓ (no cursor; nothing is "forward")
17
17
  //
@@ -19,7 +19,7 @@
19
19
  // 'backspace' → remove last char ✓
20
20
  // 'delete' → no-op ✓
21
21
  //
22
- // Planning panel draft answer (end-anchored, no cursor)
22
+ // Planning draft answer (end-anchored, no cursor)
23
23
  // 'backspace' → remove last char ✓
24
24
  // 'delete' → open ConfirmState gate (y/Enter confirms clear; n/Esc cancels)
25
25
  // The draft is NOT wiped until the user confirms.
@@ -24,6 +24,7 @@ import type { InputHistory, HistorySearch } from './input-history.ts';
24
24
  import type { ConversationManager } from '../core/conversation';
25
25
  import type { BlockActionsMenu } from '../renderer/block-actions.ts';
26
26
  import type { ContextInspectorModal } from '../renderer/context-inspector.ts';
27
+ import type { OverlayFilters } from './overlay-filter.ts';
27
28
  import type { BookmarkModal } from './bookmark-modal.ts';
28
29
  import type { SettingsModal } from './settings-modal.ts';
29
30
  import type { McpWorkspace } from './mcp-workspace.ts';
@@ -32,14 +33,11 @@ import type { ConfigModal } from './config-modal.ts';
32
33
  import type { ProfilePickerModal } from './profile-picker-modal.ts';
33
34
  import type { OnboardingWizardController } from './onboarding/onboarding-wizard.ts';
34
35
  import type { WrappedPromptInfo } from './handler-prompt-buffer.ts';
35
- import type { Panel } from '../panels/types.ts';
36
- import type { PanelManager } from '../panels/panel-manager.ts';
37
36
  import type { KeybindingsManager } from './keybindings.ts';
38
37
  import type { ModelPickerTarget } from './model-picker.ts';
39
38
  import type { KillRing } from './kill-ring.ts';
40
- import type { PanelMouseLayout } from './handler-feed-routes.ts';
41
39
  import type { FocusTracker } from '@pellux/goodvibes-sdk/platform/runtime/operations';
42
- import type { PanelBurstGuardState } from './panel-paste-flood-guard.ts';
40
+ import type { SurfaceModalHost } from './surface-modal-host.ts';
43
41
 
44
42
  /**
45
43
  * Initial mutable scalar values for InputFeedContext.
@@ -47,7 +45,7 @@ import type { PanelBurstGuardState } from './panel-paste-flood-guard.ts';
47
45
  * **Mutable fields** (synced per-feed via syncFeedContextMutableFields or inside
48
46
  * action closures that call syncFeedContextMutableFields):
49
47
  * - `prompt`, `cursorPos`, current text buffer state
50
- * - `commandMode`, `panelFocused`, `indicatorFocused`, focus-mode flags
48
+ * - `commandMode`, `indicatorFocused`, focus-mode flags
51
49
  * - `helpOverlayActive`, `helpScrollOffset`, help overlay state
52
50
  * - `shortcutsOverlayActive`, `shortcutsScrollOffset`, shortcuts overlay state
53
51
  * - `nextPasteId`, `nextImageId`, monotonically increasing ID counters
@@ -60,7 +58,6 @@ export interface FeedContextMutableInit {
60
58
  cursorPos: number;
61
59
  inputScrollTop: number;
62
60
  commandMode: boolean;
63
- panelFocused: boolean;
64
61
  indicatorFocused: boolean;
65
62
  helpOverlayActive: boolean;
66
63
  helpScrollOffset: number;
@@ -71,7 +68,6 @@ export interface FeedContextMutableInit {
71
68
  mouseDownRow: number;
72
69
  mouseDownCol: number;
73
70
  contentWidth: number;
74
- panelMouseLayout: PanelMouseLayout | null;
75
71
  selectionCallback: ((result: SelectionResult | null) => void) | null;
76
72
  }
77
73
 
@@ -84,7 +80,7 @@ export interface FeedContextMutableInit {
84
80
  * `profilePickerModal`, modal objects constructed once
85
81
  * - `filePicker`, `modelPicker`, `contextInspectorModal`, `blockActionsMenu`,
86
82
  * `searchManager`, `historySearch`, `onboardingWizard`, service objects constructed once
87
- * - `panelManager`, `keybindingsManager`, from uiServices, stable
83
+ * - `keybindingsManager`, from uiServices, stable
88
84
  * - `modalStack`, reference to the handler's shared array
89
85
  * - `getHistory`, `getViewportHeight`, `getScrollTop`, `scroll`, `exitApp`, callbacks
90
86
  * - `commandRegistry`, `commandContext`, `autocomplete`, `inputHistory`,
@@ -117,17 +113,18 @@ export interface FeedContextStableRefs {
117
113
  modelPicker: ModelPickerModal;
118
114
  onboardingWizard: OnboardingWizardController;
119
115
  contextInspectorModal: ContextInspectorModal;
116
+ /** Help / shortcuts overlay search rows (shared by reference with the renderer). */
117
+ overlayFilters: OverlayFilters;
120
118
  blockActionsMenu: BlockActionsMenu;
119
+ /** Kit modals (command palette, confirm dialog, ...), see surface-modal-host.ts. */
120
+ surfaceModals?: SurfaceModalHost;
121
121
  searchManager: SearchManager;
122
122
  modalStack: string[];
123
123
  inputHistory: InputHistory | null;
124
124
  conversationManager: ConversationManager | null;
125
- panelManager: PanelManager;
126
125
  keybindingsManager: KeybindingsManager;
127
126
  killRing: KillRing;
128
127
  focusTracker: FocusTracker;
129
- /** item 5, paste-flood guard state, mutated in place (never reallocated). */
130
- panelBurstGuard: PanelBurstGuardState;
131
128
  getHistory: () => InfiniteBuffer;
132
129
  getViewportHeight: () => number;
133
130
  getScrollTop: () => number;
@@ -138,6 +135,8 @@ export interface FeedContextStableRefs {
138
135
  /** Bound method closures for InputFeedContext. Built in handler.ts where private members are accessible. */
139
136
  export interface FeedContextClosures {
140
137
  modalOpened: (name: string) => void;
138
+ /** The agent or process view controls (null before the shell wires them). */
139
+ getSessionView: () => import('./handler-session-view-route.ts').SessionViewControls | null;
141
140
  handleEscape: () => void;
142
141
  /** Deliver a concealed submission; returns true when concealed mode consumed it. */
143
142
  submitConcealedInput: (value: string) => boolean;
@@ -158,11 +157,14 @@ export interface FeedContextClosures {
158
157
  ensureInputCursorVisible: (contentWidth?: number) => void;
159
158
  registerPaste: (content: string) => string;
160
159
  executeBlockAction: (id: string) => void;
161
- cyclePanelTab: (direction: 'next' | 'prev') => void;
162
- onPanelInputConsumed: (activePanel: Panel | null, key: string) => void;
163
160
  getWrappedPromptInfo: (contentWidth: number) => WrappedPromptInfo;
164
161
  moveCursorVertical: (direction: -1 | 1) => boolean;
165
162
  handlePathCompletion: () => boolean;
163
+ /** The view a clicked footer screen row opens (the usage rows open Usage), or undefined. */
164
+ footerTargetAt: (row: number) => import('../renderer/footer-targets.ts').FooterTarget | undefined;
165
+ openFooterTarget: (target: import('../renderer/footer-targets.ts').FooterTarget) => void;
166
+ /** Screen rows above the transcript (the header, the chips, the gap under them). */
167
+ bodyTopRow: () => number;
166
168
  handleBlockToggle: () => void;
167
169
  findMarkerAtPos: (pos: number) => { start: number; end: number } | null;
168
170
  cleanupMarkerRegistry: (text: string) => void;
@@ -213,7 +215,6 @@ export function buildInitialFeedContext(
213
215
  * - `prompt`, current prompt text buffer
214
216
  * - `cursorPos`, caret position within prompt
215
217
  * - `commandMode`, whether command-mode prefix is active
216
- * - `panelFocused`, whether the active panel owns keyboard focus
217
218
  * - `indicatorFocused`, whether the status indicator owns focus
218
219
  * - `helpOverlayActive` / `helpScrollOffset`, help overlay visibility and scroll
219
220
  * - `shortcutsOverlayActive` / `shortcutsScrollOffset`, shortcuts overlay state
@@ -248,7 +249,6 @@ export function syncFeedContextMutableFields(
248
249
  ctx.cursorPos = fields.cursorPos;
249
250
  ctx.inputScrollTop = fields.inputScrollTop;
250
251
  ctx.commandMode = fields.commandMode;
251
- ctx.panelFocused = fields.panelFocused;
252
252
  ctx.indicatorFocused = fields.indicatorFocused;
253
253
  ctx.helpOverlayActive = fields.helpOverlayActive;
254
254
  ctx.helpScrollOffset = fields.helpScrollOffset;
@@ -260,5 +260,4 @@ export function syncFeedContextMutableFields(
260
260
  ctx.mouseDownRow = fields.mouseDownRow;
261
261
  ctx.mouseDownCol = fields.mouseDownCol;
262
262
  ctx.contentWidth = fields.contentWidth;
263
- ctx.panelMouseLayout = fields.panelMouseLayout;
264
263
  }