@pellux/goodvibes-tui 2.0.21 → 2.1.1

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 (391) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/README.md +30 -29
  3. package/docs/README.md +8 -5
  4. package/docs/foundation-artifacts/operator-contract.json +1 -1
  5. package/package.json +28 -19
  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-reference.ts +2 -2
  52. package/src/input/command-registry.ts +77 -35
  53. package/src/input/commands/acp-runtime.ts +19 -9
  54. package/src/input/commands/channel-runtime.ts +2 -2
  55. package/src/input/commands/ci-runtime.ts +1 -1
  56. package/src/input/commands/codebase-runtime.ts +1 -1
  57. package/src/input/commands/config.ts +7 -1
  58. package/src/input/commands/context-window.ts +25 -19
  59. package/src/input/commands/control-room-runtime.ts +18 -18
  60. package/src/input/commands/conversation-runtime.ts +0 -1
  61. package/src/input/commands/cost-runtime.ts +29 -20
  62. package/src/input/commands/diff-runtime.ts +35 -181
  63. package/src/input/commands/eval.ts +1 -1
  64. package/src/input/commands/experience-runtime.ts +3 -7
  65. package/src/input/commands/fresh-conversation.ts +86 -0
  66. package/src/input/commands/git-runtime.ts +5 -22
  67. package/src/input/commands/graph-runtime.ts +1 -1
  68. package/src/input/commands/guidance-runtime.ts +1 -1
  69. package/src/input/commands/health-runtime.ts +8 -9
  70. package/src/input/commands/hooks-runtime.ts +2 -2
  71. package/src/input/commands/hosted-runtime.ts +3 -3
  72. package/src/input/commands/incident-runtime.ts +3 -3
  73. package/src/input/commands/intelligence-runtime.ts +6 -9
  74. package/src/input/commands/knowledge.ts +2 -2
  75. package/src/input/commands/legacy-panel-command.ts +57 -0
  76. package/src/input/commands/local-auth-runtime.ts +8 -9
  77. package/src/input/commands/local-provider-runtime.ts +7 -4
  78. package/src/input/commands/local-runtime.ts +12 -15
  79. package/src/input/commands/local-setup-review.ts +1 -1
  80. package/src/input/commands/local-setup.ts +13 -19
  81. package/src/input/commands/memory.ts +1 -1
  82. package/src/input/commands/notify-runtime.ts +9 -0
  83. package/src/input/commands/operator-runtime.ts +10 -12
  84. package/src/input/commands/permissions-provenance.ts +3 -3
  85. package/src/input/commands/planning-runtime.ts +7 -7
  86. package/src/input/commands/platform-sandbox-runtime.ts +2 -2
  87. package/src/input/commands/platform-services-runtime.ts +1 -1
  88. package/src/input/commands/policy-dispatch.ts +2 -2
  89. package/src/input/commands/policy.ts +3 -3
  90. package/src/input/commands/provider.ts +1 -1
  91. package/src/input/commands/remote-runtime.ts +1 -1
  92. package/src/input/commands/review-runtime.ts +39 -94
  93. package/src/input/commands/rewind-runtime.ts +81 -108
  94. package/src/input/commands/runtime-services.ts +15 -17
  95. package/src/input/commands/services-runtime.ts +2 -2
  96. package/src/input/commands/session-content.ts +21 -5
  97. package/src/input/commands/session-workflow.ts +13 -38
  98. package/src/input/commands/settings-sync-runtime.ts +2 -2
  99. package/src/input/commands/shell-core.ts +19 -4
  100. package/src/input/commands/skills-runtime.ts +2 -2
  101. package/src/input/commands/status-runtime.ts +13 -0
  102. package/src/input/commands/subscription-runtime.ts +1 -1
  103. package/src/input/commands/tasks-runtime.ts +2 -8
  104. package/src/input/commands/work-plan-runtime.ts +4 -4
  105. package/src/input/commands/workstream-attempts.ts +24 -47
  106. package/src/input/commands/workstream-runtime.ts +2 -2
  107. package/src/input/commands/worktree-runtime.ts +3 -3
  108. package/src/input/commands.ts +3 -1
  109. package/src/input/composer-editor.ts +44 -0
  110. package/src/input/concealed-input.ts +1 -1
  111. package/src/input/config-modal-types.ts +20 -8
  112. package/src/input/config-modal.ts +106 -105
  113. package/src/input/confirm-dialog-types.ts +28 -0
  114. package/src/input/confirm-dialog.ts +70 -0
  115. package/src/input/delete-key-policy.ts +3 -3
  116. package/src/input/feed-context-factory.ts +15 -16
  117. package/src/input/handler-command-route.ts +7 -42
  118. package/src/input/handler-content-actions.ts +16 -15
  119. package/src/input/handler-feed-routes.ts +26 -211
  120. package/src/input/handler-feed.ts +68 -95
  121. package/src/input/handler-interactions.ts +5 -3
  122. package/src/input/handler-modal-routes.ts +215 -216
  123. package/src/input/handler-modal-stack.ts +81 -11
  124. package/src/input/handler-modal-token-routes.ts +4 -0
  125. package/src/input/handler-onboarding.ts +0 -1
  126. package/src/input/handler-picker-routes.ts +65 -107
  127. package/src/input/handler-session-view-route.ts +149 -0
  128. package/src/input/handler-shortcuts.ts +16 -154
  129. package/src/input/handler-types.ts +4 -2
  130. package/src/input/handler-ui-state.ts +45 -15
  131. package/src/input/handler-work-tree-route.ts +123 -0
  132. package/src/input/handler.ts +25 -70
  133. package/src/input/keybindings.ts +16 -71
  134. package/src/input/masked-entry-modal.ts +117 -0
  135. package/src/input/mcp-workspace.ts +48 -17
  136. package/src/input/model-picker-filter.ts +8 -0
  137. package/src/input/model-picker-open.ts +99 -0
  138. package/src/input/model-picker-types.ts +1 -1
  139. package/src/input/model-picker.ts +92 -9
  140. package/src/input/notifications-modal.ts +123 -0
  141. package/src/input/overlay-filter.ts +26 -0
  142. package/src/input/profile-picker-modal.ts +37 -8
  143. package/src/input/search.ts +3 -3
  144. package/src/input/selection-modal-queue.ts +2 -0
  145. package/src/input/selection-modal.ts +28 -16
  146. package/src/input/session-picker-modal.ts +33 -8
  147. package/src/input/settings-modal-activation.ts +7 -2
  148. package/src/input/settings-modal-data.ts +11 -6
  149. package/src/input/settings-modal-types.ts +1 -1
  150. package/src/input/settings-modal.ts +20 -20
  151. package/src/input/submission-intent.ts +1 -1
  152. package/src/input/submission-router.ts +4 -9
  153. package/src/input/surface-modal-host.ts +110 -0
  154. package/src/input/theme-settings-actions.ts +82 -0
  155. package/src/input/usage-modal.ts +183 -0
  156. package/src/input/views.ts +97 -0
  157. package/src/main.ts +175 -165
  158. package/src/permissions/hunk-selection.ts +1 -1
  159. package/src/permissions/prompt-card.ts +511 -0
  160. package/src/permissions/prompt.ts +59 -622
  161. package/src/renderer/agents-modal.ts +310 -0
  162. package/src/renderer/autocomplete-overlay.ts +42 -220
  163. package/src/renderer/back-to-bottom.ts +80 -0
  164. package/src/renderer/block-actions-overlay.ts +30 -48
  165. package/src/renderer/bookmark-modal.ts +48 -79
  166. package/src/renderer/changes-modal.ts +325 -0
  167. package/src/renderer/code-block.ts +184 -106
  168. package/src/renderer/command-palette.ts +93 -0
  169. package/src/renderer/composer-fragments.ts +4 -4
  170. package/src/renderer/composer.ts +146 -0
  171. package/src/renderer/compositor.ts +26 -160
  172. package/src/renderer/config-modal.ts +185 -81
  173. package/src/renderer/confirm-dialog.ts +50 -0
  174. package/src/renderer/context-inspector.ts +116 -154
  175. package/src/renderer/context-status-hint.ts +4 -4
  176. package/src/renderer/conversation-layout.ts +20 -0
  177. package/src/renderer/conversation-overlays.ts +94 -84
  178. package/src/renderer/conversation-surface.ts +19 -38
  179. package/src/renderer/diff-tint.ts +98 -0
  180. package/src/renderer/file-picker-overlay.ts +63 -90
  181. package/src/renderer/footer-targets.ts +38 -0
  182. package/src/renderer/git-status.ts +3 -4
  183. package/src/renderer/header-line.ts +140 -117
  184. package/src/renderer/help-overlay.ts +133 -219
  185. package/src/renderer/history-search-overlay.ts +15 -12
  186. package/src/renderer/lane-graph/bead.ts +800 -0
  187. package/src/renderer/lane-graph/glyphs.ts +134 -0
  188. package/src/renderer/lane-graph/layout.ts +248 -0
  189. package/src/renderer/lane-graph/paint.ts +537 -0
  190. package/src/renderer/lane-graph/semantic-memo.ts +77 -0
  191. package/src/renderer/layout-engine.ts +2 -57
  192. package/src/renderer/layout.ts +7 -13
  193. package/src/renderer/markdown-inline.ts +2 -1
  194. package/src/renderer/markdown-table.ts +17 -12
  195. package/src/renderer/markdown.ts +92 -34
  196. package/src/renderer/masked-entry-modal.ts +78 -0
  197. package/src/renderer/mcp-workspace.ts +166 -203
  198. package/src/renderer/model-workspace.ts +381 -436
  199. package/src/renderer/notifications-modal.ts +116 -0
  200. package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
  201. package/src/renderer/process-view.ts +192 -0
  202. package/src/renderer/profile-picker-modal.ts +58 -110
  203. package/src/renderer/search-overlay.ts +6 -3
  204. package/src/renderer/selection-modal-overlay.ts +87 -327
  205. package/src/renderer/semantic-diff.ts +2 -2
  206. package/src/renderer/session-chips.ts +102 -0
  207. package/src/renderer/session-picker-modal.ts +177 -297
  208. package/src/renderer/settings-modal-connections.ts +14 -23
  209. package/src/renderer/settings-modal-context.ts +367 -0
  210. package/src/renderer/settings-modal-helpers.ts +6 -11
  211. package/src/renderer/settings-modal.ts +322 -726
  212. package/src/renderer/shell-surface.ts +277 -175
  213. package/src/renderer/shortcuts-overlay.ts +217 -0
  214. package/src/renderer/status-line.ts +475 -0
  215. package/src/renderer/surface-compose.ts +174 -0
  216. package/src/renderer/surface-kit-extra.ts +303 -0
  217. package/src/renderer/surface-kit-list.ts +366 -0
  218. package/src/renderer/surface-kit-parts.ts +360 -0
  219. package/src/renderer/surface-kit.ts +568 -0
  220. package/src/renderer/syntax-highlighter.ts +280 -197
  221. package/src/renderer/syntax-theme.ts +51 -0
  222. package/src/renderer/system-message.ts +0 -1
  223. package/src/renderer/term-caps.ts +42 -1
  224. package/src/renderer/terminal-bg-probe.ts +116 -19
  225. package/src/renderer/terminal-palette-probe.ts +322 -0
  226. package/src/renderer/terminal-palette-reader.ts +183 -0
  227. package/src/renderer/terminal-palette.ts +58 -0
  228. package/src/renderer/theme-mode-config.ts +16 -1
  229. package/src/renderer/theme.ts +234 -207
  230. package/src/renderer/thinking.ts +0 -1
  231. package/src/renderer/throbber.ts +188 -0
  232. package/src/renderer/toast-center.ts +119 -0
  233. package/src/renderer/ui-factory.ts +39 -602
  234. package/src/renderer/ui-primitives.ts +8 -25
  235. package/src/renderer/usage-modal.ts +353 -0
  236. package/src/renderer/user-message.ts +58 -0
  237. package/src/renderer/voice-capture-chip.ts +53 -0
  238. package/src/runtime/bootstrap-command-context.ts +0 -5
  239. package/src/runtime/bootstrap-command-parts.ts +38 -89
  240. package/src/runtime/bootstrap-core.ts +13 -15
  241. package/src/runtime/bootstrap-hook-bridge.ts +12 -21
  242. package/src/runtime/bootstrap-notifier-sync.ts +17 -1
  243. package/src/runtime/bootstrap-shell.ts +16 -50
  244. package/src/runtime/bootstrap.ts +14 -14
  245. package/src/runtime/client/fleet-union.ts +5 -5
  246. package/src/runtime/client/hosted-exit.ts +2 -2
  247. package/src/runtime/client/hosted-sessions.ts +1 -1
  248. package/src/runtime/client/spine-adoption.ts +1 -1
  249. package/src/runtime/context-accounting-source.ts +5 -4
  250. package/src/runtime/index.ts +1 -0
  251. package/src/runtime/notification-dispatch.ts +102 -29
  252. package/src/runtime/recovery-offer-wiring.ts +4 -1
  253. package/src/runtime/recovery-prompt.ts +31 -15
  254. package/src/runtime/runtime-services-types.ts +10 -6
  255. package/src/runtime/services.ts +17 -12
  256. package/src/runtime/session-continuity-hints.ts +2 -5
  257. package/src/runtime/session-inbound-inputs.ts +1 -1
  258. package/src/runtime/session-resume-liveness-confirm.ts +14 -2
  259. package/src/runtime/startup-typeahead-gate.ts +103 -0
  260. package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
  261. package/src/runtime/store/domains/index.ts +0 -8
  262. package/src/runtime/store/selectors/index.ts +0 -28
  263. package/src/runtime/store/state.ts +0 -8
  264. package/src/runtime/ui-services.ts +4 -6
  265. package/src/runtime/usage-tracker.ts +348 -0
  266. package/src/runtime/wrfc-persistence.ts +4 -4
  267. package/src/shell/blocking-input.ts +92 -2
  268. package/src/shell/recovery-input-helpers.ts +1 -1
  269. package/src/shell/session-views.ts +539 -0
  270. package/src/shell/status-report.ts +86 -0
  271. package/src/shell/transcript-scroll.ts +68 -0
  272. package/src/shell/ui-openers.ts +84 -172
  273. package/src/shell/view-openers.ts +205 -0
  274. package/src/tools/wrfc-agent-guard.ts +22 -2
  275. package/src/utils/format-duration.ts +4 -8
  276. package/src/utils/splash-lines.ts +9 -1
  277. package/src/utils/terminal-width.ts +56 -0
  278. package/src/verification/live-verifier.ts +0 -10
  279. package/src/version.ts +1 -1
  280. package/src/{panels → views}/agent-inspector-shared.ts +2 -2
  281. package/src/{panels → views}/builtin-modals.ts +22 -21
  282. package/src/views/builtin-views.ts +135 -0
  283. package/src/{panels → views}/confirm-state.ts +9 -9
  284. package/src/{panels → views}/diff-review-model.ts +10 -3
  285. package/src/{panels → views}/eval-registry.ts +1 -1
  286. package/src/{panels → views}/fleet-acts.ts +49 -85
  287. package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
  288. package/src/{panels → views}/fleet-gateway.ts +5 -5
  289. package/src/{panels → views}/fleet-observed-render.ts +16 -16
  290. package/src/{panels → views}/fleet-read-model.ts +9 -9
  291. package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
  292. package/src/{panels → views}/fleet-spawn.ts +33 -49
  293. package/src/{panels → views}/fleet-steer.ts +12 -12
  294. package/src/{panels → views}/fleet-stop.ts +4 -4
  295. package/src/{panels → views}/fleet-transcript.ts +26 -27
  296. package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
  297. package/src/{panels → views}/hosted-session-feed.ts +6 -6
  298. package/src/views/modal-surface-registry.ts +41 -0
  299. package/src/{panels → views}/modals/devices-modal.ts +2 -2
  300. package/src/{panels → views}/modals/hooks-modal.ts +13 -13
  301. package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
  302. package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
  303. package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
  304. package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
  305. package/src/{panels → views}/modals/memory-modal.ts +8 -8
  306. package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
  307. package/src/{panels → views}/modals/pairing-modal.ts +9 -9
  308. package/src/{panels → views}/modals/planning-modal.ts +15 -20
  309. package/src/{panels → views}/modals/plugins-modal.ts +6 -6
  310. package/src/{panels → views}/modals/policy-modal.ts +13 -5
  311. package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
  312. package/src/{panels → views}/modals/remote-modal.ts +3 -3
  313. package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
  314. package/src/{panels → views}/modals/security-modal.ts +22 -16
  315. package/src/{panels → views}/modals/services-modal.ts +3 -3
  316. package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
  317. package/src/{panels → views}/modals/skills-modal.ts +9 -9
  318. package/src/{panels → views}/modals/subscription-modal.ts +3 -3
  319. package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
  320. package/src/views/notifications-feed.ts +314 -0
  321. package/src/{panels → views}/polish-core.ts +38 -36
  322. package/src/{panels → views}/polish-tables.ts +35 -26
  323. package/src/{panels → views}/polish.ts +101 -95
  324. package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
  325. package/src/{panels → views}/provider-health-routes.ts +14 -14
  326. package/src/{panels → views}/search-focus.ts +8 -8
  327. package/src/{panels → views}/session-maintenance.ts +11 -11
  328. package/src/views/skills-discovery.ts +159 -0
  329. package/src/views/view-deps.ts +80 -0
  330. package/src/views/view-panel-adapter.ts +53 -0
  331. package/src/{panels → views}/workstream-graph-render.ts +1 -1
  332. package/src/core/conversation-fold.ts +0 -101
  333. package/src/core/conversation-turn-structure.ts +0 -464
  334. package/src/core/conversation-user-receipts.ts +0 -42
  335. package/src/input/commands/operator-panel-runtime.ts +0 -186
  336. package/src/input/panel-integration-actions.ts +0 -33
  337. package/src/input/panel-mouse-geometry.ts +0 -97
  338. package/src/input/panel-paste-flood-guard.ts +0 -86
  339. package/src/panels/base-panel.ts +0 -301
  340. package/src/panels/builtin/agent.ts +0 -27
  341. package/src/panels/builtin/development.ts +0 -92
  342. package/src/panels/builtin/knowledge.ts +0 -12
  343. package/src/panels/builtin/operations.ts +0 -177
  344. package/src/panels/builtin/session.ts +0 -81
  345. package/src/panels/builtin/shared.ts +0 -357
  346. package/src/panels/builtin-panels.ts +0 -28
  347. package/src/panels/cost-tracker-panel.ts +0 -746
  348. package/src/panels/diff-panel.ts +0 -797
  349. package/src/panels/diff-review-panel.ts +0 -395
  350. package/src/panels/expandable-list-panel.ts +0 -189
  351. package/src/panels/fleet-deep-link.ts +0 -31
  352. package/src/panels/fleet-panel.ts +0 -799
  353. package/src/panels/git-panel.ts +0 -800
  354. package/src/panels/hosted-session-panel.ts +0 -221
  355. package/src/panels/index.ts +0 -21
  356. package/src/panels/local-auth-panel.ts +0 -478
  357. package/src/panels/notifications-feed.ts +0 -127
  358. package/src/panels/notifications-panel.ts +0 -160
  359. package/src/panels/panel-confirm-overlay.ts +0 -72
  360. package/src/panels/panel-manager.ts +0 -791
  361. package/src/panels/plugins-panel.ts +0 -371
  362. package/src/panels/scrollable-list-panel.ts +0 -498
  363. package/src/panels/skills-panel.ts +0 -454
  364. package/src/panels/token-budget-panel.ts +0 -688
  365. package/src/panels/types.ts +0 -191
  366. package/src/panels/wrfc-panel-format.ts +0 -133
  367. package/src/renderer/diff-view.ts +0 -134
  368. package/src/renderer/fleet-tab-strip.ts +0 -56
  369. package/src/renderer/footer-tips.ts +0 -53
  370. package/src/renderer/fullscreen-primitives.ts +0 -146
  371. package/src/renderer/fullscreen-workspace.ts +0 -194
  372. package/src/renderer/hint-grammar.ts +0 -52
  373. package/src/renderer/modal-factory.ts +0 -480
  374. package/src/renderer/overlay-box.ts +0 -154
  375. package/src/renderer/overlay-viewport.ts +0 -104
  376. package/src/renderer/panel-composite.ts +0 -145
  377. package/src/renderer/panel-workspace-bar.ts +0 -54
  378. package/src/renderer/process-indicator.ts +0 -163
  379. package/src/renderer/tab-strip.ts +0 -222
  380. package/src/renderer/text-layout.ts +0 -31
  381. package/src/renderer/tool-call.ts +0 -276
  382. package/src/renderer/tool-result-expanded-lines.ts +0 -48
  383. package/src/renderer/tool-result-summary.ts +0 -219
  384. package/src/runtime/diagnostics/panels/index.ts +0 -21
  385. package/src/runtime/diagnostics/panels/ops.ts +0 -156
  386. package/src/runtime/perf/panel-contracts.ts +0 -32
  387. package/src/runtime/store/domains/panels.ts +0 -117
  388. package/src/verification/verification-ledger.ts +0 -707
  389. /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
  390. /package/src/{panels → views}/modals/modal-theme.ts +0 -0
  391. /package/src/{panels → views}/provider-health-domains.ts +0 -0
