@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.
- package/CHANGELOG.md +70 -0
- package/README.md +16 -16
- package/docs/README.md +4 -4
- package/docs/foundation-artifacts/operator-contract.json +59 -4
- package/package.json +16 -6
- package/src/cli/management.ts +5 -2
- package/src/cli/tui-startup.ts +34 -39
- package/src/core/agent-view-render.ts +157 -0
- package/src/core/approval-alert.ts +49 -11
- package/src/core/bookmark-navigation.ts +7 -1
- package/src/core/budget-breach-notifier.ts +27 -8
- package/src/core/compaction-receipt.ts +1 -1
- package/src/core/context-usage.ts +1 -1
- package/src/core/conversation-compaction-render.ts +3 -3
- package/src/core/conversation-display-only.ts +86 -0
- package/src/core/conversation-event-navigation.ts +27 -0
- package/src/core/conversation-line-cache.ts +216 -447
- package/src/core/conversation-render-context.ts +20 -27
- package/src/core/conversation-rendering.ts +157 -575
- package/src/core/conversation-splash-state.ts +6 -1
- package/src/core/conversation-types.ts +35 -0
- package/src/core/conversation-usage.ts +12 -0
- package/src/core/conversation.ts +130 -131
- package/src/core/long-task-notifier.ts +100 -31
- package/src/core/notices.ts +75 -0
- package/src/core/pairing-tailscale-gateway.ts +1 -1
- package/src/core/process-output-log.ts +143 -0
- package/src/core/rewind-receipt.ts +1 -1
- package/src/core/session-focus.ts +190 -0
- package/src/core/session-resume-core.ts +23 -81
- package/src/core/stream-event-wiring.ts +31 -5
- package/src/core/system-message-noise.ts +2 -2
- package/src/core/system-message-router.ts +16 -18
- package/src/core/turn-event-wiring.ts +272 -33
- package/src/core/work-tree-focus.ts +290 -0
- package/src/core/work-tree-fold-store.ts +143 -0
- package/src/core/work-tree-model.ts +799 -0
- package/src/core/work-tree-render.ts +384 -0
- package/src/core/work-tree-sources.ts +184 -0
- package/src/core/work-tree-wiring.ts +176 -0
- package/src/core/workstream-notification.ts +36 -6
- package/src/core/wrfc-notice-titles.ts +37 -0
- package/src/input/agents-modal-text.ts +119 -0
- package/src/input/agents-modal.ts +699 -0
- package/src/input/bookmark-modal.ts +33 -9
- package/src/input/changes-git.ts +251 -0
- package/src/input/changes-modal.ts +629 -0
- package/src/input/changes-model.ts +125 -0
- package/src/input/command-palette-types.ts +41 -0
- package/src/input/command-palette.ts +272 -0
- package/src/input/command-registry.ts +77 -35
- package/src/input/commands/acp-runtime.ts +19 -9
- package/src/input/commands/channel-runtime.ts +2 -2
- package/src/input/commands/ci-runtime.ts +1 -1
- package/src/input/commands/codebase-runtime.ts +1 -1
- package/src/input/commands/config.ts +7 -1
- package/src/input/commands/context-window.ts +25 -19
- package/src/input/commands/control-room-runtime.ts +18 -18
- package/src/input/commands/conversation-runtime.ts +0 -1
- package/src/input/commands/cost-runtime.ts +29 -20
- package/src/input/commands/diff-runtime.ts +35 -181
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/experience-runtime.ts +3 -7
- package/src/input/commands/fresh-conversation.ts +86 -0
- package/src/input/commands/git-runtime.ts +5 -22
- package/src/input/commands/graph-runtime.ts +1 -1
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +8 -9
- package/src/input/commands/hooks-runtime.ts +2 -2
- package/src/input/commands/hosted-runtime.ts +3 -3
- package/src/input/commands/incident-runtime.ts +3 -3
- package/src/input/commands/intelligence-runtime.ts +6 -9
- package/src/input/commands/knowledge.ts +2 -2
- package/src/input/commands/legacy-panel-command.ts +57 -0
- package/src/input/commands/local-auth-runtime.ts +8 -9
- package/src/input/commands/local-provider-runtime.ts +7 -4
- package/src/input/commands/local-runtime.ts +12 -15
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/local-setup.ts +13 -19
- package/src/input/commands/memory.ts +1 -1
- package/src/input/commands/notify-runtime.ts +9 -0
- package/src/input/commands/operator-runtime.ts +10 -12
- package/src/input/commands/permissions-provenance.ts +3 -3
- package/src/input/commands/planning-runtime.ts +7 -7
- package/src/input/commands/platform-sandbox-runtime.ts +2 -2
- package/src/input/commands/platform-services-runtime.ts +1 -1
- package/src/input/commands/policy-dispatch.ts +2 -2
- package/src/input/commands/policy.ts +3 -3
- package/src/input/commands/provider.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/review-runtime.ts +39 -94
- package/src/input/commands/rewind-runtime.ts +81 -108
- package/src/input/commands/runtime-services.ts +15 -17
- package/src/input/commands/services-runtime.ts +2 -2
- package/src/input/commands/session-content.ts +21 -5
- package/src/input/commands/session-workflow.ts +13 -38
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/shell-core.ts +19 -4
- package/src/input/commands/skills-runtime.ts +2 -2
- package/src/input/commands/status-runtime.ts +13 -0
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/tasks-runtime.ts +2 -8
- package/src/input/commands/work-plan-runtime.ts +4 -4
- package/src/input/commands/workstream-attempts.ts +24 -47
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands/worktree-runtime.ts +3 -3
- package/src/input/commands.ts +3 -1
- package/src/input/composer-editor.ts +44 -0
- package/src/input/concealed-input.ts +1 -1
- package/src/input/config-modal-types.ts +20 -8
- package/src/input/config-modal.ts +106 -105
- package/src/input/confirm-dialog-types.ts +28 -0
- package/src/input/confirm-dialog.ts +70 -0
- package/src/input/delete-key-policy.ts +3 -3
- package/src/input/feed-context-factory.ts +15 -16
- package/src/input/handler-command-route.ts +7 -42
- package/src/input/handler-content-actions.ts +16 -15
- package/src/input/handler-feed-routes.ts +26 -211
- package/src/input/handler-feed.ts +68 -95
- package/src/input/handler-interactions.ts +5 -3
- package/src/input/handler-modal-routes.ts +215 -216
- package/src/input/handler-modal-stack.ts +81 -11
- package/src/input/handler-modal-token-routes.ts +4 -0
- package/src/input/handler-onboarding.ts +0 -1
- package/src/input/handler-picker-routes.ts +65 -107
- package/src/input/handler-session-view-route.ts +149 -0
- package/src/input/handler-shortcuts.ts +16 -154
- package/src/input/handler-types.ts +4 -2
- package/src/input/handler-ui-state.ts +45 -15
- package/src/input/handler-work-tree-route.ts +123 -0
- package/src/input/handler.ts +25 -70
- package/src/input/keybindings.ts +16 -71
- package/src/input/masked-entry-modal.ts +117 -0
- package/src/input/mcp-workspace.ts +48 -17
- package/src/input/model-picker-filter.ts +8 -0
- package/src/input/model-picker-open.ts +99 -0
- package/src/input/model-picker-types.ts +1 -1
- package/src/input/model-picker.ts +92 -9
- package/src/input/notifications-modal.ts +123 -0
- package/src/input/overlay-filter.ts +26 -0
- package/src/input/profile-picker-modal.ts +37 -8
- package/src/input/search.ts +3 -3
- package/src/input/selection-modal-queue.ts +2 -0
- package/src/input/selection-modal.ts +28 -16
- package/src/input/session-picker-modal.ts +33 -8
- package/src/input/settings-modal-activation.ts +7 -2
- package/src/input/settings-modal-data.ts +11 -6
- package/src/input/settings-modal-types.ts +1 -1
- package/src/input/settings-modal.ts +20 -20
- package/src/input/submission-intent.ts +1 -1
- package/src/input/submission-router.ts +4 -9
- package/src/input/surface-modal-host.ts +110 -0
- package/src/input/theme-settings-actions.ts +82 -0
- package/src/input/usage-modal.ts +183 -0
- package/src/input/views.ts +97 -0
- package/src/main.ts +175 -165
- package/src/permissions/hunk-selection.ts +1 -1
- package/src/permissions/prompt-card.ts +511 -0
- package/src/permissions/prompt.ts +59 -622
- package/src/renderer/agents-modal.ts +310 -0
- package/src/renderer/autocomplete-overlay.ts +42 -220
- package/src/renderer/back-to-bottom.ts +80 -0
- package/src/renderer/block-actions-overlay.ts +30 -48
- package/src/renderer/bookmark-modal.ts +48 -79
- package/src/renderer/changes-modal.ts +325 -0
- package/src/renderer/code-block.ts +184 -106
- package/src/renderer/command-palette.ts +93 -0
- package/src/renderer/composer-fragments.ts +4 -4
- package/src/renderer/composer.ts +146 -0
- package/src/renderer/compositor.ts +26 -160
- package/src/renderer/config-modal.ts +185 -81
- package/src/renderer/confirm-dialog.ts +50 -0
- package/src/renderer/context-inspector.ts +116 -154
- package/src/renderer/context-status-hint.ts +4 -4
- package/src/renderer/conversation-layout.ts +20 -0
- package/src/renderer/conversation-overlays.ts +94 -84
- package/src/renderer/conversation-surface.ts +19 -38
- package/src/renderer/diff-tint.ts +98 -0
- package/src/renderer/file-picker-overlay.ts +63 -90
- package/src/renderer/footer-targets.ts +38 -0
- package/src/renderer/git-status.ts +3 -4
- package/src/renderer/header-line.ts +140 -117
- package/src/renderer/help-overlay.ts +133 -219
- package/src/renderer/history-search-overlay.ts +15 -12
- package/src/renderer/lane-graph/bead.ts +800 -0
- package/src/renderer/lane-graph/glyphs.ts +134 -0
- package/src/renderer/lane-graph/layout.ts +248 -0
- package/src/renderer/lane-graph/paint.ts +537 -0
- package/src/renderer/lane-graph/semantic-memo.ts +77 -0
- package/src/renderer/layout-engine.ts +2 -57
- package/src/renderer/layout.ts +7 -13
- package/src/renderer/markdown-inline.ts +2 -1
- package/src/renderer/markdown-table.ts +17 -12
- package/src/renderer/markdown.ts +92 -34
- package/src/renderer/masked-entry-modal.ts +78 -0
- package/src/renderer/mcp-workspace.ts +166 -203
- package/src/renderer/model-workspace.ts +381 -436
- package/src/renderer/notifications-modal.ts +116 -0
- package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
- package/src/renderer/process-view.ts +192 -0
- package/src/renderer/profile-picker-modal.ts +58 -110
- package/src/renderer/search-overlay.ts +6 -3
- package/src/renderer/selection-modal-overlay.ts +87 -327
- package/src/renderer/semantic-diff.ts +2 -2
- package/src/renderer/session-chips.ts +102 -0
- package/src/renderer/session-picker-modal.ts +177 -297
- package/src/renderer/settings-modal-connections.ts +14 -23
- package/src/renderer/settings-modal-context.ts +367 -0
- package/src/renderer/settings-modal-helpers.ts +6 -11
- package/src/renderer/settings-modal.ts +322 -726
- package/src/renderer/shell-surface.ts +277 -175
- package/src/renderer/shortcuts-overlay.ts +217 -0
- package/src/renderer/status-line.ts +475 -0
- package/src/renderer/surface-compose.ts +174 -0
- package/src/renderer/surface-kit-extra.ts +303 -0
- package/src/renderer/surface-kit-list.ts +366 -0
- package/src/renderer/surface-kit-parts.ts +360 -0
- package/src/renderer/surface-kit.ts +568 -0
- package/src/renderer/syntax-highlighter.ts +280 -197
- package/src/renderer/syntax-theme.ts +51 -0
- package/src/renderer/system-message.ts +0 -1
- package/src/renderer/term-caps.ts +42 -1
- package/src/renderer/terminal-bg-probe.ts +116 -19
- package/src/renderer/terminal-palette-probe.ts +322 -0
- package/src/renderer/terminal-palette-reader.ts +183 -0
- package/src/renderer/terminal-palette.ts +58 -0
- package/src/renderer/theme-mode-config.ts +16 -1
- package/src/renderer/theme.ts +234 -207
- package/src/renderer/thinking.ts +0 -1
- package/src/renderer/throbber.ts +188 -0
- package/src/renderer/toast-center.ts +119 -0
- package/src/renderer/ui-factory.ts +39 -602
- package/src/renderer/ui-primitives.ts +8 -25
- package/src/renderer/usage-modal.ts +353 -0
- package/src/renderer/user-message.ts +58 -0
- package/src/renderer/voice-capture-chip.ts +53 -0
- package/src/runtime/bootstrap-command-context.ts +0 -5
- package/src/runtime/bootstrap-command-parts.ts +38 -89
- package/src/runtime/bootstrap-core.ts +13 -15
- package/src/runtime/bootstrap-hook-bridge.ts +12 -21
- package/src/runtime/bootstrap-notifier-sync.ts +17 -1
- package/src/runtime/bootstrap-shell.ts +16 -50
- package/src/runtime/bootstrap.ts +14 -14
- package/src/runtime/client/fleet-union.ts +5 -5
- package/src/runtime/client/hosted-exit.ts +2 -2
- package/src/runtime/client/hosted-sessions.ts +1 -1
- package/src/runtime/client/spine-adoption.ts +1 -1
- package/src/runtime/context-accounting-source.ts +5 -4
- package/src/runtime/index.ts +1 -0
- package/src/runtime/notification-dispatch.ts +102 -29
- package/src/runtime/recovery-offer-wiring.ts +4 -1
- package/src/runtime/recovery-prompt.ts +31 -15
- package/src/runtime/runtime-services-types.ts +10 -6
- package/src/runtime/services.ts +17 -12
- package/src/runtime/session-continuity-hints.ts +2 -5
- package/src/runtime/session-inbound-inputs.ts +1 -1
- package/src/runtime/session-resume-liveness-confirm.ts +14 -2
- package/src/runtime/startup-typeahead-gate.ts +103 -0
- package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
- package/src/runtime/store/domains/index.ts +0 -8
- package/src/runtime/store/selectors/index.ts +0 -28
- package/src/runtime/store/state.ts +0 -8
- package/src/runtime/ui-services.ts +4 -6
- package/src/runtime/usage-tracker.ts +348 -0
- package/src/runtime/wrfc-persistence.ts +4 -4
- package/src/shell/blocking-input.ts +92 -2
- package/src/shell/recovery-input-helpers.ts +1 -1
- package/src/shell/session-views.ts +539 -0
- package/src/shell/status-report.ts +86 -0
- package/src/shell/transcript-scroll.ts +68 -0
- package/src/shell/ui-openers.ts +84 -172
- package/src/shell/view-openers.ts +205 -0
- package/src/tools/wrfc-agent-guard.ts +22 -2
- package/src/utils/format-duration.ts +4 -8
- package/src/utils/splash-lines.ts +9 -1
- package/src/utils/terminal-width.ts +56 -0
- package/src/verification/verification-ledger.ts +18 -16
- package/src/{panels → views}/agent-inspector-shared.ts +2 -2
- package/src/{panels → views}/builtin-modals.ts +22 -21
- package/src/views/builtin-views.ts +135 -0
- package/src/{panels → views}/confirm-state.ts +9 -9
- package/src/{panels → views}/diff-review-model.ts +10 -3
- package/src/{panels → views}/eval-registry.ts +1 -1
- package/src/{panels → views}/fleet-acts.ts +49 -85
- package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
- package/src/{panels → views}/fleet-gateway.ts +5 -5
- package/src/{panels → views}/fleet-observed-render.ts +16 -16
- package/src/{panels → views}/fleet-read-model.ts +9 -9
- package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
- package/src/{panels → views}/fleet-spawn.ts +33 -49
- package/src/{panels → views}/fleet-steer.ts +12 -12
- package/src/{panels → views}/fleet-stop.ts +4 -4
- package/src/{panels → views}/fleet-transcript.ts +26 -27
- package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
- package/src/{panels → views}/hosted-session-feed.ts +6 -6
- package/src/views/modal-surface-registry.ts +41 -0
- package/src/{panels → views}/modals/devices-modal.ts +2 -2
- package/src/{panels → views}/modals/hooks-modal.ts +13 -13
- package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
- package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
- package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
- package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
- package/src/{panels → views}/modals/memory-modal.ts +8 -8
- package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
- package/src/{panels → views}/modals/pairing-modal.ts +9 -9
- package/src/{panels → views}/modals/planning-modal.ts +15 -20
- package/src/{panels → views}/modals/plugins-modal.ts +6 -6
- package/src/{panels → views}/modals/policy-modal.ts +13 -5
- package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
- package/src/{panels → views}/modals/remote-modal.ts +3 -3
- package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
- package/src/{panels → views}/modals/security-modal.ts +22 -16
- package/src/{panels → views}/modals/services-modal.ts +3 -3
- package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
- package/src/{panels → views}/modals/skills-modal.ts +9 -9
- package/src/{panels → views}/modals/subscription-modal.ts +3 -3
- package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
- package/src/views/notifications-feed.ts +314 -0
- package/src/{panels → views}/polish-core.ts +38 -36
- package/src/{panels → views}/polish-tables.ts +35 -26
- package/src/{panels → views}/polish.ts +101 -95
- package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
- package/src/{panels → views}/provider-health-routes.ts +14 -14
- package/src/{panels → views}/search-focus.ts +8 -8
- package/src/{panels → views}/session-maintenance.ts +11 -11
- package/src/views/skills-discovery.ts +159 -0
- package/src/views/view-deps.ts +80 -0
- package/src/views/view-panel-adapter.ts +53 -0
- package/src/{panels → views}/workstream-graph-render.ts +1 -1
- package/src/core/conversation-fold.ts +0 -101
- package/src/core/conversation-turn-structure.ts +0 -464
- package/src/core/conversation-user-receipts.ts +0 -42
- package/src/input/commands/operator-panel-runtime.ts +0 -186
- package/src/input/panel-integration-actions.ts +0 -33
- package/src/input/panel-mouse-geometry.ts +0 -97
- package/src/input/panel-paste-flood-guard.ts +0 -86
- package/src/panels/base-panel.ts +0 -301
- package/src/panels/builtin/agent.ts +0 -27
- package/src/panels/builtin/development.ts +0 -92
- package/src/panels/builtin/knowledge.ts +0 -12
- package/src/panels/builtin/operations.ts +0 -177
- package/src/panels/builtin/session.ts +0 -81
- package/src/panels/builtin/shared.ts +0 -357
- package/src/panels/builtin-panels.ts +0 -28
- package/src/panels/cost-tracker-panel.ts +0 -746
- package/src/panels/diff-panel.ts +0 -797
- package/src/panels/diff-review-panel.ts +0 -395
- package/src/panels/expandable-list-panel.ts +0 -189
- package/src/panels/fleet-deep-link.ts +0 -31
- package/src/panels/fleet-panel.ts +0 -799
- package/src/panels/git-panel.ts +0 -800
- package/src/panels/hosted-session-panel.ts +0 -221
- package/src/panels/index.ts +0 -21
- package/src/panels/local-auth-panel.ts +0 -478
- package/src/panels/notifications-feed.ts +0 -127
- package/src/panels/notifications-panel.ts +0 -160
- package/src/panels/panel-confirm-overlay.ts +0 -72
- package/src/panels/panel-manager.ts +0 -791
- package/src/panels/plugins-panel.ts +0 -371
- package/src/panels/scrollable-list-panel.ts +0 -498
- package/src/panels/skills-panel.ts +0 -454
- package/src/panels/token-budget-panel.ts +0 -688
- package/src/panels/types.ts +0 -191
- package/src/panels/wrfc-panel-format.ts +0 -133
- package/src/renderer/diff-view.ts +0 -134
- package/src/renderer/fleet-tab-strip.ts +0 -56
- package/src/renderer/footer-tips.ts +0 -53
- package/src/renderer/fullscreen-primitives.ts +0 -146
- package/src/renderer/fullscreen-workspace.ts +0 -194
- package/src/renderer/hint-grammar.ts +0 -52
- package/src/renderer/modal-factory.ts +0 -480
- package/src/renderer/overlay-box.ts +0 -154
- package/src/renderer/overlay-viewport.ts +0 -104
- package/src/renderer/panel-composite.ts +0 -145
- package/src/renderer/panel-workspace-bar.ts +0 -54
- package/src/renderer/process-indicator.ts +0 -163
- package/src/renderer/tab-strip.ts +0 -222
- package/src/renderer/text-layout.ts +0 -31
- package/src/renderer/tool-call.ts +0 -276
- package/src/renderer/tool-result-expanded-lines.ts +0 -48
- package/src/renderer/tool-result-summary.ts +0 -219
- package/src/runtime/diagnostics/panels/index.ts +0 -21
- package/src/runtime/diagnostics/panels/ops.ts +0 -156
- package/src/runtime/perf/panel-contracts.ts +0 -32
- package/src/runtime/store/domains/panels.ts +0 -117
- /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
- /package/src/{panels → views}/modals/modal-theme.ts +0 -0
- /package/src/{panels → views}/provider-health-domains.ts +0 -0
|
@@ -1,37 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from '../
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
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 '
|
|
33
|
-
if (mode === 'reopen') return '
|
|
34
|
-
return '
|
|
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
|
|
44
|
-
|
|
45
|
-
step:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
65
|
-
field
|
|
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' ?
|
|
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 === '
|
|
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
|
-
|
|
79
|
-
if (
|
|
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
|
|
77
|
+
return tones.fg.secondary;
|
|
83
78
|
}
|
|
84
79
|
|
|
85
|
-
function
|
|
86
|
-
wizard
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
90
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
138
|
+
function hintsFor(wizard: OnboardingWizardController): KitHint[] {
|
|
255
139
|
if (wizard.isEditingTextField()) {
|
|
256
|
-
return '
|
|
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
|
-
|
|
480
|
-
|
|
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
|
-
):
|
|
623
|
-
const
|
|
624
|
-
const
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
}
|