@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
@@ -0,0 +1,360 @@
1
+ /**
2
+ * surface-kit-parts.ts, the smaller pieces of the modal surface kit:
3
+ *
4
+ * - element insets (a lighter fill inside a modal: 2 columns of side padding,
5
+ * a padding row above and below)
6
+ * - tabs (the active tab carries the gradient)
7
+ * - button chips (arrows pick, Enter confirms; the danger chip is red)
8
+ * - meters (▰▱)
9
+ * - toasts (top right, a colored ┃ on both sides, wrapped text)
10
+ * - composer popups (the slash and @ lists: anchored to the composer, no dim,
11
+ * ┃ bar, surface fill, selected row gradient, no frame and no hint rows)
12
+ *
13
+ * Colors come only from the active theme's tokens.
14
+ */
15
+
16
+ import type { Line } from '@pellux/goodvibes-sdk/platform/types';
17
+ import { getDisplayWidth } from '../utils/terminal-width.ts';
18
+ import { activeTokens } from './theme.ts';
19
+ import {
20
+ NARROW_MODAL_BREAKPOINT,
21
+ SurfaceCanvas,
22
+ clipText,
23
+ type KitHint,
24
+ wrapLines,
25
+ type KitStyle,
26
+ type SurfaceLayer,
27
+ } from './surface-kit.ts';
28
+ import { drawList, drawRow, drawScrollingList, measureRow, type KitListResult, type KitRow } from './surface-kit-list.ts';
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Element insets
32
+ // ---------------------------------------------------------------------------
33
+
34
+ /** Side padding inside an inset. */
35
+ const INSET_PAD_X = 2;
36
+
37
+ export interface KitInset {
38
+ /** Inset fill rectangle (canvas coordinates). */
39
+ readonly x: number;
40
+ readonly y: number;
41
+ readonly w: number;
42
+ readonly h: number;
43
+ /** Text area: columns l..r, rows top..bottom (inclusive). */
44
+ readonly l: number;
45
+ readonly r: number;
46
+ readonly top: number;
47
+ readonly bottom: number;
48
+ readonly bg: string;
49
+ }
50
+
51
+ /** Fill a lighter element inset and return its text area. */
52
+ export function inset(canvas: SurfaceCanvas, x: number, y: number, w: number, h: number, bg = activeTokens().backgroundElement): KitInset {
53
+ canvas.fill(x, y, w, h, bg);
54
+ return {
55
+ x, y, w, h, bg,
56
+ l: x + INSET_PAD_X,
57
+ r: x + w - 1 - INSET_PAD_X,
58
+ top: y + 1,
59
+ bottom: y + h - 2,
60
+ };
61
+ }
62
+
63
+ /** Put text inside an inset (keeps the inset bg). Returns the column after it. */
64
+ export function insetPut(canvas: SurfaceCanvas, p: KitInset, x: number, y: number, text: string, style: KitStyle = {}): number {
65
+ if (y < p.top || y > p.bottom) return x;
66
+ return canvas.put(x, y, clipText(text, p.r - x + 1), { ...style, bg: p.bg });
67
+ }
68
+
69
+ /** Wrapped text inside an inset from row y; returns the row after the last line drawn. */
70
+ export function insetWrap(canvas: SurfaceCanvas, p: KitInset, x: number, y: number, text: string, style: KitStyle = {}): number {
71
+ let yy = y;
72
+ for (const line of wrapLines(text, p.r - x + 1)) {
73
+ if (yy > p.bottom) break;
74
+ canvas.put(x, yy, line, { ...style, bg: p.bg });
75
+ yy++;
76
+ }
77
+ return yy;
78
+ }
79
+
80
+ /** One line of inset text. */
81
+ export interface InsetLine { readonly text: string; readonly style: KitStyle }
82
+
83
+ /**
84
+ * Wrap and draw paragraphs inside an inset from row y. When they do not all
85
+ * fit, the last visible row says how many lines are hidden instead of the text
86
+ * stopping silently. Returns the row after the last one drawn.
87
+ */
88
+ export function insetLines(canvas: SurfaceCanvas, p: KitInset, x: number, y: number, paragraphs: readonly InsetLine[]): number {
89
+ const t = activeTokens();
90
+ const lines: InsetLine[] = [];
91
+ for (const para of paragraphs) {
92
+ if (para.text === '') lines.push(para);
93
+ else for (const text of wrapLines(para.text, p.r - x + 1)) lines.push({ text, style: para.style });
94
+ }
95
+ const room = Math.max(0, p.bottom - y + 1);
96
+ const overflow = lines.length > room;
97
+ const shown = overflow ? lines.slice(0, Math.max(0, room - 1)) : lines;
98
+ let yy = y;
99
+ for (const line of shown) canvas.put(x, yy++, line.text, { ...line.style, bg: p.bg });
100
+ if (overflow && room > 0) canvas.put(x, yy++, `${lines.length - shown.length} more lines`, { fg: t.textFaint, bg: p.bg });
101
+ return yy;
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Keycap hints in a plain row (bars and status rows outside a modal)
106
+ // ---------------------------------------------------------------------------
107
+
108
+ /** Display width of a row of keycap hints (3 columns between hints). */
109
+ export function keycapHintsWidth(hints: readonly KitHint[]): number {
110
+ return hints.reduce((sum, [key, action], i) => sum + (i > 0 ? 3 : 0) + getDisplayWidth(key) + 3 + getDisplayWidth(action), 0);
111
+ }
112
+
113
+ /**
114
+ * Paint keycap hints into a Line from column x (each key a bold chip on the
115
+ * keycap fill, its action in `actionStyle`), the same look as a modal's hint
116
+ * row. Hints that do not fit before `endX` are left out whole, never cut.
117
+ * Returns the column after the last hint drawn.
118
+ */
119
+ export function paintKeycapHints(line: Line, x: number, endX: number, hints: readonly KitHint[], actionStyle: { readonly fg: string; readonly bg?: string }): number {
120
+ const t = activeTokens();
121
+ let cx = x;
122
+ const put = (text: string, style: { fg: string; bg?: string; bold?: boolean }): void => {
123
+ for (const ch of text) {
124
+ const w = getDisplayWidth(ch);
125
+ if (cx + w > endX || cx >= line.length) return;
126
+ const prev = line[cx]!;
127
+ line[cx] = { ...prev, char: ch, fg: style.fg, bg: style.bg ?? prev.bg, bold: style.bold ?? false, dim: false, underline: false, italic: false, strikethrough: false };
128
+ if (w === 2 && cx + 1 < line.length) line[cx + 1] = { ...line[cx]!, char: '' };
129
+ cx += w;
130
+ }
131
+ };
132
+ hints.forEach(([key, action], i) => {
133
+ const need = (i > 0 ? 3 : 0) + getDisplayWidth(key) + 3 + getDisplayWidth(action);
134
+ if (cx + need > endX) return;
135
+ if (i > 0) cx += 3;
136
+ put(` ${key} `, { fg: t.text, bg: t.border, bold: true });
137
+ cx += 1;
138
+ put(action, { fg: actionStyle.fg, bg: actionStyle.bg });
139
+ });
140
+ return cx;
141
+ }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // Tabs
145
+ // ---------------------------------------------------------------------------
146
+
147
+ /** Tabs across a row; the active one carries the gradient with dark bold text. Returns the column after the last tab. */
148
+ export function tabs(canvas: SurfaceCanvas, x: number, y: number, labels: readonly string[], active: number, maxX: number): number {
149
+ const t = activeTokens();
150
+ let cx = x;
151
+ labels.forEach((label, k) => {
152
+ const w = getDisplayWidth(label);
153
+ if (cx + w > maxX) return;
154
+ if (k === active) {
155
+ canvas.grad(cx - 1, y, w + 2, t.brand, t.brandEnd);
156
+ canvas.put(cx, y, label, { fg: t.selectedListItemText, bold: true });
157
+ } else {
158
+ canvas.put(cx, y, label, { fg: t.textMuted });
159
+ }
160
+ cx += w + 4;
161
+ });
162
+ return cx;
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // Buttons, chips and meters
167
+ // ---------------------------------------------------------------------------
168
+
169
+ export type ButtonTone = 'primary' | 'warning' | 'danger';
170
+
171
+ /** A button chip. The chosen one is filled in its tone with dark bold text; the rest are quiet chips (the keycap fill). */
172
+ export function button(canvas: SurfaceCanvas, x: number, y: number, label: string, chosen: boolean, tone: ButtonTone = 'primary'): number {
173
+ const t = activeTokens();
174
+ const toneBg = tone === 'danger' ? t.error : tone === 'warning' ? t.warning : t.brand;
175
+ const style: KitStyle = chosen
176
+ ? { fg: t.selectedListItemText, bg: toneBg, bold: true }
177
+ : { fg: t.textMuted, bg: t.border };
178
+ return canvas.put(x, y, ` ${label} `, style);
179
+ }
180
+
181
+ /** Width of a button chip. */
182
+ export function buttonWidth(label: string): number {
183
+ return getDisplayWidth(label) + 2;
184
+ }
185
+
186
+ /** A small colored chip (e.g. a risk level). */
187
+ export function chip(canvas: SurfaceCanvas, x: number, y: number, label: string, bg: string): number {
188
+ const t = activeTokens();
189
+ return canvas.put(x, y, ` ${label} `, { fg: t.selectedListItemText, bg, bold: true });
190
+ }
191
+
192
+ /** A 10-step meter (▰ filled, ▱ empty) in `color`. Returns the column after it. */
193
+ export function meter(canvas: SurfaceCanvas, x: number, y: number, fraction: number, color: string, bg?: string): number {
194
+ const t = activeTokens();
195
+ const n = Math.max(0, Math.min(10, Math.round(fraction * 10)));
196
+ let cx = x;
197
+ for (let q = 0; q < 10; q++) cx = canvas.put(cx, y, q < n ? '▰' : '▱', { fg: q < n ? color : t.border, bg });
198
+ return cx;
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Toasts
203
+ // ---------------------------------------------------------------------------
204
+
205
+ export type ToastTone = 'info' | 'success' | 'warning' | 'error';
206
+
207
+ export interface ToastSpec {
208
+ readonly title: string;
209
+ readonly body?: string;
210
+ readonly tone: ToastTone;
211
+ }
212
+
213
+ function toastToneColor(tone: ToastTone): string {
214
+ const t = activeTokens();
215
+ return tone === 'error' ? t.error : tone === 'warning' ? t.warning : tone === 'success' ? t.success : t.info;
216
+ }
217
+
218
+ /** Width of a toast on a screen of width screenW. */
219
+ function toastWidth(screenW: number): number {
220
+ return Math.max(20, Math.min(50, screenW - 4));
221
+ }
222
+
223
+ /** Rows toasts may use: from `top` down to (not including) `bottom`. */
224
+ export interface ToastArea {
225
+ readonly top: number;
226
+ /** First row toasts must not cover: the composer's top (the status line and composer sit below it). */
227
+ readonly bottom: number;
228
+ }
229
+
230
+ /** Cleared columns left of each toast: text beside it keeps 2 columns from its fill (Measurements). */
231
+ const TOAST_GAP_COLS = 2;
232
+ /** Cleared rows between two stacked toasts and under the lowest one. */
233
+ const TOAST_GAP_ROWS = 1;
234
+
235
+ /**
236
+ * Toasts stacked in the top right corner (below the header row), newest first.
237
+ * Each toast: ┃ in its tone on both sides, a padding row above and below, the
238
+ * title bold, the body muted and wrapped in full. They stay inside `area`, so
239
+ * they never cover the composer or the status line: a toast that does not fit
240
+ * below the ones above it waits in the notification history, and a single
241
+ * toast taller than the area shows what fits and ends with where the full
242
+ * text is.
243
+ *
244
+ * Toasts float over the transcript, so the layer carries its own gap: a
245
+ * cleared row between stacked toasts and under the lowest one, and
246
+ * TOAST_GAP_COLS cleared columns on their left. The layer is `clearBlank`, so
247
+ * those cells show the plain screen, never a transcript block's fill or text
248
+ * touching a toast's edge.
249
+ */
250
+ export function renderToasts(screenW: number, screenH: number, toasts: readonly ToastSpec[], area: ToastArea = { top: 1, bottom: screenH - 2 }): SurfaceLayer | null {
251
+ const top = Math.max(0, area.top);
252
+ // Room for toasts: the area less the gap row the lowest toast keeps under it.
253
+ const maxH = Math.min(screenH, area.bottom) - top - TOAST_GAP_ROWS;
254
+ if (toasts.length === 0 || screenW < 24 || maxH < 3) return null;
255
+ const t = activeTokens();
256
+ const w = toastWidth(screenW);
257
+ const textW = w - 6;
258
+ const blocks = toasts.map((toast) => {
259
+ const lines = [
260
+ ...wrapLines(toast.title, textW).map((text) => ({ text, bold: true, fg: t.text })),
261
+ ...(toast.body ? wrapLines(toast.body, textW).map((text) => ({ text, bold: false, fg: t.textMuted })) : []),
262
+ ];
263
+ return { toast, lines, h: lines.length + 2 };
264
+ });
265
+ let total = 0;
266
+ const shown: typeof blocks = [];
267
+ for (const block of blocks) {
268
+ const need = total + (shown.length > 0 ? TOAST_GAP_ROWS : 0) + block.h;
269
+ if (need > maxH) break;
270
+ shown.push(block);
271
+ total = need;
272
+ }
273
+ if (shown.length === 0) {
274
+ // The newest toast alone is taller than the area: show what fits, and say
275
+ // where the rest is instead of cutting it off silently.
276
+ const first = blocks[0]!;
277
+ const room = maxH - 2;
278
+ const pointer = { text: 'full text in /notifications', bold: false, fg: t.textFaint };
279
+ const lines = room <= 1 ? [pointer] : [...first.lines.slice(0, room - 1), pointer];
280
+ shown.push({ toast: first.toast, lines, h: lines.length + 2 });
281
+ total = lines.length + 2;
282
+ }
283
+ // Blank cells (the left gap columns, the rows between toasts, the gap row
284
+ // under the lowest one) stay blank: the layer clears them on the screen.
285
+ const canvas = new SurfaceCanvas(w + TOAST_GAP_COLS, total + TOAST_GAP_ROWS);
286
+ let y = 0;
287
+ shown.forEach((block, k) => {
288
+ if (k > 0) y += TOAST_GAP_ROWS;
289
+ const bar = toastToneColor(block.toast.tone);
290
+ const x0 = TOAST_GAP_COLS;
291
+ for (let r = 0; r < block.h; r++) {
292
+ canvas.fill(x0, y + r, w, 1, t.backgroundPanel);
293
+ canvas.put(x0, y + r, '┃', { fg: bar });
294
+ canvas.put(x0 + w - 1, y + r, '┃', { fg: bar });
295
+ }
296
+ block.lines.forEach((line, i) => canvas.put(x0 + 3, y + 1 + i, line.text, { fg: line.fg, bold: line.bold }));
297
+ y += block.h;
298
+ });
299
+ return { x: screenW - w - 2 - TOAST_GAP_COLS, y: top, lines: canvas.lines, dim: false, clearBlank: true };
300
+ }
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // Composer popups (slash commands, @ files)
304
+ // ---------------------------------------------------------------------------
305
+
306
+ /** Column of the popup's ┃ bar (the composer's own left edge). */
307
+ export const POPUP_BAR_X = 2;
308
+
309
+ export interface PopupOptions {
310
+ /** Width of the lines to produce (the conversation width). */
311
+ readonly width: number;
312
+ readonly rows: readonly KitRow[];
313
+ /** A row pinned above the list (it never scrolls away), e.g. the typed query. */
314
+ readonly header?: KitRow;
315
+ /** Maximum rows the content may use (padding rows excluded). */
316
+ readonly maxRows: number;
317
+ /** Remembers the list's scroll position between renders (the picker's state object). */
318
+ readonly scrollOwner?: object;
319
+ }
320
+
321
+ export interface PopupResult {
322
+ readonly lines: Line[];
323
+ readonly list: KitListResult;
324
+ }
325
+
326
+ /**
327
+ * A composer popup: the ┃ bar at column 2 in the faint color, a surface fill
328
+ * from column 3 to width-3 with a padding row above and below, text from
329
+ * column 5 and right-aligned text ending 2 columns inside the fill. Returns
330
+ * full-width lines (cells left of the bar and right of the fill are empty)
331
+ * for docking right above the composer. A list longer than the popup keeps
332
+ * the selected row in view and says what is hidden on its last row.
333
+ */
334
+ export function renderPopup(options: PopupOptions): PopupResult {
335
+ const t = activeTokens();
336
+ const { width } = options;
337
+ const fillX = POPUP_BAR_X + 1;
338
+ const fillW = Math.max(6, width - fillX - 2);
339
+ const x0 = fillX + 2;
340
+ const x1 = fillX + fillW - 3;
341
+ const headerRows = options.header ? measureRow(options.header, x0, x1) : 0;
342
+ const listMax = Math.max(1, options.maxRows - headerRows);
343
+ const scrollKey = options.scrollOwner ? { owner: options.scrollOwner, name: 'popup' } : undefined;
344
+ // Measure on a scratch canvas: how many rows the list really needs.
345
+ const compact = width < NARROW_MODAL_BREAKPOINT;
346
+ const scratch = new SurfaceCanvas(width, listMax + 1);
347
+ scratch.compact = compact;
348
+ const trial = drawList(scratch, { rows: options.rows, top: 0, bottom: listMax - 1, x0, x1, scrollKey });
349
+ const overflow = trial.above > 0 || trial.below > 0;
350
+ const listUsed = overflow ? listMax : Math.max(1, trial.endY);
351
+ const used = headerRows + listUsed;
352
+ const canvas = new SurfaceCanvas(width, used + 2);
353
+ canvas.compact = compact;
354
+ canvas.fill(fillX, 0, fillW, used + 2, t.backgroundPanel);
355
+ if (options.header) drawRow(canvas, 1, options.header, x0, x1);
356
+ const listOptions = { rows: options.rows, top: 1 + headerRows, bottom: used, x0, x1, scrollKey };
357
+ const list = overflow ? drawScrollingList(canvas, listOptions) : drawList(canvas, listOptions);
358
+ for (let r = 0; r < used + 2; r++) canvas.put(POPUP_BAR_X, r, '┃', { fg: t.textFaint });
359
+ return { lines: canvas.lines, list };
360
+ }