@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.
- package/CHANGELOG.md +65 -1
- package/README.md +30 -29
- package/docs/README.md +8 -5
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +28 -19
- 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-reference.ts +2 -2
- 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/live-verifier.ts +0 -10
- package/src/version.ts +1 -1
- 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/verification/verification-ledger.ts +0 -707
- /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,480 +0,0 @@
|
|
|
1
|
-
import type { Line, Cell } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
|
-
import { createStyledCell } from '@pellux/goodvibes-sdk/platform/types';
|
|
3
|
-
import { fitDisplay, getDisplayWidth, truncateDisplay, wrapText } from '../utils/terminal-width.ts';
|
|
4
|
-
import {
|
|
5
|
-
createOverlayBorderLine,
|
|
6
|
-
createOverlayBoxLayout,
|
|
7
|
-
createOverlayContentLine,
|
|
8
|
-
putOverlayText,
|
|
9
|
-
} from './overlay-box.ts';
|
|
10
|
-
import { getOverlayMaxWidth } from './overlay-viewport.ts';
|
|
11
|
-
import { GLYPHS } from './ui-primitives.ts';
|
|
12
|
-
import { activeUiTones, registerThemeRefresh } from './theme.ts';
|
|
13
|
-
import { HINT_SEPARATOR } from './hint-grammar.ts';
|
|
14
|
-
|
|
15
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Truncate a string to fit within maxWidth display columns.
|
|
19
|
-
* Handles wide characters (CJK, emoji) correctly via getDisplayWidth.
|
|
20
|
-
*/
|
|
21
|
-
function truncateToWidth(text: string, maxWidth: number): string {
|
|
22
|
-
return truncateDisplay(text, maxWidth, '');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ── Types ────────────────────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
export interface ModalSectionStyle {
|
|
28
|
-
fg?: string;
|
|
29
|
-
bg?: string;
|
|
30
|
-
bold?: boolean;
|
|
31
|
-
dim?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface ModalSection {
|
|
35
|
-
type: 'text' | 'list' | 'input' | 'separator' | 'title' | 'spacer';
|
|
36
|
-
/** Text content for 'text' and 'input' types. */
|
|
37
|
-
content?: string;
|
|
38
|
-
/** Items for 'list' type. Each item: { label, selected?, style? } */
|
|
39
|
-
items?: ModalListItem[];
|
|
40
|
-
style?: ModalSectionStyle;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface ModalListItem {
|
|
44
|
-
label: string;
|
|
45
|
-
selected?: boolean;
|
|
46
|
-
style?: ModalSectionStyle;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface ModalStyle {
|
|
50
|
-
titleFg?: string;
|
|
51
|
-
borderFg?: string;
|
|
52
|
-
hintFg?: string;
|
|
53
|
-
selectedFg?: string;
|
|
54
|
-
selectedBg?: string;
|
|
55
|
-
textFg?: string;
|
|
56
|
-
accentFg?: string;
|
|
57
|
-
titleRowFg?: string;
|
|
58
|
-
titleBg?: string;
|
|
59
|
-
sectionBg?: string;
|
|
60
|
-
inputBg?: string;
|
|
61
|
-
surfaceBg?: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface ModalTab {
|
|
65
|
-
label: string;
|
|
66
|
-
active?: boolean;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface ModalHelperRow {
|
|
70
|
-
label?: string;
|
|
71
|
-
content: string;
|
|
72
|
-
accent?: boolean;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface ModalConfig {
|
|
76
|
-
title: string;
|
|
77
|
-
/** Box width including borders. Default: 72. */
|
|
78
|
-
width?: number;
|
|
79
|
-
/** Horizontal margin (spaces on left). Default: 4. */
|
|
80
|
-
margin?: number;
|
|
81
|
-
tabs?: ModalTab[];
|
|
82
|
-
search?: string;
|
|
83
|
-
sections: ModalSection[];
|
|
84
|
-
targetContentRows?: number;
|
|
85
|
-
helpers?: ModalHelperRow[];
|
|
86
|
-
/** Footer hint string. If omitted, no bottom hint text is inlined. */
|
|
87
|
-
footer?: string;
|
|
88
|
-
/** Keyboard hint strings to join with spaces in the footer border. */
|
|
89
|
-
hints?: string[];
|
|
90
|
-
style?: ModalStyle;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ── Defaults ─────────────────────────────────────────────────────────────────
|
|
94
|
-
|
|
95
|
-
// DEFAULT_STYLE is built from the mode-resolved chrome tones (activeUiTones) and
|
|
96
|
-
// rebuilt IN PLACE on a mode flip via the registered refresher. createModal reads
|
|
97
|
-
// it per call via `{ ...DEFAULT_STYLE, ...config.style }`, so the in-place rebuild
|
|
98
|
-
// reaches every modal, see theme.ts's active-mode runtime note.
|
|
99
|
-
function buildDefaultModalStyle(): Required<ModalStyle> {
|
|
100
|
-
const t = activeUiTones();
|
|
101
|
-
return {
|
|
102
|
-
titleFg: t.fg.primary,
|
|
103
|
-
borderFg: t.fg.dim,
|
|
104
|
-
hintFg: t.fg.muted,
|
|
105
|
-
selectedFg: t.fg.primary,
|
|
106
|
-
selectedBg: t.bg.selected,
|
|
107
|
-
textFg: t.fg.primary,
|
|
108
|
-
accentFg: t.state.info,
|
|
109
|
-
titleRowFg: t.fg.secondary,
|
|
110
|
-
titleBg: t.bg.title,
|
|
111
|
-
sectionBg: t.bg.section,
|
|
112
|
-
inputBg: t.bg.input,
|
|
113
|
-
surfaceBg: t.bg.surface,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const DEFAULT_STYLE: Required<ModalStyle> = buildDefaultModalStyle();
|
|
118
|
-
registerThemeRefresh(() => Object.assign(DEFAULT_STYLE, buildDefaultModalStyle()));
|
|
119
|
-
|
|
120
|
-
// ── ModalFactory ─────────────────────────────────────────────────────────────
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* ModalFactory, unified modal rendering for goodvibes-tui.
|
|
124
|
-
*
|
|
125
|
-
* Renders modals as Line[] (Cell arrays) consistent with the compositor
|
|
126
|
-
* overlay pipeline. All existing modals (file-picker, model-picker,
|
|
127
|
-
* selection-modal) can be expressed via createModal().
|
|
128
|
-
*
|
|
129
|
-
* Helper methods are also exported so callers can compose custom modals
|
|
130
|
-
* without going through the full config object.
|
|
131
|
-
*/
|
|
132
|
-
export class ModalFactory {
|
|
133
|
-
/**
|
|
134
|
-
* Render a complete modal box as Line[].
|
|
135
|
-
*
|
|
136
|
-
* @param config Modal configuration.
|
|
137
|
-
* @param terminalWidth Full terminal width (used to size/pad output lines).
|
|
138
|
-
*/
|
|
139
|
-
static createModal(config: ModalConfig, terminalWidth: number): Line[] {
|
|
140
|
-
const margin = config.margin ?? 4;
|
|
141
|
-
const maxBoxW = config.width ?? 72;
|
|
142
|
-
const boxW = Math.max(24, getOverlayMaxWidth(terminalWidth, margin, maxBoxW));
|
|
143
|
-
const style = { ...DEFAULT_STYLE, ...(config.style ?? {}) };
|
|
144
|
-
const lines: Line[] = [];
|
|
145
|
-
|
|
146
|
-
// Title bar
|
|
147
|
-
lines.push(ModalFactory.renderTitle(boxW, margin, config.title, terminalWidth, style));
|
|
148
|
-
|
|
149
|
-
// Sections
|
|
150
|
-
const sectionLines: Line[] = [];
|
|
151
|
-
if (config.tabs && config.tabs.length > 0) {
|
|
152
|
-
lines.push(ModalFactory._renderTabsRow(boxW, margin, terminalWidth, config.tabs, style));
|
|
153
|
-
}
|
|
154
|
-
if (typeof config.search === 'string') {
|
|
155
|
-
lines.push(...ModalFactory._renderInputSection({ type: 'input', content: config.search }, boxW, margin, terminalWidth, style));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
for (const section of config.sections) {
|
|
159
|
-
switch (section.type) {
|
|
160
|
-
case 'text':
|
|
161
|
-
sectionLines.push(...ModalFactory._renderTextSection(section, boxW, margin, terminalWidth, style));
|
|
162
|
-
break;
|
|
163
|
-
case 'list':
|
|
164
|
-
sectionLines.push(...ModalFactory._renderListSection(section, boxW, margin, terminalWidth, style));
|
|
165
|
-
break;
|
|
166
|
-
case 'input':
|
|
167
|
-
sectionLines.push(...ModalFactory._renderInputSection(section, boxW, margin, terminalWidth, style));
|
|
168
|
-
break;
|
|
169
|
-
case 'separator':
|
|
170
|
-
sectionLines.push(ModalFactory._renderSeparatorLine(boxW, margin, terminalWidth, style));
|
|
171
|
-
break;
|
|
172
|
-
case 'title':
|
|
173
|
-
sectionLines.push(ModalFactory._renderSectionTitle(section, boxW, margin, terminalWidth, style));
|
|
174
|
-
break;
|
|
175
|
-
case 'spacer':
|
|
176
|
-
sectionLines.push(ModalFactory._renderEmptyRow(boxW, margin, terminalWidth, style));
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (typeof config.targetContentRows === 'number' && config.targetContentRows > 0) {
|
|
182
|
-
const bounded = sectionLines.slice(0, config.targetContentRows);
|
|
183
|
-
while (bounded.length < config.targetContentRows) {
|
|
184
|
-
bounded.push(ModalFactory._renderEmptyRow(boxW, margin, terminalWidth, style));
|
|
185
|
-
}
|
|
186
|
-
lines.push(...bounded);
|
|
187
|
-
} else {
|
|
188
|
-
lines.push(...sectionLines);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (config.helpers && config.helpers.length > 0) {
|
|
192
|
-
for (const helper of config.helpers) {
|
|
193
|
-
lines.push(ModalFactory._renderHelperRow(boxW, margin, terminalWidth, helper, style));
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Footer / hint bar. Hint segments join with the shared middle-dot grammar
|
|
198
|
-
// separator so every modal footer reads as one dialect.
|
|
199
|
-
const hintStr = config.hints
|
|
200
|
-
? config.hints.join(HINT_SEPARATOR)
|
|
201
|
-
: (config.footer ?? '');
|
|
202
|
-
lines.push(ModalFactory.renderHints(boxW, margin, hintStr, terminalWidth, style));
|
|
203
|
-
|
|
204
|
-
return lines;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ── Public helpers ──────────────────────────────────────────────────────────
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Wrap content lines in a top/bottom border box.
|
|
211
|
-
* Content lines are expected to already be full-width Lines.
|
|
212
|
-
*/
|
|
213
|
-
static renderBox(
|
|
214
|
-
boxW: number,
|
|
215
|
-
margin: number,
|
|
216
|
-
content: Line[],
|
|
217
|
-
terminalWidth: number,
|
|
218
|
-
style: Partial<ModalStyle> = {},
|
|
219
|
-
): Line[] {
|
|
220
|
-
const s = { ...DEFAULT_STYLE, ...style };
|
|
221
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
222
|
-
return [
|
|
223
|
-
createOverlayBorderLine(terminalWidth, layout, '┌', '─', '┐', s.borderFg),
|
|
224
|
-
...content,
|
|
225
|
-
createOverlayBorderLine(terminalWidth, layout, '└', '─', '┘', s.borderFg),
|
|
226
|
-
];
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Render a title bar line: ┌─ Title ─────┐
|
|
231
|
-
*/
|
|
232
|
-
static renderTitle(
|
|
233
|
-
boxW: number,
|
|
234
|
-
margin: number,
|
|
235
|
-
title: string,
|
|
236
|
-
terminalWidth: number,
|
|
237
|
-
style: Partial<ModalStyle> = {},
|
|
238
|
-
): Line {
|
|
239
|
-
const s = { ...DEFAULT_STYLE, ...style };
|
|
240
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
241
|
-
const line = createOverlayBorderLine(terminalWidth, layout, GLYPHS.frame.topLeft, GLYPHS.frame.horizontal, GLYPHS.frame.topRight, s.borderFg);
|
|
242
|
-
for (let x = layout.margin + 1; x < layout.margin + layout.width - 1; x++) {
|
|
243
|
-
line[x] = createStyledCell(GLYPHS.frame.horizontal, { fg: s.borderFg, bg: s.titleBg });
|
|
244
|
-
}
|
|
245
|
-
putOverlayText(line, layout.margin + 2, layout.width - 4, title, { fg: s.titleFg, bg: s.titleBg, bold: true });
|
|
246
|
-
return line;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Render a bottom border line with optional keyboard hints inlined.
|
|
251
|
-
* ┘ side chars fill remaining width.
|
|
252
|
-
*/
|
|
253
|
-
static renderHints(
|
|
254
|
-
boxW: number,
|
|
255
|
-
margin: number,
|
|
256
|
-
hints: string,
|
|
257
|
-
terminalWidth: number,
|
|
258
|
-
style: Partial<ModalStyle> = {},
|
|
259
|
-
): Line {
|
|
260
|
-
const s = { ...DEFAULT_STYLE, ...style };
|
|
261
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
262
|
-
const line = createOverlayBorderLine(terminalWidth, layout, GLYPHS.frame.bottomLeft, GLYPHS.frame.horizontal, GLYPHS.frame.bottomRight, s.borderFg);
|
|
263
|
-
if (hints.length > 0) {
|
|
264
|
-
putOverlayText(line, layout.margin + 2, layout.width - 4, truncateDisplay(hints, layout.width - 4), {
|
|
265
|
-
fg: s.hintFg,
|
|
266
|
-
dim: true,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
return line;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Render a single selectable list item row.
|
|
274
|
-
* ┤ text ├ , bordered left/right, with optional selection highlight.
|
|
275
|
-
*/
|
|
276
|
-
static renderListItem(
|
|
277
|
-
boxW: number,
|
|
278
|
-
margin: number,
|
|
279
|
-
text: string,
|
|
280
|
-
selected: boolean,
|
|
281
|
-
terminalWidth: number,
|
|
282
|
-
style: Partial<ModalStyle> = {},
|
|
283
|
-
): Line {
|
|
284
|
-
const s = { ...DEFAULT_STYLE, ...style };
|
|
285
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
286
|
-
const contentW = layout.innerWidth;
|
|
287
|
-
const indicator = selected ? '▸ ' : ' ';
|
|
288
|
-
const displayText = getDisplayWidth(text) > contentW - 2
|
|
289
|
-
? truncateDisplay(text, contentW - 2)
|
|
290
|
-
: text;
|
|
291
|
-
const padded = fitDisplay(displayText, contentW - 2);
|
|
292
|
-
const row = createOverlayContentLine(terminalWidth, layout, s.borderFg, selected ? s.selectedBg : s.surfaceBg);
|
|
293
|
-
const cellStyle: Partial<Cell> = selected ? { fg: s.selectedFg, bg: s.selectedBg, bold: true } : { fg: s.textFg };
|
|
294
|
-
putOverlayText(row, layout.margin + 2, contentW, indicator + padded, {
|
|
295
|
-
fg: cellStyle.fg ?? s.textFg,
|
|
296
|
-
bg: cellStyle.bg ?? s.surfaceBg,
|
|
297
|
-
bold: cellStyle.bold ?? false,
|
|
298
|
-
});
|
|
299
|
-
return row;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// ── Private section renderers ────────────────────────────────────────────────
|
|
303
|
-
|
|
304
|
-
private static _renderTextSection(
|
|
305
|
-
section: ModalSection,
|
|
306
|
-
boxW: number,
|
|
307
|
-
margin: number,
|
|
308
|
-
terminalWidth: number,
|
|
309
|
-
style: Required<ModalStyle>,
|
|
310
|
-
): Line[] {
|
|
311
|
-
const contentW = boxW - 4;
|
|
312
|
-
const text = section.content ?? '';
|
|
313
|
-
const fg = section.style?.fg ?? style.textFg;
|
|
314
|
-
const bg = section.style?.bg ?? '';
|
|
315
|
-
const bold = section.style?.bold ?? false;
|
|
316
|
-
const dim = section.style?.dim ?? false;
|
|
317
|
-
|
|
318
|
-
const wrappedLines = wrapText(text, Math.max(8, contentW));
|
|
319
|
-
return wrappedLines.map((wrappedLine) => {
|
|
320
|
-
const truncated = getDisplayWidth(wrappedLine) > contentW
|
|
321
|
-
? truncateDisplay(wrappedLine, contentW)
|
|
322
|
-
: wrappedLine;
|
|
323
|
-
const padded = fitDisplay(truncated, contentW);
|
|
324
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
325
|
-
const row = createOverlayContentLine(terminalWidth, layout, style.borderFg, bg || style.surfaceBg);
|
|
326
|
-
putOverlayText(row, layout.margin + 2, contentW, padded, { fg, bg, bold, dim });
|
|
327
|
-
return row;
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
private static _renderListSection(
|
|
332
|
-
section: ModalSection,
|
|
333
|
-
boxW: number,
|
|
334
|
-
margin: number,
|
|
335
|
-
terminalWidth: number,
|
|
336
|
-
style: Required<ModalStyle>,
|
|
337
|
-
): Line[] {
|
|
338
|
-
const items = section.items ?? [];
|
|
339
|
-
if (items.length === 0) {
|
|
340
|
-
return [ModalFactory._renderEmptyRow(boxW, margin, terminalWidth, style)];
|
|
341
|
-
}
|
|
342
|
-
const contentW = boxW - 4;
|
|
343
|
-
const rows: Line[] = [];
|
|
344
|
-
for (const item of items) {
|
|
345
|
-
const itemStyle = {
|
|
346
|
-
...style,
|
|
347
|
-
...(item.style ? {
|
|
348
|
-
selectedFg: item.style.fg ?? style.selectedFg,
|
|
349
|
-
textFg: item.style.fg ?? style.textFg,
|
|
350
|
-
} : {}),
|
|
351
|
-
};
|
|
352
|
-
const wrapped = wrapText(item.label, Math.max(8, contentW - 2));
|
|
353
|
-
const displayLines = wrapped.length > 0 ? wrapped : [''];
|
|
354
|
-
for (let i = 0; i < displayLines.length; i++) {
|
|
355
|
-
const isFirst = i === 0;
|
|
356
|
-
const indicator = isFirst
|
|
357
|
-
? (item.selected ? `${GLYPHS.navigation.selected} ` : ' ')
|
|
358
|
-
: ' ';
|
|
359
|
-
const row = createOverlayContentLine(
|
|
360
|
-
terminalWidth,
|
|
361
|
-
createOverlayBoxLayout(terminalWidth, margin, boxW),
|
|
362
|
-
itemStyle.borderFg,
|
|
363
|
-
item.selected ? itemStyle.selectedBg : itemStyle.surfaceBg,
|
|
364
|
-
);
|
|
365
|
-
const padded = fitDisplay(truncateToWidth(`${indicator}${displayLines[i] ?? ''}`, contentW), contentW);
|
|
366
|
-
putOverlayText(row, margin + 2, contentW, padded, {
|
|
367
|
-
fg: item.selected ? itemStyle.selectedFg : itemStyle.textFg,
|
|
368
|
-
bg: item.selected ? itemStyle.selectedBg : itemStyle.surfaceBg,
|
|
369
|
-
bold: item.selected && isFirst,
|
|
370
|
-
});
|
|
371
|
-
rows.push(row);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return rows;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
private static _renderInputSection(
|
|
378
|
-
section: ModalSection,
|
|
379
|
-
boxW: number,
|
|
380
|
-
margin: number,
|
|
381
|
-
terminalWidth: number,
|
|
382
|
-
style: Required<ModalStyle>,
|
|
383
|
-
): Line[] {
|
|
384
|
-
const contentW = boxW - 4;
|
|
385
|
-
const query = section.content ?? '';
|
|
386
|
-
const cursor = GLYPHS.surface.cursor;
|
|
387
|
-
const displayQuery = getDisplayWidth(query) > contentW - 4
|
|
388
|
-
? truncateDisplay(query, contentW - 4)
|
|
389
|
-
: query;
|
|
390
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
391
|
-
const row = createOverlayContentLine(terminalWidth, layout, style.borderFg, style.inputBg);
|
|
392
|
-
const text = fitDisplay(`/ ${displayQuery}${cursor}`, contentW);
|
|
393
|
-
putOverlayText(row, layout.margin + 2, contentW, text, { fg: style.textFg, bg: style.inputBg, bold: true });
|
|
394
|
-
return [row];
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
private static _renderTabsRow(
|
|
398
|
-
boxW: number,
|
|
399
|
-
margin: number,
|
|
400
|
-
terminalWidth: number,
|
|
401
|
-
tabs: readonly ModalTab[],
|
|
402
|
-
style: Required<ModalStyle>,
|
|
403
|
-
): Line {
|
|
404
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
405
|
-
const row = createOverlayContentLine(terminalWidth, layout, style.borderFg, style.sectionBg);
|
|
406
|
-
const active = tabs.find((tab) => tab.active);
|
|
407
|
-
const inactive = tabs.filter((tab) => !tab.active);
|
|
408
|
-
const text = [
|
|
409
|
-
...(active ? [`[${active.label.toUpperCase()}]`] : []),
|
|
410
|
-
...inactive.map((tab) => tab.label),
|
|
411
|
-
].join(' ');
|
|
412
|
-
putOverlayText(row, layout.margin + 2, layout.innerWidth, fitDisplay(text, layout.innerWidth), {
|
|
413
|
-
fg: style.accentFg,
|
|
414
|
-
bg: style.sectionBg,
|
|
415
|
-
bold: true,
|
|
416
|
-
});
|
|
417
|
-
return row;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
private static _renderSectionTitle(
|
|
421
|
-
section: ModalSection,
|
|
422
|
-
boxW: number,
|
|
423
|
-
margin: number,
|
|
424
|
-
terminalWidth: number,
|
|
425
|
-
style: Required<ModalStyle>,
|
|
426
|
-
): Line {
|
|
427
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
428
|
-
const row = createOverlayContentLine(terminalWidth, layout, style.borderFg, section.style?.bg ?? style.sectionBg);
|
|
429
|
-
const text = fitDisplay(truncateToWidth(section.content ?? '', layout.innerWidth), layout.innerWidth);
|
|
430
|
-
putOverlayText(row, layout.margin + 2, layout.innerWidth, text, {
|
|
431
|
-
fg: section.style?.fg ?? style.titleRowFg,
|
|
432
|
-
bg: section.style?.bg ?? style.sectionBg,
|
|
433
|
-
bold: section.style?.bold ?? true,
|
|
434
|
-
dim: section.style?.dim ?? false,
|
|
435
|
-
});
|
|
436
|
-
return row;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
private static _renderHelperRow(
|
|
440
|
-
boxW: number,
|
|
441
|
-
margin: number,
|
|
442
|
-
terminalWidth: number,
|
|
443
|
-
helper: ModalHelperRow,
|
|
444
|
-
style: Required<ModalStyle>,
|
|
445
|
-
): Line {
|
|
446
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
447
|
-
const row = createOverlayContentLine(terminalWidth, layout, style.borderFg, style.surfaceBg);
|
|
448
|
-
const prefix = helper.label ? `${helper.label} ` : '';
|
|
449
|
-
const helperText = fitDisplay(`${prefix}${helper.content}`, layout.innerWidth);
|
|
450
|
-
putOverlayText(row, layout.margin + 2, layout.innerWidth, helperText, {
|
|
451
|
-
fg: helper.accent ? style.accentFg : style.hintFg,
|
|
452
|
-
dim: !helper.accent,
|
|
453
|
-
});
|
|
454
|
-
return row;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
private static _renderSeparatorLine(
|
|
458
|
-
boxW: number,
|
|
459
|
-
margin: number,
|
|
460
|
-
terminalWidth: number,
|
|
461
|
-
style: Required<ModalStyle>,
|
|
462
|
-
): Line {
|
|
463
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
464
|
-
const line = createOverlayBorderLine(terminalWidth, layout, GLYPHS.frame.teeLeft, GLYPHS.frame.horizontal, GLYPHS.frame.teeRight, style.borderFg);
|
|
465
|
-
for (let x = layout.margin + 1; x < layout.margin + layout.width - 1; x++) {
|
|
466
|
-
line[x] = createStyledCell(GLYPHS.frame.horizontal, { fg: style.borderFg, bg: style.sectionBg });
|
|
467
|
-
}
|
|
468
|
-
return line;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
private static _renderEmptyRow(
|
|
472
|
-
boxW: number,
|
|
473
|
-
margin: number,
|
|
474
|
-
terminalWidth: number,
|
|
475
|
-
style: Required<ModalStyle>,
|
|
476
|
-
): Line {
|
|
477
|
-
const layout = createOverlayBoxLayout(terminalWidth, margin, boxW);
|
|
478
|
-
return createOverlayContentLine(terminalWidth, layout, style.borderFg, style.surfaceBg);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { createStyledCell, type Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
|
-
import { getOverlayMaxWidth } from './overlay-viewport.ts';
|
|
3
|
-
import { GLYPHS } from './ui-primitives.ts';
|
|
4
|
-
import { activeUiTones, registerThemeRefresh } from './theme.ts';
|
|
5
|
-
import { fillWidth, makeLine, writeText } from './fullscreen-primitives.ts';
|
|
6
|
-
|
|
7
|
-
export interface OverlayBoxPalette {
|
|
8
|
-
readonly borderFg: string;
|
|
9
|
-
readonly titleFg: string;
|
|
10
|
-
readonly bodyFg: string;
|
|
11
|
-
readonly mutedFg: string;
|
|
12
|
-
readonly selectedBg: string;
|
|
13
|
-
readonly titleBg: string;
|
|
14
|
-
readonly sectionBg: string;
|
|
15
|
-
readonly inputBg: string;
|
|
16
|
-
readonly bodyBg: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Built from the mode-resolved chrome tones (activeUiTones) and rebuilt IN PLACE
|
|
20
|
-
// on a mode flip via the registered refresher (60+ call sites read it by
|
|
21
|
-
// reference, see theme.ts's active-mode runtime note).
|
|
22
|
-
function buildOverlayPalette(): OverlayBoxPalette {
|
|
23
|
-
const t = activeUiTones();
|
|
24
|
-
return {
|
|
25
|
-
borderFg: t.fg.secondary,
|
|
26
|
-
titleFg: t.fg.primary,
|
|
27
|
-
bodyFg: t.fg.primary,
|
|
28
|
-
mutedFg: t.fg.dim,
|
|
29
|
-
selectedBg: t.bg.selected,
|
|
30
|
-
titleBg: t.bg.title,
|
|
31
|
-
sectionBg: t.bg.section,
|
|
32
|
-
inputBg: t.bg.input,
|
|
33
|
-
bodyBg: t.bg.surface,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const DEFAULT_OVERLAY_PALETTE: Readonly<OverlayBoxPalette> = buildOverlayPalette();
|
|
38
|
-
registerThemeRefresh(() => Object.assign(DEFAULT_OVERLAY_PALETTE as OverlayBoxPalette, buildOverlayPalette()));
|
|
39
|
-
|
|
40
|
-
export interface OverlayBoxLayout {
|
|
41
|
-
readonly margin: number;
|
|
42
|
-
readonly width: number;
|
|
43
|
-
readonly contentWidth: number;
|
|
44
|
-
readonly innerWidth: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface OverlayTextStyle {
|
|
48
|
-
fg: string;
|
|
49
|
-
bg?: string;
|
|
50
|
-
bold?: boolean;
|
|
51
|
-
dim?: boolean;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function createOverlayBoxLayout(
|
|
55
|
-
terminalWidth: number,
|
|
56
|
-
margin: number,
|
|
57
|
-
maxWidth: number,
|
|
58
|
-
): OverlayBoxLayout {
|
|
59
|
-
const resolvedMaxWidth = getOverlayMaxWidth(terminalWidth, margin, maxWidth);
|
|
60
|
-
// The 20-column floor exists so overlays stay legible on ordinary narrow
|
|
61
|
-
// terminals, but on a hostile-narrow terminal (terminalWidth smaller than
|
|
62
|
-
// margin*2 + 20) that floor alone can push the box wider than the real
|
|
63
|
-
// terminal, walking cells off the right edge of the line array. Clamp the
|
|
64
|
-
// floored result back down to what actually fits before returning it.
|
|
65
|
-
const availableWidth = Math.max(1, terminalWidth - margin * 2);
|
|
66
|
-
const width = Math.min(availableWidth, Math.max(20, Math.min(availableWidth, resolvedMaxWidth)));
|
|
67
|
-
const contentWidth = width - 2;
|
|
68
|
-
const innerWidth = contentWidth - 2;
|
|
69
|
-
return { margin, width, contentWidth, innerWidth };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function putOverlayText(
|
|
73
|
-
line: Line,
|
|
74
|
-
startX: number,
|
|
75
|
-
maxWidth: number,
|
|
76
|
-
text: string,
|
|
77
|
-
style: OverlayTextStyle,
|
|
78
|
-
): void {
|
|
79
|
-
writeText(line, startX, maxWidth, text, {
|
|
80
|
-
fg: style.fg,
|
|
81
|
-
bg: style.bg ?? '',
|
|
82
|
-
bold: style.bold ?? false,
|
|
83
|
-
dim: style.dim ?? false,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function createOverlayBorderLine(
|
|
88
|
-
terminalWidth: number,
|
|
89
|
-
layout: OverlayBoxLayout,
|
|
90
|
-
left: string,
|
|
91
|
-
fill: string,
|
|
92
|
-
right: string,
|
|
93
|
-
fg: string = DEFAULT_OVERLAY_PALETTE.borderFg,
|
|
94
|
-
): Line {
|
|
95
|
-
const line = makeLine(terminalWidth);
|
|
96
|
-
const leftX = layout.margin;
|
|
97
|
-
const rightX = layout.margin + layout.width - 1;
|
|
98
|
-
line[leftX] = createStyledCell(left, { fg });
|
|
99
|
-
for (let x = leftX + 1; x < rightX; x++) {
|
|
100
|
-
line[x] = createStyledCell(fill, { fg });
|
|
101
|
-
}
|
|
102
|
-
line[rightX] = createStyledCell(right, { fg });
|
|
103
|
-
return line;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function createOverlayContentLine(
|
|
107
|
-
terminalWidth: number,
|
|
108
|
-
layout: OverlayBoxLayout,
|
|
109
|
-
borderFg: string = DEFAULT_OVERLAY_PALETTE.borderFg,
|
|
110
|
-
bg = '',
|
|
111
|
-
): Line {
|
|
112
|
-
const line = makeLine(terminalWidth);
|
|
113
|
-
const leftX = layout.margin;
|
|
114
|
-
const rightX = layout.margin + layout.width - 1;
|
|
115
|
-
line[leftX] = createStyledCell('│', { fg: borderFg });
|
|
116
|
-
fillWidth(line, leftX + 1, rightX - leftX - 1, bg);
|
|
117
|
-
line[rightX] = createStyledCell('│', { fg: borderFg });
|
|
118
|
-
return line;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function createOverlayFilledBorderLine(
|
|
122
|
-
terminalWidth: number,
|
|
123
|
-
layout: OverlayBoxLayout,
|
|
124
|
-
left: string,
|
|
125
|
-
fill: string,
|
|
126
|
-
right: string,
|
|
127
|
-
fg: string = DEFAULT_OVERLAY_PALETTE.borderFg,
|
|
128
|
-
bg = '',
|
|
129
|
-
): Line {
|
|
130
|
-
const line = makeLine(terminalWidth);
|
|
131
|
-
const leftX = layout.margin;
|
|
132
|
-
const rightX = layout.margin + layout.width - 1;
|
|
133
|
-
line[leftX] = createStyledCell(left, { fg, bg });
|
|
134
|
-
for (let x = leftX + 1; x < rightX; x++) {
|
|
135
|
-
line[x] = createStyledCell(fill, { fg, bg });
|
|
136
|
-
}
|
|
137
|
-
line[rightX] = createStyledCell(right, { fg, bg });
|
|
138
|
-
return line;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export const OVERLAY_GLYPHS = {
|
|
142
|
-
topLeft: GLYPHS.frame.topLeft,
|
|
143
|
-
topRight: GLYPHS.frame.topRight,
|
|
144
|
-
bottomLeft: GLYPHS.frame.bottomLeft,
|
|
145
|
-
bottomRight: GLYPHS.frame.bottomRight,
|
|
146
|
-
teeLeft: GLYPHS.frame.teeLeft,
|
|
147
|
-
teeRight: GLYPHS.frame.teeRight,
|
|
148
|
-
horizontal: GLYPHS.frame.horizontal,
|
|
149
|
-
selected: GLYPHS.navigation.selected,
|
|
150
|
-
expanded: GLYPHS.navigation.expanded,
|
|
151
|
-
cursor: GLYPHS.surface.cursor,
|
|
152
|
-
moreAbove: GLYPHS.navigation.moreAbove,
|
|
153
|
-
moreBelow: GLYPHS.navigation.moreBelow,
|
|
154
|
-
} as const;
|