@@ -1,37 +1,40 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types';
2
- import { fitDisplay, getDisplayWidth, truncateDisplay, wrapText } from '../../utils/terminal-width.ts';
1
+ /**
2
+ * renderOnboardingWizard, the onboarding wizard on the modal surface kit.
3
+ *
4
+ * The wizard owns the screen (the shell hides its header and footer), so the
5
+ * surface takes the full width minus 1 column per side and the full height:
6
+ * the gradient ▄ cap on the first row, the surface ▀ cap on the last, 4
7
+ * columns of inner padding, the ✦ title row with the mode, the step position
8
+ * and the changed-screen count, and keycap hints on the second-to-last row.
9
+ * No box-drawing frames.
10
+ *
11
+ * Wide screens: the steps as a kit list on the left (the current step is the
12
+ * gradient row; ✓ complete, ◈ changed, • untouched, completion right-aligned)
13
+ * with the step summary under it, and on the right the step title, its
14
+ * description, the field rows and a focus box that wraps the selected
15
+ * field's hint in full. Narrow screens drop the step list and keep the rest.
16
+ */
17
+
18
+ import { activeTokens, activeUiTones } from '../theme.ts';
3
19
  import {
4
- createOverlayBoxLayout,
5
- createOverlayContentLine,
6
- createOverlayFilledBorderLine,
7
- DEFAULT_OVERLAY_PALETTE,
8
- OVERLAY_GLYPHS,
9
- putOverlayText,
10
- } from '../overlay-box.ts';
11
- import { clamp, drawVerticalRule, fillWidth } from '../fullscreen-primitives.ts';
12
- import { UI_TONES } from '../ui-primitives.ts';
13
- import {
14
- getOnboardingWizardBodyRows,
15
- getOnboardingWizardVisibleFieldCount,
16
- type OnboardingWizardController,
17
- type OnboardingWizardFieldDefinition,
18
- type OnboardingWizardStepDefinition,
20
+ beginModal,
21
+ finishModal,
22
+ scrollCountText,
23
+ type KitHint,
24
+ type SurfaceLayer,
25
+ } from '../surface-kit.ts';
26
+ import { drawList, type KitRow } from '../surface-kit-list.ts';
27
+ import { inset } from '../surface-kit-parts.ts';
28
+ import { drawTextBlock, textBlockHeight, type TextLine } from '../surface-kit-extra.ts';
29
+ import type {
30
+ OnboardingWizardController,
31
+ OnboardingWizardFieldDefinition,
19
32
  } from '../../input/onboarding/onboarding-wizard.ts';
