@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
@@ -0,0 +1,314 @@
1
+ /**
2
+ * NotificationFeed, the read-model behind the `panel_only` notification
3
+ * target.
4
+ *
5
+ * The SDK's NotificationRouter (`@/runtime/index.ts` -> platform runtime `ui`
6
+ * barrel) can route a notification's `RoutingDecision.target` to
7
+ * `'panel_only'`, meaning: don't surface this in the conversation or the
8
+ * status bar, but it must still reach a real, visible place. Before this
9
+ * file, nothing in this repo consumed that target outside of tests, a
10
+ * view-routed notification (including every burst- or batch-collapsed one)
11
+ * had nowhere to go. This feed is that place: a caller holding a routed
12
+ * `Notification` + `RoutingDecision` pair calls `record()`, and the
13
+ * Notifications modal (input/notifications-modal.ts) shows whatever has
14
+ * accumulated; warning and critical entries also toast.
15
+ *
16
+ * Collapsed groups (reasonCode `burst_collapsed` / `batch_window_collapsed`)
17
+ * are tracked by their `batchKey` and accumulate an honest running count
18
+ * instead of rendering one row per suppressed duplicate.
19
+ *
20
+ * Production wiring, something in the running app actually calling
21
+ * `record()` with live notifications, rides the SDK round that turns on
22
+ * adaptive notification suppression by default; this feed and its modal are
23
+ * the render target that work lands on, built ahead of it so the target is
24
+ * never missing when that switch flips.
25
+ */
26
+ import type { Notification, RoutingDecision } from '@/runtime/index.ts';
27
+
28
+ export interface NotificationFeedEntry {
29
+ readonly key: string;
30
+ readonly domain: string;
31
+ readonly level: Notification['level'];
32
+ readonly title: string;
33
+ readonly body: string | undefined;
34
+ /** Unix ms of the most recent notification folded into this entry. */
35
+ readonly timestamp: number;
36
+ /** The router's reason, or 'system_notice' for a conversation system notice (recordNotice). */
37
+ readonly reasonCode: RoutingDecision['reasonCode'] | 'system_notice';
38
+ /** How many notifications this entry represents. 1 for a standalone item; >1 for a collapsed group. Always the true count, never estimated. */
39
+ readonly collapsedCount: number;
40
+ /**
41
+ * The view this notification is about (the notification's own panelId or
42
+ * jump action, else one derived from its domain), resolved to a modal by
43
+ * input/views.ts. Undefined when it is not about anything that can be opened.
44
+ */
45
+ readonly subject?: string;
46
+ /**
47
+ * Whether this entry toasts: 'always' for a system notice (every one shows
48
+ * as a toast), 'never' for one restored from a saved session (it already
49
+ * happened), absent for a routed notification (warning and critical toast).
50
+ */
51
+ readonly toast?: 'always' | 'never';
52
+ }
53
+
54
+ /** Severity order, for folding two arrivals of one event (the higher level wins). */
55
+ const LEVEL_RANK: Readonly<Record<Notification['level'], number>> = { debug: -1, info: 0, warning: 1, critical: 2 };
56
+
57
+ /** A conversation system notice to keep in the history (see recordNotice). */
58
+ export interface NoticeInput {
59
+ readonly domain: string;
60
+ readonly level: Notification['level'];
61
+ readonly title: string;
62
+ readonly body?: string | undefined;
63
+ readonly timestamp: number;
64
+ /** A notice restored from a saved session: kept in history, never toasted, already seen. */
65
+ readonly restored?: boolean;
66
+ /** The runtime event this notice restates (the SDK's runtimeEventOfNotice key), if any. */
67
+ readonly eventKey?: string | undefined;
68
+ }
69
+
70
+ /** Domains whose notifications are about something the Agents modal shows. */
71
+ const AGENT_DOMAINS: ReadonlySet<string> = new Set(['agents', 'tasks', 'workflows', 'automation', 'wrfc', 'orchestration', 'plan']);
72
+
73
+ /** The view a notification is about, or undefined. */
74
+ function subjectOf(notification: Notification): string | undefined {
75
+ if (notification.panelId) return notification.panelId;
76
+ if (notification.action?.type === 'jump_to_panel' && notification.action.panelId) return notification.action.panelId;
77
+ if (AGENT_DOMAINS.has(notification.domain)) return 'agents';
78
+ if (notification.domain === 'security') return 'security';
79
+ if (notification.domain === 'git') return 'changes';
80
+ return undefined;
81
+ }
82
+
83
+ /** Plain names for a collapsed group of mixed notifications, by domain. */
84
+ const GROUP_TITLES: Readonly<Record<string, string>> = {
85
+ agents: 'Agent updates',
86
+ tasks: 'Task updates',
87
+ workflows: 'Review chain updates',
88
+ automation: 'Scheduled job updates',
89
+ deliveries: 'Message delivery updates',
90
+ security: 'Security updates',
91
+ ops: 'System updates',
92
+ };
93
+
94
+ /** The title a collapsed group of mixed notifications shows: a plain name, never a raw domain id. */
95
+ export function groupTitle(domain: string): string {
96
+ const known = GROUP_TITLES[domain];
97
+ if (known) return known;
98
+ const words = domain.replace(/[_-]+/g, ' ').trim();
99
+ return words ? `${words[0]!.toUpperCase()}${words.slice(1)} updates` : 'Updates';
100
+ }
101
+
102
+ /**
103
+ * Entries kept before the oldest leave. System notices share this bound, so a
104
+ * long session's full notice history stays reachable in the modal.
105
+ */
106
+ const MAX_ENTRIES = 2000;
107
+
108
+ /** Reason codes whose notifications fold into one running-count entry per batch key, rather than one entry per occurrence. */
109
+ const COLLAPSING_REASON_CODES: ReadonlySet<RoutingDecision['reasonCode']> = new Set([
110
+ 'burst_collapsed',
111
+ 'batch_window_collapsed',
112
+ ]);
113
+
114
+ export class NotificationFeed {
115
+ private readonly entries = new Map<string, NotificationFeedEntry>();
116
+ /** Insertion order of `entries` keys (oldest first), for bounded eviction. */
117
+ private order: string[] = [];
118
+ private readonly listeners = new Set<() => void>();
119
+ /** Unix ms of the newest entry the user has seen in the Notifications modal. */
120
+ private seenThrough = 0;
121
+ private nextNoticeId = 1;
122
+ /** Per collapsed group: how many of each notification title it folded. */
123
+ private readonly groupTitles = new Map<string, Map<string, number>>();
124
+ /**
125
+ * One entry per runtime event: the entry each event key was recorded in. An
126
+ * agent or chain event reaches the feed twice, from the runtime-bus bridge
127
+ * (record) and as the conversation line restating it (recordNotice); the
128
+ * second arrival folds into the first entry instead of adding another.
129
+ */
130
+ private readonly eventEntries = new Map<string, string>();
131
+
132
+ /**
133
+ * Record a routed notification. Only notifications actually targeted at
134
+ * `panel_only` are kept, a caller passing anything else is a mistake (the
135
+ * router sent it elsewhere), so it's dropped rather than shown in the
136
+ * wrong place.
137
+ */
138
+ record(notification: Notification, decision: RoutingDecision, eventKey?: string): void {
139
+ if (decision.target !== 'panel_only') return;
140
+ if (eventKey && this.foldEvent(eventKey, { level: notification.level, body: notification.body, fromNotice: false, restored: false })) return;
141
+
142
+ const collapsing = COLLAPSING_REASON_CODES.has(decision.reasonCode) && Boolean(decision.batchKey);
143
+ const key = collapsing ? `group:${decision.batchKey}` : `single:${notification.id}`;
144
+ const previousCount = collapsing ? (this.entries.get(key)?.collapsedCount ?? 0) : 0;
145
+
146
+ // A collapsed group folds every notification of one domain and level, of
147
+ // any kind. Its title names the group, never just its latest member, and
148
+ // the body counts each kind, so the number shown is what it says it is.
149
+ let title = notification.title;
150
+ let body = notification.body;
151
+ if (collapsing) {
152
+ const counts = this.groupTitles.get(key) ?? new Map<string, number>();
153
+ counts.set(notification.title, (counts.get(notification.title) ?? 0) + 1);
154
+ this.groupTitles.set(key, counts);
155
+ if (counts.size > 1) {
156
+ title = groupTitle(notification.domain);
157
+ body = [...counts].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \u00d7${n}`).join(', ');
158
+ }
159
+ }
160
+ const entry: NotificationFeedEntry = {
161
+ key,
162
+ domain: notification.domain,
163
+ level: notification.level,
164
+ title,
165
+ body,
166
+ timestamp: notification.timestamp,
167
+ reasonCode: decision.reasonCode,
168
+ collapsedCount: previousCount + 1,
169
+ subject: subjectOf(notification),
170
+ };
171
+
172
+ this.store(key, entry);
173
+ if (eventKey) this.eventEntries.set(eventKey, key);
174
+ }
175
+
176
+ /**
177
+ * Record a conversation system notice ([WRFC] …, [Agents] …, a compaction
178
+ * receipt): its full text, one entry per notice. It toasts unless it was
179
+ * restored from a saved session, which also counts as already seen.
180
+ */
181
+ recordNotice(input: NoticeInput): NotificationFeedEntry {
182
+ if (input.eventKey) {
183
+ const folded = this.foldEvent(input.eventKey, { level: input.level, body: input.body, fromNotice: true, restored: input.restored === true });
184
+ if (folded) return folded;
185
+ }
186
+ const key = `notice:${this.nextNoticeId++}`;
187
+ const entry: NotificationFeedEntry = {
188
+ key,
189
+ domain: input.domain,
190
+ level: input.level,
191
+ title: input.title,
192
+ body: input.body,
193
+ timestamp: input.timestamp,
194
+ reasonCode: 'system_notice',
195
+ collapsedCount: 1,
196
+ subject: AGENT_DOMAINS.has(input.domain) ? 'agents' : undefined,
197
+ toast: input.restored ? 'never' : 'always',
198
+ };
199
+ if (input.restored) this.seenThrough = Math.max(this.seenThrough, input.timestamp);
200
+ this.store(key, entry);
201
+ if (input.eventKey) this.eventEntries.set(input.eventKey, key);
202
+ return entry;
203
+ }
204
+
205
+ /**
206
+ * The second arrival of one runtime event: fold it into the entry the first
207
+ * made, keeping that entry's plain title and place. The conversation line's
208
+ * detail is the fuller one, so it becomes the body; the higher level wins;
209
+ * a live notice makes the entry toast (once, see the toast bridge). A
210
+ * collapsed group that already counted the event is left as it is. Returns
211
+ * the entry, or undefined when the event has no entry yet.
212
+ */
213
+ private foldEvent(
214
+ eventKey: string,
215
+ arrival: { readonly level: Notification['level']; readonly body: string | undefined; readonly fromNotice: boolean; readonly restored: boolean },
216
+ ): NotificationFeedEntry | undefined {
217
+ const key = this.eventEntries.get(eventKey);
218
+ const existing = key === undefined ? undefined : this.entries.get(key);
219
+ if (!key || !existing) return undefined;
220
+ if (existing.collapsedCount > 1) return existing;
221
+ const merged: NotificationFeedEntry = {
222
+ ...existing,
223
+ level: LEVEL_RANK[arrival.level] > LEVEL_RANK[existing.level] ? arrival.level : existing.level,
224
+ body: arrival.fromNotice ? (arrival.body ?? existing.body) : (existing.body ?? arrival.body),
225
+ ...(arrival.fromNotice && !arrival.restored ? { toast: 'always' as const } : {}),
226
+ };
227
+ this.store(key, merged);
228
+ return merged;
229
+ }
230
+
231
+ private store(key: string, entry: NotificationFeedEntry): void {
232
+ if (!this.entries.has(key)) {
233
+ this.order.push(key);
234
+ if (this.order.length > MAX_ENTRIES) {
235
+ const evicted = this.order.shift();
236
+ if (evicted !== undefined) { this.entries.delete(evicted); this.groupTitles.delete(evicted); this.forgetEvents(evicted); }
237
+ }
238
+ }
239
+ this.entries.set(key, entry);
240
+ this.emitChange();
241
+ }
242
+
243
+ /** Every entry, most recently updated first. */
244
+ list(): readonly NotificationFeedEntry[] {
245
+ const items: NotificationFeedEntry[] = [];
246
+ for (const key of this.order) {
247
+ const entry = this.entries.get(key);
248
+ if (entry) items.push(entry);
249
+ }
250
+ return items.reverse();
251
+ }
252
+
253
+ clear(): void {
254
+ this.entries.clear();
255
+ this.groupTitles.clear();
256
+ this.eventEntries.clear();
257
+ this.order = [];
258
+ this.emitChange();
259
+ }
260
+
261
+ /** Remove one entry (a collapsed group goes as a whole). Returns whether it existed. */
262
+ dismiss(key: string): boolean {
263
+ if (!this.entries.delete(key)) return false;
264
+ this.groupTitles.delete(key);
265
+ this.forgetEvents(key);
266
+ this.order = this.order.filter((k) => k !== key);
267
+ this.emitChange();
268
+ return true;
269
+ }
270
+
271
+ private forgetEvents(entryKey: string): void {
272
+ for (const [eventKey, owner] of this.eventEntries) if (owner === entryKey) this.eventEntries.delete(eventKey);
273
+ }
274
+
275
+ /** Entries updated since the user last looked. */
276
+ unreadCount(): number {
277
+ let n = 0;
278
+ for (const entry of this.entries.values()) if (entry.timestamp > this.seenThrough) n++;
279
+ return n;
280
+ }
281
+
282
+ /** Whether an entry was updated since the user last looked. */
283
+ isUnread(entry: NotificationFeedEntry): boolean {
284
+ return entry.timestamp > this.seenThrough;
285
+ }
286
+
287
+ /** Everything recorded so far counts as seen. */
288
+ markAllSeen(): void {
289
+ for (const entry of this.entries.values()) this.seenThrough = Math.max(this.seenThrough, entry.timestamp);
290
+ }
291
+
292
+ /** Subscribe to feed changes (e.g. to repaint an open modal). Returns an unsubscribe function. */
293
+ subscribe(listener: () => void): () => void {
294
+ this.listeners.add(listener);
295
+ return () => this.listeners.delete(listener);
296
+ }
297
+
298
+ private emitChange(): void {
299
+ for (const listener of this.listeners) listener();
300
+ }
301
+ }
302
+
303
+ let sharedFeed: NotificationFeed | null = null;
304
+
305
+ /**
306
+ * The process-wide `panel_only` feed. Lazily created so the running app has
307
+ * exactly one feed that every caller and the Notifications modal agree
308
+ * on, while tests construct their own isolated `NotificationFeed`
309
+ * instance instead of reaching for this one.
310
+ */
311
+ export function getSharedNotificationFeed(): NotificationFeed {
312
+ if (!sharedFeed) sharedFeed = new NotificationFeed();
313
+ return sharedFeed;
314
+ }
@@ -4,15 +4,15 @@ import { getDisplayWidth } from '../utils/terminal-width.ts';
4
4
  import { activeUiTones, registerThemeRefresh } from '../renderer/theme.ts';
5
5
 
6
6
  // ---------------------------------------------------------------------------
7
- // Panel palette + core line primitives.
7
+ // View palette + core line primitives.
8
8
  //
9
- // The leaf foundation of the panel formatting toolkit: both `polish.ts` and
9
+ // The leaf foundation of the view formatting toolkit: both `polish.ts` and
10
10
  // `polish-tables.ts` build on these. Kept dependency-free (only grid /
11
11
  // terminal-width / ui-primitives) so it never participates in an import cycle.
12
- // All symbols are re-exported from `polish.ts`, panels import from there.
12
+ // All symbols are re-exported from `polish.ts`, views import from there.
13
13
  // ---------------------------------------------------------------------------
14
14
 
15
- export interface PanelPalette {
15
+ export interface ViewPalette {
16
16
  readonly label: string;
17
17
  readonly value: string;
18
18
  readonly dim: string;
@@ -34,10 +34,10 @@ export interface PanelPalette {
34
34
  // Built from the mode-resolved chrome tones (activeUiTones). Because 100+ call
35
35
  // sites read this object by reference, mode changes rebuild it IN PLACE via the
36
36
  // registered refresher below rather than re-resolving per call, see theme.ts's
37
- // active-mode runtime note. This refresher is registered before any panel's
38
- // extendPalette() runs (panels import polish → polish-core first), so on a mode
37
+ // active-mode runtime note. This refresher is registered before any view's
38
+ // extendPalette() runs (views import polish → polish-core first), so on a mode
39
39
  // flip the base is rebuilt before the extended palettes re-merge from it.
40
- function buildPanelPalette(): Required<PanelPalette> {
40
+ function buildViewPalette(): Required<ViewPalette> {
41
41
  const t = activeUiTones();
42
42
  return {
43
43
  header: t.fg.primary,
@@ -59,41 +59,43 @@ function buildPanelPalette(): Required<PanelPalette> {
59
59
  };
60
60
  }
61
61
 
62
- export const DEFAULT_PANEL_PALETTE: Readonly<Required<PanelPalette>> = buildPanelPalette();
63
- registerThemeRefresh(() => Object.assign(DEFAULT_PANEL_PALETTE as Required<PanelPalette>, buildPanelPalette()));
62
+ export const DEFAULT_VIEW_PALETTE: Readonly<Required<ViewPalette>> = buildViewPalette();
63
+ registerThemeRefresh(() => Object.assign(DEFAULT_VIEW_PALETTE as Required<ViewPalette>, buildViewPalette()));
64
64
 
65
65
  /**
66
- * Extend the base panel palette with domain-specific colors.
66
+ * Extend the base view palette with domain-specific colors.
67
67
  *
68
- * Convention: raw hex colors may only live inside a palette constant declared
69
- * at the top of a panel file, not inline in render calls.
68
+ * `extras` is a builder that reads theme tokens (activeTokens() /
69
+ * activeUiTones()); it runs now and again on every theme or mode change, so
70
+ * the domain colors follow the active theme. Raw hex literals do not belong
71
+ * in extras: map each domain color onto a theme token.
70
72
  *
71
73
  * @example
72
74
  * ```ts
73
- * const C = extendPalette(DEFAULT_PANEL_PALETTE, {
74
- * decision: '#38bdf8',
75
- * incident: '#ef4444',
75
+ * const C = extendPalette(DEFAULT_VIEW_PALETTE, () => {
76
+ * const p = activeTokens();
77
+ * return { decision: p.info, incident: p.error };
76
78
  * });
77
79
  * ```
78
80
  */
79
81
  export function extendPalette<T extends Record<string, string>>(
80
- base: typeof DEFAULT_PANEL_PALETTE,
81
- extras: T,
82
- ): typeof DEFAULT_PANEL_PALETTE & T {
83
- const merged = { ...base, ...extras } as typeof DEFAULT_PANEL_PALETTE & T;
84
- // Self-register an in-place rebuild so every extendPalette-derived panel
85
- // palette (cost/token/git/skills/diff/wrfc) tracks the active mode with zero
86
- // per-panel churn. Runs AFTER the base refresher (registered at module eval,
87
- // before any panel calls this), so `base` already carries the new-mode values.
88
- registerThemeRefresh(() => Object.assign(merged as Record<string, string>, base, extras));
82
+ base: typeof DEFAULT_VIEW_PALETTE,
83
+ extras: () => T,
84
+ ): typeof DEFAULT_VIEW_PALETTE & T {
85
+ const merged = { ...base, ...extras() } as typeof DEFAULT_VIEW_PALETTE & T;
86
+ // Self-register an in-place rebuild so every extendPalette-derived view
87
+ // palette tracks the active theme with zero per-view churn. Runs AFTER the
88
+ // base refresher (registered at module eval, before any view calls this),
89
+ // so `base` already carries the new values when the extras are rebuilt.
90
+ registerThemeRefresh(() => Object.assign(merged as Record<string, string>, base, extras()));
89
91
  return merged;
90
92
  }
91
93
 
92
- export function buildPanelLine(
94
+ export function buildViewLine(
93
95
  width: number,
94
- segments: Array<StyledPanelSegment | [string, string, string?]>,
96
+ segments: Array<StyledViewSegment | [string, string, string?]>,
95
97
  ): Line {
96
- return buildStyledPanelLine(
98
+ return buildStyledViewLine(
97
99
  width,
98
100
  segments.map((seg) =>
99
101
  Array.isArray(seg) ? { text: seg[0], fg: seg[1], bg: seg[2] } : seg,
@@ -101,7 +103,7 @@ export function buildPanelLine(
101
103
  );
102
104
  }
103
105
 
104
- export interface StyledPanelSegment {
106
+ export interface StyledViewSegment {
105
107
  readonly text: string;
106
108
  readonly fg: string;
107
109
  readonly bg?: string;
@@ -109,13 +111,13 @@ export interface StyledPanelSegment {
109
111
  readonly dim?: boolean;
110
112
  }
111
113
 
112
- export function buildSelectablePanelLine(
114
+ export function buildSelectableViewLine(
113
115
  width: number,
114
- segments: ReadonlyArray<StyledPanelSegment>,
116
+ segments: ReadonlyArray<StyledViewSegment>,
115
117
  options: { selected?: boolean; selectedBg?: string; fillFg?: string; fillBg?: string; leadingMarker?: string } = {},
116
118
  ): Line {
117
119
  const selected = options.selected ?? false;
118
- const selectedBg = selected ? (options.selectedBg ?? DEFAULT_PANEL_PALETTE.selectBg) : '';
120
+ const selectedBg = selected ? (options.selectedBg ?? DEFAULT_VIEW_PALETTE.selectBg) : '';
119
121
  const fillBg = selectedBg || options.fillBg || '';
120
122
  const fillFg = options.fillFg ?? '';
121
123
  const cells = createEmptyLine(width);
@@ -130,8 +132,8 @@ export function buildSelectablePanelLine(
130
132
  for (const ch of options.leadingMarker) {
131
133
  const charWidth = getDisplayWidth(ch);
132
134
  if (charWidth <= 0 || col + charWidth > width) break;
133
- cells[col] = createStyledCell(ch, { fg: DEFAULT_PANEL_PALETTE.info, bg: selectedBg, bold: true });
134
- if (charWidth > 1 && col + 1 < width) cells[col + 1] = createStyledCell(' ', { fg: DEFAULT_PANEL_PALETTE.info, bg: selectedBg, bold: true });
135
+ cells[col] = createStyledCell(ch, { fg: DEFAULT_VIEW_PALETTE.info, bg: selectedBg, bold: true });
136
+ if (charWidth > 1 && col + 1 < width) cells[col + 1] = createStyledCell(' ', { fg: DEFAULT_VIEW_PALETTE.info, bg: selectedBg, bold: true });
135
137
  col += charWidth;
136
138
  }
137
139
  }
@@ -166,10 +168,10 @@ export function buildSelectablePanelLine(
166
168
  return cells;
167
169
  }
168
170
 
169
- export function buildStyledPanelLine(
171
+ export function buildStyledViewLine(
170
172
  width: number,
171
- segments: ReadonlyArray<StyledPanelSegment>,
173
+ segments: ReadonlyArray<StyledViewSegment>,
172
174
  options: { fillBg?: string; fillFg?: string } = {},
173
175
  ): Line {
174
- return buildSelectablePanelLine(width, segments, options);
176
+ return buildSelectableViewLine(width, segments, options);
175
177
  }
@@ -1,17 +1,18 @@
1
1
  import type { Line } from '@pellux/goodvibes-sdk/platform/types';
2
2
  import { getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
3
- import { GLYPHS, UI_TONES } from '../renderer/ui-primitives.ts';
3
+ import { GLYPHS } from '../renderer/ui-primitives.ts';
4
+ import { activeUiTones, registerThemeRefresh } from '../renderer/theme.ts';
4
5
  // Build on the leaf primitives in ./polish-core.ts (palette + base line
5
6
  // builders). polish-core has no back-edge to this file, so there is no cycle.
6
- import { buildSelectablePanelLine, DEFAULT_PANEL_PALETTE } from './polish-core.ts';
7
- import type { PanelPalette, StyledPanelSegment } from './polish-core.ts';
7
+ import { buildSelectableViewLine, DEFAULT_VIEW_PALETTE } from './polish-core.ts';
8
+ import type { ViewPalette, StyledViewSegment } from './polish-core.ts';
8
9
 
9
10
  // ---------------------------------------------------------------------------
10
- // Shared formatting primitives (the toolkit panels standardize on).
11
+ // Shared formatting primitives (the toolkit views standardize on).
11
12
  //
12
13
  // All are display-width aware (via getDisplayWidth / truncateDisplay) so they
13
14
  // stay aligned across emoji, CJK, and other wide characters, never hand-roll
14
- // .slice()/.padEnd() truncation or alignment in panels.
15
+ // .slice()/.padEnd() truncation or alignment in views.
15
16
  // ---------------------------------------------------------------------------
16
17
 
17
18
  export type ColumnAlign = 'left' | 'right' | 'center';
@@ -64,7 +65,7 @@ export function buildAlignedRow(
64
65
  } = {},
65
66
  ): Line {
66
67
  const gap = options.gap ?? 1;
67
- const segments: StyledPanelSegment[] = [];
68
+ const segments: StyledViewSegment[] = [];
68
69
  const count = Math.min(cells.length, columns.length);
69
70
  for (let i = 0; i < count; i++) {
70
71
  const col = columns[i]!;
@@ -78,7 +79,7 @@ export function buildAlignedRow(
78
79
  dim: cell.dim,
79
80
  });
80
81
  }
81
- return buildSelectablePanelLine(width, segments, {
82
+ return buildSelectableViewLine(width, segments, {
82
83
  selected: options.selected,
83
84
  selectedBg: options.selectedBg,
84
85
  fillBg: options.fillBg,
@@ -101,27 +102,35 @@ interface BadgeSpec {
101
102
  readonly label: string;
102
103
  }
103
104
 
104
- const STATUS_BADGES: Record<StatusBadgeKind, BadgeSpec> = {
105
- pending: { glyph: GLYPHS.status.pending, fg: DEFAULT_PANEL_PALETTE.info, label: 'Pending' },
106
- running: { glyph: GLYPHS.status.active, fg: UI_TONES.state.active, label: 'Running' },
107
- completed: { glyph: GLYPHS.status.success, fg: DEFAULT_PANEL_PALETTE.good, label: 'Completed' },
108
- failed: { glyph: GLYPHS.status.failure, fg: DEFAULT_PANEL_PALETTE.bad, label: 'Failed' },
109
- cancelled: { glyph: GLYPHS.status.skipped, fg: DEFAULT_PANEL_PALETTE.dim, label: 'Cancelled' },
110
- blocked: { glyph: GLYPHS.status.blocked, fg: UI_TONES.state.blocked, label: 'Blocked' },
111
- review: { glyph: GLYPHS.status.review, fg: DEFAULT_PANEL_PALETTE.info, label: 'Review' },
112
- };
105
+ function buildStatusBadges(): Record<StatusBadgeKind, BadgeSpec> {
106
+ const tones = activeUiTones();
107
+ return {
108
+ pending: { glyph: GLYPHS.status.pending, fg: DEFAULT_VIEW_PALETTE.info, label: 'Pending' },
109
+ running: { glyph: GLYPHS.status.active, fg: tones.state.active, label: 'Running' },
110
+ completed: { glyph: GLYPHS.status.success, fg: DEFAULT_VIEW_PALETTE.good, label: 'Completed' },
111
+ failed: { glyph: GLYPHS.status.failure, fg: DEFAULT_VIEW_PALETTE.bad, label: 'Failed' },
112
+ cancelled: { glyph: GLYPHS.status.skipped, fg: DEFAULT_VIEW_PALETTE.dim, label: 'Cancelled' },
113
+ blocked: { glyph: GLYPHS.status.blocked, fg: tones.state.blocked, label: 'Blocked' },
114
+ review: { glyph: GLYPHS.status.review, fg: DEFAULT_VIEW_PALETTE.info, label: 'Review' },
115
+ };
116
+ }
117
+
118
+ // Rebuilt in place on every theme change (registered after the base palette's
119
+ // own refresher, so DEFAULT_VIEW_PALETTE already carries the new values).
120
+ const STATUS_BADGES: Record<StatusBadgeKind, BadgeSpec> = buildStatusBadges();
121
+ registerThemeRefresh(() => Object.assign(STATUS_BADGES, buildStatusBadges()));
113
122
 
114
123
  /**
115
124
  * Canonical status badge (glyph + label) with a consistent color per lifecycle
116
- * state. Returns StyledPanelSegment[], spread into a buildPanelLine call:
117
- * buildPanelLine(width, [[' ', C.label], ...buildStatusBadge('running')])
118
- * Supersedes ad-hoc per-panel status→color maps.
125
+ * state. Returns StyledViewSegment[], spread into a buildViewLine call:
126
+ * buildViewLine(width, [[' ', C.label], ...buildStatusBadge('running')])
127
+ * Supersedes ad-hoc per-view status→color maps.
119
128
  */
120
129
  export function buildStatusBadge(
121
130
  kind: StatusBadgeKind,
122
131
  label?: string,
123
132
  opts?: { glyph?: string; bg?: string; count?: number },
124
- ): StyledPanelSegment[] {
133
+ ): StyledViewSegment[] {
125
134
  const spec = STATUS_BADGES[kind];
126
135
  const glyph = opts?.glyph ?? spec.glyph;
127
136
  const text = label ?? spec.label;
@@ -143,12 +152,12 @@ export interface TreeItemSpec {
143
152
  /**
144
153
  * Build a hierarchical tree row: indentation + expand/collapse glyph + optional
145
154
  * icon + label, with right-aligned metadata. Replaces hand-rolled indentation
146
- * in file-explorer / symbol-outline / orchestration panels.
155
+ * in file-explorer / symbol-outline / orchestration views.
147
156
  */
148
157
  export function buildTreeRow(
149
158
  width: number,
150
159
  item: TreeItemSpec,
151
- palette: PanelPalette,
160
+ palette: ViewPalette,
152
161
  options: { selected?: boolean; selectedBg?: string; indentWidth?: number } = {},
153
162
  ): Line {
154
163
  const indent = ' '.repeat(Math.max(0, item.depth) * (options.indentWidth ?? 2));
@@ -168,7 +177,7 @@ export function buildTreeRow(
168
177
  const label = truncateDisplay(item.label, labelBudget);
169
178
  const usedW = prefixW + getDisplayWidth(label);
170
179
 
171
- const segments: StyledPanelSegment[] = [
180
+ const segments: StyledViewSegment[] = [
172
181
  { text: prefix, fg: palette.dim },
173
182
  { text: label, fg: item.labelColor ?? palette.value },
174
183
  ];
@@ -180,7 +189,7 @@ export function buildTreeRow(
180
189
  segments.push({ text: m.text, fg: m.fg });
181
190
  });
182
191
  }
183
- return buildSelectablePanelLine(width, segments, {
192
+ return buildSelectableViewLine(width, segments, {
184
193
  selected: options.selected,
185
194
  selectedBg: options.selectedBg ?? palette.selectBg,
186
195
  });
@@ -222,13 +231,13 @@ function resolveColumnWidths(width: number, columns: ReadonlyArray<TableColumn>,
222
231
 
223
232
  /**
224
233
  * Build a table: a header row plus aligned data rows. Replaces ad-hoc
225
- * row/column offset math in history/ledger/roster panels.
234
+ * row/column offset math in history/ledger/roster views.
226
235
  */
227
236
  export function buildTable(
228
237
  width: number,
229
238
  columns: ReadonlyArray<TableColumn>,
230
239
  rows: ReadonlyArray<TableRow>,
231
- palette: PanelPalette,
240
+ palette: ViewPalette,
232
241
  options: { gap?: number; selectedBg?: string } = {},
233
242
  ): Line[] {
234
243
  const gap = options.gap ?? 1;