20
33
 
21
- type RenderedFieldRow =
22
- | { readonly kind: 'empty' }
23
- | { readonly kind: 'moreAbove'; readonly text: string }
24
- | { readonly kind: 'moreBelow'; readonly text: string }
25
- | {
26
- readonly kind: 'field';
27
- readonly field: OnboardingWizardFieldDefinition;
28
- readonly absoluteIndex: number;
29
- };
30
-
31
34
  function modeLabel(mode: OnboardingWizardController['mode']): string {
32
- if (mode === 'edit') return 'Edit existing';
33
- if (mode === 'reopen') return 'Reopen review';
34
- return 'New setup';
35
+ if (mode === 'edit') return 'edit existing';
36
+ if (mode === 'reopen') return 'reopen review';
37
+ return 'new setup';
35
38
  }
36
39
 
37
40
  function changedScreensLabel(wizard: OnboardingWizardController): string {
@@ -40,590 +43,177 @@ function changedScreensLabel(wizard: OnboardingWizardController): string {
40
43
  return `${wizard.dirtyStepCount} changed screens`;
41
44
  }
42
45
 
43
- function stepGlyph(
44
- wizard: OnboardingWizardController,
45
- step: OnboardingWizardStepDefinition,
46
- stepIndex: number,
47
- ): { readonly glyph: string; readonly fg: string } {
48
- if (stepIndex === wizard.stepIndex) {
49
- return { glyph: OVERLAY_GLYPHS.selected, fg: UI_TONES.state.active };
50
- }
51
-
52
- const total = wizard.getStepFieldCount(stepIndex);
53
- const completed = wizard.getCompletedFieldCount(stepIndex);
54
- if (wizard.isStepDirty(stepIndex)) {
55
- return { glyph: '◈', fg: UI_TONES.state.warn };
56
- }
57
- if (total > 0 && completed === total) {
58
- return { glyph: '✓', fg: UI_TONES.state.good };
59
- }
60
- return { glyph: '•', fg: UI_TONES.fg.muted };
46
+ function stepRows(wizard: OnboardingWizardController): KitRow[] {
47
+ const tones = activeUiTones();
48
+ return wizard.steps.map((step, index): KitRow => {
49
+ const total = wizard.getStepFieldCount(index);
50
+ const completed = wizard.getCompletedFieldCount(index);
51
+ const dirty = wizard.isStepDirty(index);
52
+ const complete = total > 0 && completed === total;
53
+ return {
54
+ label: `${index + 1}. ${step.shortLabel}`,
55
+ mark: dirty ? '◈' : complete ? '✓' : '•',
56
+ markFg: dirty ? tones.state.warn : complete ? tones.state.good : tones.fg.muted,
57
+ right: `${completed}/${total}`,
58
+ rightFg: dirty ? tones.state.warn : undefined,
59
+ selected: index === wizard.stepIndex,
60
+ };
61
+ });
61
62
  }
62
63
 
63
- function fieldBadgeTone(
64
- wizard: OnboardingWizardController,
65
- field: OnboardingWizardFieldDefinition,
66
- ): string {
67
- if (field.kind === 'status') return UI_TONES.state.info;
68
- if (field.kind === 'modelPicker') return UI_TONES.state.info;
64
+ function fieldBadgeTone(wizard: OnboardingWizardController, field: OnboardingWizardFieldDefinition): string {
65
+ const tones = activeUiTones();
66
+ if (field.kind === 'status' || field.kind === 'modelPicker') return tones.state.info;
69
67
  if (field.kind === 'acknowledgement') {
70
68
  const label = wizard.getFieldValueLabel(field);
71
- return label === 'Accepted' ? UI_TONES.state.good : label === 'Pending' ? UI_TONES.state.warn : UI_TONES.fg.muted;
72
- }
73
- if (field.kind === 'checklist') {
74
- return wizard.getFieldValue(field) ? UI_TONES.state.good : UI_TONES.fg.muted;
69
+ return label === 'Accepted' ? tones.state.good : label === 'Pending' ? tones.state.warn : tones.fg.muted;
75
70
  }
76
- if (field.kind === 'radio') return UI_TONES.state.active;
71
+ if (field.kind === 'checklist') return wizard.getFieldValue(field) ? tones.state.good : tones.fg.muted;
72
+ if (field.kind === 'radio') return tones.state.active;
77
73
  if (field.kind === 'text' || field.kind === 'masked') {
78
- const missingRequired = wizard.getFieldValueLabel(field) === 'Missing';
79
- if (missingRequired) return UI_TONES.state.warn;
80
- if (field.kind === 'masked') return UI_TONES.state.warn;
74
+ if (wizard.getFieldValueLabel(field) === 'Missing') return tones.state.warn;
75
+ if (field.kind === 'masked') return tones.state.warn;
81
76
  }
82
- return UI_TONES.fg.secondary;
77
+ return tones.fg.secondary;
83
78
  }
84
79
 
85
- function buildFieldRows(
86
- wizard: OnboardingWizardController,
87
- visibleFields: number,
88
- capacity: number,
89
- ): readonly RenderedFieldRow[] {
90
- wizard.ensureSelectionVisible(visibleFields);
91
- const fields = wizard.currentStep.fields;
92
- const rows: RenderedFieldRow[] = [];
93
- if (fields.length === 0 || capacity <= 0) return rows;
94
-
95
- const allRows: RenderedFieldRow[] = [];
96
- fields.forEach((field, absoluteIndex) => {
97
- const spacerRows = Math.max(0, field.spacerBeforeRows ?? 0);
98
- for (let index = 0; index < spacerRows; index += 1) {
99
- allRows.push({ kind: 'empty' });
100
- }
101
- allRows.push({ kind: 'field', field, absoluteIndex });
102
- });
103
-
104
- const selectedFieldIndex = wizard.getSelectedFieldIndex();
105
- const selectedRowIndex = Math.max(0, allRows.findIndex((row) => row.kind === 'field' && row.absoluteIndex === selectedFieldIndex));
106
- const scrollFieldIndex = wizard.scrollOffsets[wizard.stepIndex] ?? 0;
107
- const scrollRowIndex = allRows.findIndex((row) => row.kind === 'field' && row.absoluteIndex === scrollFieldIndex);
108
- const maxStart = Math.max(0, allRows.length - capacity);
109
- let start = clamp(scrollRowIndex >= 0 ? scrollRowIndex : 0, 0, maxStart);
110
-
111
- if (selectedRowIndex < start) start = selectedRowIndex;
112
- if (selectedRowIndex >= start + capacity) start = selectedRowIndex - capacity + 1;
113
- start = clamp(start, 0, maxStart);
114
-
115
- if (start > 0 && selectedRowIndex === start) start = Math.max(0, start - 1);
116
- if (start + capacity < allRows.length && selectedRowIndex === start + capacity - 1) {
117
- start = Math.min(maxStart, start + 1);
118
- }
119
-
120
- rows.push(...allRows.slice(start, start + capacity));
121
- if (start > 0 && rows.length > 0) {
122
- rows[0] = {
123
- kind: 'moreAbove',
124
- text: `${OVERLAY_GLYPHS.moreAbove} ${start} more above`,
125
- };
126
- }
127
-
128
- const hiddenBelow = Math.max(0, allRows.length - (start + capacity));
129
- if (hiddenBelow > 0 && rows.length > 0) {
130
- rows[rows.length - 1] = {
131
- kind: 'moreBelow',
132
- text: `${OVERLAY_GLYPHS.moreBelow} ${hiddenBelow} more below`,
133
- };
134
- }
135
-
136
- while (rows.length < capacity) rows.push({ kind: 'empty' });
137
- return rows.slice(0, capacity);
80
+ function fieldMark(wizard: OnboardingWizardController, field: OnboardingWizardFieldDefinition): string | undefined {
81
+ if (wizard.isFieldDirty(field.id)) return '◇';
82
+ if (field.kind === 'checklist' || field.kind === 'acknowledgement') return wizard.getFieldValue(field) ? '✓' : '□';
83
+ if (field.kind === 'action') return '▶';
84
+ if (field.kind === 'radio') return '◉';
85
+ return undefined;
138
86
  }
139
87
 
140
- function fieldHint(
141
- wizard: OnboardingWizardController,
142
- field: OnboardingWizardFieldDefinition,
143
- selected: boolean,
144
- ): string {
145
- if (
146
- selected
147
- && wizard.isEditingTextField()
148
- && wizard.editingFieldId === field.id
149
- && (field.kind === 'text' || field.kind === 'masked')
150
- ) {
151
- const rawValue = wizard.editBuffer.length > 0 ? wizard.editBuffer : field.placeholder;
152
- const editingValue = field.kind === 'masked' && wizard.editBuffer.length > 0
153
- ? '•'.repeat(Math.min(12, Math.max(4, wizard.editBuffer.length)))
154
- : rawValue;
155
- return `Editing: ${editingValue}█`;
156
- }
157
-
158
- if (selected && field.kind === 'modelPicker') return `${field.hint} Press Enter to open picker.`;
159
- if (selected && field.kind === 'text') return `${field.hint} Press Enter to edit inline.`;
160
- if (selected && field.kind === 'masked') return `${field.hint} Press Enter to edit inline.`;
161
- return field.hint;
162
- }
163
-
164
- function fieldRowPrefix(
165
- wizard: OnboardingWizardController,
166
- field: OnboardingWizardFieldDefinition,
167
- selected: boolean,
168
- ): string {
169
- if (selected) return `${OVERLAY_GLYPHS.selected} `;
170
- if (wizard.isFieldDirty(field.id)) return '◇ ';
171
- if (field.kind === 'checklist') return (wizard.getFieldValue(field) as boolean) ? '✓ ' : '□ ';
172
- if (field.kind === 'acknowledgement') return (wizard.getFieldValue(field) as boolean) ? '✓ ' : '□ ';
173
- if (field.kind === 'action') return '▶ ';
174
- if (field.kind === 'radio') return '◉ ';
175
- return ' ';
88
+ /** Field rows; spacer rows before a field are kept as blank rows. */
89
+ function fieldRows(wizard: OnboardingWizardController): { rows: KitRow[]; scrollStart: number } {
90
+ const t = activeTokens();
91
+ const rows: KitRow[] = [];
92
+ const selectedIndex = wizard.getSelectedFieldIndex();
93
+ const scrollField = wizard.scrollOffsets[wizard.stepIndex] ?? 0;
94
+ let scrollStart = 0;
95
+ wizard.currentStep.fields.forEach((field, index) => {
96
+ for (let k = 0; k < Math.max(0, field.spacerBeforeRows ?? 0); k++) rows.push({ label: '' });
97
+ if (index === scrollField) scrollStart = rows.length;
98
+ rows.push({
99
+ label: field.label,
100
+ mark: fieldMark(wizard, field),
101
+ markFg: wizard.isFieldDirty(field.id) ? t.warning : t.brand,
102
+ right: `[${wizard.getFieldValueLabel(field)}]`,
103
+ rightFg: fieldBadgeTone(wizard, field),
104
+ selected: index === selectedIndex,
105
+ });
106
+ });
107
+ return { rows, scrollStart };
176
108
  }
177
109
 
178
- function selectedFieldText(wizard: OnboardingWizardController): {
179
- readonly title: string;
180
- readonly hint: string;
181
- } {
110
+ /** The focus box's lines: what the selected field is and what it does, in full. */
111
+ function focusLines(wizard: OnboardingWizardController): TextLine[] {
112
+ const t = activeTokens();
182
113
  if (wizard.isEditingTextField() && wizard.editingFieldId !== null) {
183
- const editingField = wizard.getFieldById(wizard.editingFieldId);
184
- if (editingField) {
185
- return {
186
- title: `Editing: ${editingField.label}`,
187
- hint: fieldHint(wizard, editingField, true),
188
- };
114
+ const field = wizard.getFieldById(wizard.editingFieldId);
115
+ if (field && (field.kind === 'text' || field.kind === 'masked')) {
116
+ const raw = wizard.editBuffer.length > 0 ? wizard.editBuffer : field.placeholder;
117
+ const shown = field.kind === 'masked' && wizard.editBuffer.length > 0
118
+ ? '•'.repeat(Math.min(12, Math.max(4, wizard.editBuffer.length)))
119
+ : raw;
120
+ return [
121
+ { text: `Editing: ${field.label}`, style: { fg: t.text, bold: true } },
122
+ { text: `${shown}▏`, style: { fg: t.text } },
123
+ { text: field.hint, style: { fg: t.textMuted } },
124
+ ];
189
125
  }
190
126
  }
191
-
192
127
  const field = wizard.getSelectedField();
193
- if (!field) {
194
- return {
195
- title: 'Selected: none',
196
- hint: 'No selectable row is active on this screen.',
197
- };
198
- }
199
-
200
- return {
201
- title: `Selected: ${field.label} [${wizard.getFieldValueLabel(field)}]`,
202
- hint: fieldHint(wizard, field, true),
203
- };
204
- }
205
-
206
- function renderFieldRow(
207
- line: Line,
208
- wizard: OnboardingWizardController,
209
- fieldRow: RenderedFieldRow,
210
- startX: number,
211
- width: number,
212
- ): void {
213
- if (fieldRow.kind === 'empty') return;
214
-
215
- if (fieldRow.kind === 'moreAbove' || fieldRow.kind === 'moreBelow') {
216
- putOverlayText(line, startX + 1, width - 2, truncateDisplay(fieldRow.text, width - 2), {
217
- fg: UI_TONES.fg.muted,
218
- bg: UI_TONES.bg.base,
219
- dim: true,
220
- });
221
- return;
222
- }
223
-
224
- const selected = fieldRow.absoluteIndex === wizard.getSelectedFieldIndex();
225
- const field = fieldRow.field;
226
- const fieldBg = selected ? DEFAULT_OVERLAY_PALETTE.selectedBg : UI_TONES.bg.base;
227
- fillWidth(line, startX, width, fieldBg);
228
-
229
- const badge = truncateDisplay(`[${wizard.getFieldValueLabel(field)}]`, Math.max(8, Math.floor(width * 0.34)));
230
- const badgeWidth = getDisplayWidth(badge);
231
- const labelWidth = Math.max(0, width - badgeWidth - 4);
232
- const prefix = fieldRowPrefix(wizard, field, selected);
233
-
234
- putOverlayText(line, startX + 1, labelWidth, truncateDisplay(`${prefix}${field.label}`, labelWidth), {
235
- fg: UI_TONES.fg.primary,
236
- bg: fieldBg,
237
- bold: selected,
238
- });
239
- putOverlayText(line, startX + width - badgeWidth - 1, badgeWidth, badge, {
240
- fg: fieldBadgeTone(wizard, field),
241
- bg: fieldBg,
242
- bold: selected,
243
- });
244
- }
245
-
246
- function footerText(wizard: OnboardingWizardController): string {
247
- if (wizard.isEditingTextField()) {
248
- return '[Enter] Save value [Esc] Cancel edit [Backspace] Delete char [Del/Ctrl+U] Clear value';
249
- }
250
-
251
- return '[Enter] Toggle/open [Esc] Close [Tab/Shift+Tab] Screen [↑↓] Move [Del/Ctrl+U] Clear input';
128
+ if (!field) return [{ text: 'No selectable row is active on this screen.', style: { fg: t.textMuted } }];
129
+ let hint = field.hint;
130
+ if (field.kind === 'modelPicker') hint = `${hint} Press Enter to open the picker.`;
131
+ if (field.kind === 'text' || field.kind === 'masked') hint = `${hint} Press Enter to edit inline.`;
132
+ return [
133
+ { text: `Focus: ${field.label} [${wizard.getFieldValueLabel(field)}]`, style: { fg: t.text, bold: true } },
134
+ { text: hint, style: { fg: t.textMuted } },
135
+ ];
252
136
  }
253
137
 
254
- function controlsText(wizard: OnboardingWizardController): string {
138
+ function hintsFor(wizard: OnboardingWizardController): KitHint[] {
255
139
  if (wizard.isEditingTextField()) {
256
- return 'Controls: Enter saves this value, Esc cancels editing, Backspace deletes one character, Delete or Ctrl+U clears the field.';
257
- }
258
- return 'Controls: Enter or Space changes the selected row; Delete or Ctrl+U clears selected text inputs; Tab/Shift+Tab changes screens; arrows move.';
259
- }
260
-
261
- function renderWideLayout(
262
- wizard: OnboardingWizardController,
263
- width: number,
264
- viewportHeight: number,
265
- layout: ReturnType<typeof createOverlayBoxLayout>,
266
- ): Line[] {
267
- const lines: Line[] = [];
268
- const bodyRows = getOnboardingWizardBodyRows(viewportHeight);
269
- const visibleFields = getOnboardingWizardVisibleFieldCount(viewportHeight);
270
- const currentStep = wizard.currentStep;
271
- const borderFg = DEFAULT_OVERLAY_PALETTE.borderFg;
272
- const headerBg = UI_TONES.bg.title;
273
- const railBg = UI_TONES.bg.section;
274
- const bodyBg = UI_TONES.bg.base;
275
- const summaryBg = UI_TONES.bg.summary;
276
- const innerLeft = layout.margin + 1;
277
- const availableInner = layout.innerWidth - 2;
278
- const leftWidthBase = layout.innerWidth >= 150 ? 32 : layout.innerWidth >= 108 ? 28 : 24;
279
- const rightWidthBase = layout.innerWidth >= 150 ? 34 : layout.innerWidth >= 108 ? 32 : 24;
280
- const minCenterWidth = layout.innerWidth >= 120 ? 48 : 40;
281
- let leftWidth = Math.min(leftWidthBase, Math.max(20, availableInner - minCenterWidth - 12));
282
- let rightWidth = Math.min(rightWidthBase, Math.max(22, availableInner - minCenterWidth - leftWidth));
283
- let centerWidth = layout.innerWidth - leftWidth - rightWidth - 2;
284
-
285
- if (centerWidth < minCenterWidth) {
286
- const deficit = minCenterWidth - centerWidth;
287
- const leftCut = Math.min(Math.max(0, leftWidth - 20), Math.ceil(deficit / 2));
288
- leftWidth -= leftCut;
289
- rightWidth -= Math.min(Math.max(0, rightWidth - 22), deficit - leftCut);
290
- centerWidth = layout.innerWidth - leftWidth - rightWidth - 2;
291
- }
292
-
293
- const leftStart = innerLeft;
294
- const leftSeparatorX = leftStart + leftWidth;
295
- const centerStart = leftSeparatorX + 1;
296
- const rightSeparatorX = centerStart + centerWidth;
297
- const rightStart = rightSeparatorX + 1;
298
- const summaryLines = [
299
- currentStep.summaryTitle,
300
- ...currentStep.summaryLines.slice(0, 2),
301
- `Fields ${wizard.getCompletedFieldCount(wizard.stepIndex)}/${wizard.getStepFieldCount(wizard.stepIndex)} complete`,
302
- changedScreensLabel(wizard),
303
- ];
304
- const centerTextWidth = Math.max(18, centerWidth - 2);
305
- const descriptionLines = wrapText(currentStep.description, centerTextWidth).slice(0, 2);
306
- const selectedText = selectedFieldText(wizard);
307
- // The selected field's own hint is the direct analog of the reported
308
- // defect (descriptive text explaining what the currently-focused option
309
- // does), some hints run past 400 characters, e.g. security-caveat text.
310
- // It is never clipped to a single row: it wraps onto as many lines as it
311
- // needs, and the field list below starts after it, growing/shrinking the
312
- // field-list area rather than losing hint text. Title/description/controls
313
- // stay fixed-row (short chrome strings, not the field-specific
314
- // explanation), matching prior layout exactly when the hint fits on one line.
315
- const hintLines = wrapText(selectedText.hint, centerTextWidth);
316
- const fieldStartRow = 5 + hintLines.length;
317
- const fieldRows = buildFieldRows(wizard, visibleFields, Math.max(0, bodyRows - fieldStartRow));
318
-
319
- const topLine = createOverlayFilledBorderLine(
320
- width,
321
- layout,
322
- OVERLAY_GLYPHS.topLeft,
323
- OVERLAY_GLYPHS.horizontal,
324
- OVERLAY_GLYPHS.topRight,
325
- borderFg,
326
- headerBg,
327
- );
328
- putOverlayText(topLine, layout.margin + 2, layout.width - 4, 'Onboarding Wizard', {
329
- fg: UI_TONES.fg.primary,
330
- bg: headerBg,
331
- bold: true,
332
- });
333
- const meta = `${modeLabel(wizard.mode)} ${wizard.stepIndex + 1}/${wizard.steps.length} ${changedScreensLabel(wizard)}`;
334
- putOverlayText(topLine, Math.max(layout.margin + 2, layout.margin + layout.width - getDisplayWidth(meta) - 3), layout.width - 4, meta, {
335
- fg: UI_TONES.fg.secondary,
336
- bg: headerBg,
337
- });
338
- lines.push(topLine);
339
-
340
- const headerLine = createOverlayContentLine(width, layout, borderFg, headerBg);
341
- fillWidth(headerLine, leftStart, leftWidth, railBg);
342
- fillWidth(headerLine, centerStart, centerWidth, headerBg);
343
- fillWidth(headerLine, rightStart, rightWidth, summaryBg);
344
- drawVerticalRule(headerLine, leftSeparatorX, borderFg, headerBg);
345
- drawVerticalRule(headerLine, rightSeparatorX, borderFg, headerBg);
346
- putOverlayText(headerLine, leftStart + 1, leftWidth - 2, 'Steps', {
347
- fg: UI_TONES.fg.secondary,
348
- bg: railBg,
349
- bold: true,
350
- });
351
- putOverlayText(headerLine, centerStart + 1, centerWidth - 2, truncateDisplay(currentStep.title, centerWidth - 2), {
352
- fg: UI_TONES.state.active,
353
- bg: headerBg,
354
- bold: true,
355
- });
356
- putOverlayText(headerLine, rightStart + 1, rightWidth - 2, 'Summary', {
357
- fg: UI_TONES.fg.secondary,
358
- bg: summaryBg,
359
- bold: true,
360
- });
361
- lines.push(headerLine);
362
-
363
- lines.push(createOverlayFilledBorderLine(
364
- width,
365
- layout,
366
- OVERLAY_GLYPHS.teeLeft,
367
- OVERLAY_GLYPHS.horizontal,
368
- OVERLAY_GLYPHS.teeRight,
369
- borderFg,
370
- bodyBg,
371
- ));
372
-
373
- for (let row = 0; row < bodyRows; row += 1) {
374
- const line = createOverlayContentLine(width, layout, borderFg, bodyBg);
375
- fillWidth(line, leftStart, leftWidth, railBg);
376
- fillWidth(line, centerStart, centerWidth, bodyBg);
377
- fillWidth(line, rightStart, rightWidth, summaryBg);
378
- drawVerticalRule(line, leftSeparatorX, borderFg);
379
- drawVerticalRule(line, rightSeparatorX, borderFg);
380
-
381
- if (row === 0) {
382
- putOverlayText(line, centerStart + 1, centerWidth - 2, truncateDisplay(currentStep.title, centerWidth - 2), {
383
- fg: UI_TONES.fg.primary,
384
- bg: bodyBg,
385
- bold: true,
386
- });
387
- } else if (row === 1) {
388
- putOverlayText(line, centerStart + 1, centerWidth - 2, descriptionLines[0] ?? '', {
389
- fg: UI_TONES.fg.secondary,
390
- bg: bodyBg,
391
- });
392
- } else if (row === 2) {
393
- putOverlayText(line, centerStart + 1, centerWidth - 2, descriptionLines[1] ?? '', {
394
- fg: UI_TONES.fg.secondary,
395
- bg: bodyBg,
396
- });
397
- } else if (row === 3) {
398
- fillWidth(line, centerStart, centerWidth, railBg);
399
- putOverlayText(line, centerStart + 1, centerWidth - 2, truncateDisplay(controlsText(wizard), centerWidth - 2), {
400
- fg: UI_TONES.state.info,
401
- bg: railBg,
402
- });
403
- } else if (row === 4) {
404
- fillWidth(line, centerStart, centerWidth, DEFAULT_OVERLAY_PALETTE.selectedBg);
405
- putOverlayText(line, centerStart + 1, centerWidth - 2, truncateDisplay(`Focus: ${selectedText.title.replace(/^Selected: /, '')}`, centerWidth - 2), {
406
- fg: UI_TONES.fg.primary,
407
- bg: DEFAULT_OVERLAY_PALETTE.selectedBg,
408
- bold: true,
409
- });
410
- } else if (row < fieldStartRow) {
411
- fillWidth(line, centerStart, centerWidth, DEFAULT_OVERLAY_PALETTE.selectedBg);
412
- putOverlayText(line, centerStart + 1, centerWidth - 2, hintLines[row - 5] ?? '', {
413
- fg: UI_TONES.fg.secondary,
414
- bg: DEFAULT_OVERLAY_PALETTE.selectedBg,
415
- });
416
- } else {
417
- renderFieldRow(line, wizard, fieldRows[row - fieldStartRow] ?? { kind: 'empty' }, centerStart, centerWidth);
418
- }
419
-
420
- const step = wizard.steps[row] ?? null;
421
- if (step) {
422
- const stepState = stepGlyph(wizard, step, row);
423
- const completion = `${wizard.getCompletedFieldCount(row)}/${wizard.getStepFieldCount(row)}`;
424
- const completionWidth = getDisplayWidth(completion);
425
- const stepLabelWidth = Math.max(0, leftWidth - completionWidth - 4);
426
- putOverlayText(line, leftStart + 1, stepLabelWidth, truncateDisplay(`${stepState.glyph} ${row + 1}. ${step.shortLabel}`, stepLabelWidth), {
427
- fg: stepState.fg,
428
- bg: railBg,
429
- bold: row === wizard.stepIndex,
430
- });
431
- putOverlayText(line, Math.max(leftStart + 1, leftStart + leftWidth - completionWidth - 2), completionWidth, completion, {
432
- fg: wizard.isStepDirty(row) ? UI_TONES.state.warn : UI_TONES.fg.muted,
433
- bg: railBg,
434
- });
435
- }
436
-
437
- const summaryText = summaryLines[row];
438
- if (summaryText) {
439
- putOverlayText(line, rightStart + 1, rightWidth - 2, truncateDisplay(summaryText, rightWidth - 2), {
440
- fg: row === 0 ? UI_TONES.state.info : UI_TONES.fg.secondary,
441
- bg: summaryBg,
442
- bold: row === 0,
443
- dim: row > 0,
444
- });
445
- }
446
-
447
- lines.push(line);
140
+ return [['⏎', 'save'], ['esc', 'cancel edit'], ['bksp', 'delete char'], ['del', 'clear']];
448
141
  }
449
-
450
- lines.push(createOverlayFilledBorderLine(
451
- width,
452
- layout,
453
- OVERLAY_GLYPHS.teeLeft,
454
- OVERLAY_GLYPHS.horizontal,
455
- OVERLAY_GLYPHS.teeRight,
456
- borderFg,
457
- bodyBg,
458
- ));
459
-
460
- const footer = createOverlayFilledBorderLine(
461
- width,
462
- layout,
463
- OVERLAY_GLYPHS.bottomLeft,
464
- OVERLAY_GLYPHS.horizontal,
465
- OVERLAY_GLYPHS.bottomRight,
466
- borderFg,
467
- headerBg,
468
- );
469
- putOverlayText(footer, layout.margin + 2, layout.width - 4, truncateDisplay(footerText(wizard), layout.width - 4), {
470
- fg: UI_TONES.fg.muted,
471
- bg: headerBg,
472
- dim: true,
473
- });
474
- lines.push(footer);
475
-
476
- return lines.slice(0, viewportHeight);
142
+ return [['↑↓', 'move'], ['⏎', 'change'], ['tab', 'next screen'], ['shift+tab', 'back'], ['del', 'clear']];
477
143
  }
478
144
 
479
- function renderCollapsedLayout(
480
- wizard: OnboardingWizardController,
481
- width: number,
482
- viewportHeight: number,
483
- layout: ReturnType<typeof createOverlayBoxLayout>,
484
- ): Line[] {
485
- const lines: Line[] = [];
486
- const bodyRows = getOnboardingWizardBodyRows(viewportHeight);
487
- const visibleFields = getOnboardingWizardVisibleFieldCount(viewportHeight);
488
- const currentStep = wizard.currentStep;
489
- const borderFg = DEFAULT_OVERLAY_PALETTE.borderFg;
490
- const headerBg = UI_TONES.bg.title;
491
- const bodyBg = UI_TONES.bg.base;
492
- const innerStart = layout.margin + 1;
493
- const innerWidth = layout.innerWidth;
494
- const innerTextWidth = Math.max(14, innerWidth - 2);
495
- const descriptionLines = wrapText(currentStep.description, innerTextWidth).slice(0, 2);
496
- const selectedText = selectedFieldText(wizard);
497
- // Same rule as the wide layout: the selected field's hint wraps onto as
498
- // many lines as it needs rather than being clipped to a single row.
499
- const hintLines = wrapText(selectedText.hint, innerTextWidth);
500
- const fieldStartRow = 5 + hintLines.length;
501
- const fieldRows = buildFieldRows(wizard, visibleFields, Math.max(0, bodyRows - fieldStartRow));
502
-
503
- const topLine = createOverlayFilledBorderLine(
504
- width,
505
- layout,
506
- OVERLAY_GLYPHS.topLeft,
507
- OVERLAY_GLYPHS.horizontal,
508
- OVERLAY_GLYPHS.topRight,
509
- borderFg,
510
- headerBg,
511
- );
512
- putOverlayText(topLine, layout.margin + 2, layout.width - 4, 'Onboarding Wizard', {
513
- fg: UI_TONES.fg.primary,
514
- bg: headerBg,
515
- bold: true,
516
- });
517
- const meta = `${wizard.stepIndex + 1}/${wizard.steps.length} • ${changedScreensLabel(wizard)}`;
518
- putOverlayText(topLine, Math.max(layout.margin + 2, layout.margin + layout.width - getDisplayWidth(meta) - 3), layout.width - 4, meta, {
519
- fg: UI_TONES.fg.secondary,
520
- bg: headerBg,
521
- });
522
- lines.push(topLine);
523
-
524
- const headerLine = createOverlayContentLine(width, layout, borderFg, headerBg);
525
- fillWidth(headerLine, innerStart, innerWidth, headerBg);
526
- putOverlayText(headerLine, innerStart + 1, innerWidth - 2, fitDisplay(`${modeLabel(wizard.mode)} • ${currentStep.shortLabel}`, innerWidth - 2), {
527
- fg: UI_TONES.state.active,
528
- bg: headerBg,
529
- bold: true,
530
- });
531
- lines.push(headerLine);
532
-
533
- lines.push(createOverlayFilledBorderLine(
534
- width,
535
- layout,
536
- OVERLAY_GLYPHS.teeLeft,
537
- OVERLAY_GLYPHS.horizontal,
538
- OVERLAY_GLYPHS.teeRight,
539
- borderFg,
540
- bodyBg,
541
- ));
542
-
543
- for (let row = 0; row < bodyRows; row += 1) {
544
- const line = createOverlayContentLine(width, layout, borderFg, bodyBg);
545
- fillWidth(line, innerStart, innerWidth, bodyBg);
546
-
547
- if (row === 0) {
548
- putOverlayText(line, innerStart + 1, innerWidth - 2, truncateDisplay(currentStep.title, innerWidth - 2), {
549
- fg: UI_TONES.fg.primary,
550
- bg: bodyBg,
551
- bold: true,
552
- });
553
- } else if (row === 1) {
554
- putOverlayText(line, innerStart + 1, innerWidth - 2, descriptionLines[0] ?? '', {
555
- fg: UI_TONES.fg.secondary,
556
- bg: bodyBg,
557
- });
558
- } else if (row === 2) {
559
- putOverlayText(line, innerStart + 1, innerWidth - 2, descriptionLines[1] ?? '', {
560
- fg: UI_TONES.fg.secondary,
561
- bg: bodyBg,
562
- });
563
- } else if (row === 3) {
564
- fillWidth(line, innerStart, innerWidth, UI_TONES.bg.section);
565
- putOverlayText(line, innerStart + 1, innerWidth - 2, truncateDisplay(controlsText(wizard), innerWidth - 2), {
566
- fg: UI_TONES.state.info,
567
- bg: UI_TONES.bg.section,
568
- });
569
- } else if (row === 4) {
570
- fillWidth(line, innerStart, innerWidth, DEFAULT_OVERLAY_PALETTE.selectedBg);
571
- putOverlayText(line, innerStart + 1, innerWidth - 2, truncateDisplay(`Focus: ${selectedText.title.replace(/^Selected: /, '')}`, innerWidth - 2), {
572
- fg: UI_TONES.fg.primary,
573
- bg: DEFAULT_OVERLAY_PALETTE.selectedBg,
574
- bold: true,
575
- });
576
- } else if (row < fieldStartRow) {
577
- fillWidth(line, innerStart, innerWidth, DEFAULT_OVERLAY_PALETTE.selectedBg);
578
- putOverlayText(line, innerStart + 1, innerWidth - 2, hintLines[row - 5] ?? '', {
579
- fg: UI_TONES.fg.secondary,
580
- bg: DEFAULT_OVERLAY_PALETTE.selectedBg,
581
- });
582
- } else {
583
- renderFieldRow(line, wizard, fieldRows[row - fieldStartRow] ?? { kind: 'empty' }, innerStart, innerWidth);
584
- }
585
-
586
- lines.push(line);
587
- }
588
-
589
- lines.push(createOverlayFilledBorderLine(
590
- width,
591
- layout,
592
- OVERLAY_GLYPHS.teeLeft,
593
- OVERLAY_GLYPHS.horizontal,
594
- OVERLAY_GLYPHS.teeRight,
595
- borderFg,
596
- bodyBg,
597
- ));
598
-
599
- const footer = createOverlayFilledBorderLine(
600
- width,
601
- layout,
602
- OVERLAY_GLYPHS.bottomLeft,
603
- OVERLAY_GLYPHS.horizontal,
604
- OVERLAY_GLYPHS.bottomRight,
605
- borderFg,
606
- headerBg,
607
- );
608
- putOverlayText(footer, layout.margin + 2, layout.width - 4, truncateDisplay(footerText(wizard), layout.width - 4), {
609
- fg: UI_TONES.fg.muted,
610
- bg: headerBg,
611
- dim: true,
612
- });
613
- lines.push(footer);
614
-
615
- return lines.slice(0, viewportHeight);
616
- }
145
+ /** Text width at which the step list sits beside the step body. */
146
+ const WIDE_WIDTH = 90;
617
147
 
148
+ /**
149
+ * Render the wizard as a SurfaceLayer covering the screen (x 1, y 0).
150
+ * `width` and `viewportHeight` are the screen size the wizard owns.
151
+ */
618
152
  export function renderOnboardingWizard(
619
153
  wizard: OnboardingWizardController,
620
154
  width: number,
621
155
  viewportHeight: number,
622
- ): Line[] {
623
- const margin = width >= 64 ? 1 : 0;
624
- const layout = createOverlayBoxLayout(width, margin, Math.max(20, width - margin * 2));
625
- const collapsed = layout.innerWidth < 86;
626
- return collapsed
627
- ? renderCollapsedLayout(wizard, width, viewportHeight, layout)
628
- : renderWideLayout(wizard, width, viewportHeight, layout);
156
+ ): SurfaceLayer {
157
+ const t = activeTokens();
158
+ const step = wizard.currentStep;
159
+ const hints = hintsFor(wizard);
160
+ // The wizard owns the screen: full width minus 1 per side, full height.
161
+ // beginModal sizes against a screen tall enough that the requested height
162
+ // is never clamped; the layer is then placed at the top row.
163
+ const height = Math.max(5, viewportHeight - 2);
164
+ const f = beginModal(width, Math.max(viewportHeight * 2, 12), {
165
+ title: 'Onboarding',
166
+ sub: `${modeLabel(wizard.mode)} · step ${wizard.stepIndex + 1} of ${wizard.steps.length} · ${changedScreensLabel(wizard)}`,
167
+ hints,
168
+ width: width - 2,
169
+ height,
170
+ dim: false,
171
+ });
172
+ const inner = f.r - f.l + 1;
173
+ const wide = inner >= WIDE_WIDTH;
174
+
175
+ let x0 = f.l;
176
+ if (wide) {
177
+ const railW = inner >= 140 ? 32 : 28;
178
+ const railX1 = f.l + railW - 1;
179
+ const steps = stepRows(wizard);
180
+ const res = drawList(f.canvas, { rows: steps, top: f.top, bottom: f.bottom, x0: f.l, x1: railX1 });
181
+ const summary: TextLine[] = [
182
+ { text: step.summaryTitle, style: { fg: t.info, bold: true } },
183
+ ...step.summaryLines.slice(0, 2).map((text) => ({ text, style: { fg: t.textMuted } })),
184
+ { text: `Fields ${wizard.getCompletedFieldCount(wizard.stepIndex)}/${wizard.getStepFieldCount(wizard.stepIndex)} complete`, style: { fg: t.textMuted } },
185
+ ];
186
+ const summaryTop = res.endY + 1;
187
+ if (summaryTop + textBlockHeight(summary, railW) - 1 <= f.bottom) drawTextBlock(f.canvas, f.l, summaryTop, railW, summary, f.bottom);
188
+ x0 = railX1 + 6;
189
+ }
190
+ const x1 = f.r;
191
+ const textW = x1 - x0 + 1;
192
+
193
+ // Step title and its description, in full.
194
+ let y = drawTextBlock(f.canvas, x0, f.top, textW, [
195
+ { text: step.title, style: { fg: t.text, bold: true } },
196
+ { text: step.description, style: { fg: t.textMuted } },
197
+ ], f.bottom) + 1;
198
+
199
+ // The focus box at the bottom sizes to its text (never clipped); the
200
+ // field rows get the rows between.
201
+ const focus = focusLines(wizard);
202
+ const insetTextW = textW;
203
+ const focusRows = textBlockHeight(focus, insetTextW);
204
+ const detailH = Math.min(focusRows + 2, Math.max(3, f.bottom - y - 1));
205
+ const detailY = f.bottom - detailH + 1;
206
+ const p = inset(f.canvas, x0 - 2, detailY, textW + 4, detailH);
207
+ drawTextBlock(f.canvas, p.l, p.top, p.r - p.l + 1, focus, p.bottom);
208
+
209
+ const listBottom = detailY - 2;
210
+ if (y > listBottom) y = listBottom;
211
+ const fields = fieldRows(wizard);
212
+ if (fields.rows.length === 0) {
213
+ drawTextBlock(f.canvas, x0, y, textW, [{ text: 'This screen has no fields.', style: { fg: t.textMuted } }], listBottom);
214
+ } else {
215
+ const res = drawList(f.canvas, { rows: fields.rows, top: y, bottom: listBottom, x0, x1, scrollStart: fields.scrollStart });
216
+ f.hintRight = scrollCountText(res.above, res.below);
217
+ }
218
+ return { ...finishModal(f), y: 0 };
629
219
  }