@pellux/goodvibes-tui 2.0.21 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/README.md +16 -16
- package/docs/README.md +4 -4
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- 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
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
ConfigModalTab,
|
|
7
7
|
ConfigModalView,
|
|
8
8
|
} from './config-modal-types.ts';
|
|
9
|
-
import type { ModalSectionStyle } from '
|
|
9
|
+
import type { ModalSectionStyle } from './config-modal-types.ts';
|
|
10
10
|
import { truncateDisplay, wrapText } from '../utils/terminal-width.ts';
|
|
11
11
|
|
|
12
12
|
export type {
|
|
@@ -35,6 +35,8 @@ export interface ConfigModalRenderRow {
|
|
|
35
35
|
/** True when this frozen row has no live counterpart this tick (value went
|
|
36
36
|
* stale, kept in place, dimmed, until the next interaction boundary). */
|
|
37
37
|
readonly stale: boolean;
|
|
38
|
+
/** A group header row (see ConfigModalRow.header). */
|
|
39
|
+
readonly header: boolean;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/** Everything renderConfigModal needs, computed by overlaying live values onto
|
|
@@ -47,18 +49,20 @@ export interface ConfigModalRenderModel {
|
|
|
47
49
|
readonly emptyText?: string;
|
|
48
50
|
readonly degraded?: string;
|
|
49
51
|
readonly status?: string;
|
|
52
|
+
/** True while a destructive action waits for its second key press. */
|
|
53
|
+
readonly confirmPending: boolean;
|
|
54
|
+
/** Surface, tab and action hints as "key action" strings (the renderer draws keycaps). */
|
|
50
55
|
readonly hints: readonly string[];
|
|
51
56
|
readonly scroll: { readonly offset: number; readonly total: number; readonly visible: number };
|
|
57
|
+
/** The always-live search row: the query and a truthful match count over the active tab's selectable rows. */
|
|
58
|
+
readonly search: { readonly query: string; readonly matched: number; readonly total: number };
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
const DEFAULT_VISIBLE_ROWS = 10;
|
|
55
62
|
|
|
56
63
|
/**
|
|
57
64
|
* Default label wrap width for getRenderModel() callers that don't pass one
|
|
58
|
-
* (tests, mainly).
|
|
59
|
-
* common terminal sizes this codebase tests against: default box width 76,
|
|
60
|
-
* minus the 4-column text margin and 2-column selection indicator (see
|
|
61
|
-
* ModalFactory._renderListSection's `contentW - 2`). renderConfigModal()
|
|
65
|
+
* (tests, mainly). renderConfigModal()
|
|
62
66
|
* itself always computes and passes the ACTUAL width for the current
|
|
63
67
|
* terminal size, this constant only matters when getRenderModel() is
|
|
64
68
|
* called directly without going through the renderer.
|
|
@@ -97,31 +101,15 @@ export class ConfigModal {
|
|
|
97
101
|
private pendingConfirmKey: string | null = null;
|
|
98
102
|
|
|
99
103
|
/**
|
|
100
|
-
*
|
|
101
|
-
* the
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* FILTER-CONVENTION RULING (batch integration, where '/'-armed vs instant
|
|
110
|
-
* filtering is decided across the TUI's list UIs):
|
|
111
|
-
* - '/'-ARMED here (config-modal host surfaces): these surfaces bind PLAIN
|
|
112
|
-
* single keys to ACTION HOTKEYS (e.g. 'r' refresh, 'd' delete). A key
|
|
113
|
-
* can't be both an action and a filter character, so filtering must be
|
|
114
|
-
* explicitly armed with '/' first. This is a deliberate design choice.
|
|
115
|
-
* - INSTANT filtering (pure pickers, help overlay, command palette,
|
|
116
|
-
* selection-modal): these have no single-key actions, so every printable
|
|
117
|
-
* key is unambiguously a filter character and narrows the list on the
|
|
118
|
-
* first keystroke. This is a deliberate design choice (selection-modal-overlay.ts).
|
|
119
|
-
* - Both keep single-Esc-close semantics. The only extra step is here: a
|
|
120
|
-
* two-stage Esc (first Esc clears a NON-EMPTY query, second Esc closes)
|
|
121
|
-
* , see the Esc branch in handleConfigModalToken. With an empty query,
|
|
122
|
-
* one Esc closes, exactly like the instant pickers.
|
|
104
|
+
* The always-live search row. Every printable key the modal receives is
|
|
105
|
+
* typed here, except a key the surface claims as an action, which fires
|
|
106
|
+
* the action while the query is empty (handleConfigModalToken). The query
|
|
107
|
+
* is the TEXT-CAPTURE buffer itself (a multi-char paste token appends in
|
|
108
|
+
* one shot). Filtering only ever narrows the ACTIVE tab's rows and is reset
|
|
109
|
+
* on tab switch (a query typed against one tab's rows has no defined
|
|
110
|
+
* meaning against another's). Esc never clears it as a separate step: Esc
|
|
111
|
+
* cancels an armed destructive confirm first, otherwise it closes.
|
|
123
112
|
*/
|
|
124
|
-
private filterActive = false;
|
|
125
113
|
private filterQuery = '';
|
|
126
114
|
|
|
127
115
|
/**
|
|
@@ -131,6 +119,8 @@ export class ConfigModal {
|
|
|
131
119
|
private frozenView: ConfigModalView | null = null;
|
|
132
120
|
|
|
133
121
|
private scrollOffset = 0;
|
|
122
|
+
/** The label wrap width of the last render, so scrolling can count wrapped lines. */
|
|
123
|
+
private lastWrapWidth = DEFAULT_LABEL_WRAP_WIDTH;
|
|
134
124
|
private visibleRows = DEFAULT_VISIBLE_ROWS;
|
|
135
125
|
|
|
136
126
|
/**
|
|
@@ -153,7 +143,6 @@ export class ConfigModal {
|
|
|
153
143
|
this.statusMessage = '';
|
|
154
144
|
this.pendingConfirmKey = null;
|
|
155
145
|
this.scrollOffset = 0;
|
|
156
|
-
this.filterActive = false;
|
|
157
146
|
this.filterQuery = '';
|
|
158
147
|
this.interactedSinceOpen = false;
|
|
159
148
|
const view = surface.buildView();
|
|
@@ -171,7 +160,6 @@ export class ConfigModal {
|
|
|
171
160
|
this.frozenView = null;
|
|
172
161
|
this.statusMessage = '';
|
|
173
162
|
this.pendingConfirmKey = null;
|
|
174
|
-
this.filterActive = false;
|
|
175
163
|
this.filterQuery = '';
|
|
176
164
|
}
|
|
177
165
|
|
|
@@ -221,55 +209,48 @@ export class ConfigModal {
|
|
|
221
209
|
this._clampScroll();
|
|
222
210
|
}
|
|
223
211
|
|
|
224
|
-
// ──
|
|
212
|
+
// ── Search row (each mutation is an interaction boundary) ─────────────────
|
|
225
213
|
|
|
214
|
+
/** True while the search row holds a query. */
|
|
226
215
|
isFilterActive(): boolean {
|
|
227
|
-
return this.
|
|
216
|
+
return this.filterQuery.length > 0;
|
|
228
217
|
}
|
|
229
218
|
|
|
230
219
|
getFilterQuery(): string {
|
|
231
220
|
return this.filterQuery;
|
|
232
221
|
}
|
|
233
222
|
|
|
234
|
-
/** Arm the filter (handleConfigModalToken calls this on '/'). Idempotent. */
|
|
235
|
-
activateFilter(): void {
|
|
236
|
-
if (this.filterActive) return;
|
|
237
|
-
this._clearConfirm();
|
|
238
|
-
this.filterActive = true;
|
|
239
|
-
this.syncStructure();
|
|
240
|
-
}
|
|
241
|
-
|
|
242
223
|
/**
|
|
243
224
|
* Append text to the query, the WHOLE token value in one call, so a
|
|
244
225
|
* multi-char paste token lands in the filter atomically rather than being
|
|
245
|
-
* split into per-char nav/action dispatch
|
|
246
|
-
* item's brief calls out). A no-op when the filter isn't armed.
|
|
226
|
+
* split into per-char nav/action dispatch.
|
|
247
227
|
*/
|
|
248
228
|
appendFilterText(text: string): void {
|
|
249
|
-
if (
|
|
229
|
+
if (text.length === 0) return;
|
|
230
|
+
this._clearConfirm();
|
|
250
231
|
this.filterQuery += text;
|
|
251
232
|
this.syncStructure();
|
|
252
233
|
}
|
|
253
234
|
|
|
254
235
|
backspaceFilter(): void {
|
|
255
|
-
if (
|
|
236
|
+
if (this.filterQuery.length === 0) return;
|
|
256
237
|
this.filterQuery = this.filterQuery.slice(0, -1);
|
|
257
238
|
this.syncStructure();
|
|
258
239
|
}
|
|
259
240
|
|
|
241
|
+
/** True while a destructive action waits for its second key press. */
|
|
242
|
+
hasPendingConfirm(): boolean {
|
|
243
|
+
return this.pendingConfirmKey !== null;
|
|
244
|
+
}
|
|
245
|
+
|
|
260
246
|
/**
|
|
261
|
-
* Esc
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* empty query returns false, the caller falls through to the ordinary
|
|
265
|
-
* close path, preserving single-Esc-close for the no-filter case (the one
|
|
266
|
-
* documented exception: two-stage Esc only when there is something to
|
|
267
|
-
* clear).
|
|
247
|
+
* Esc's first level inside the modal: an armed destructive confirm is a
|
|
248
|
+
* sub-state of the modal, so Esc cancels it (returns true) before a second
|
|
249
|
+
* Esc closes the modal. Returns false when nothing was armed.
|
|
268
250
|
*/
|
|
269
|
-
|
|
270
|
-
if (
|
|
271
|
-
this.
|
|
272
|
-
this.syncStructure();
|
|
251
|
+
cancelPendingConfirm(): boolean {
|
|
252
|
+
if (this.pendingConfirmKey === null) return false;
|
|
253
|
+
this._clearConfirm();
|
|
273
254
|
return true;
|
|
274
255
|
}
|
|
275
256
|
|
|
@@ -279,7 +260,13 @@ export class ConfigModal {
|
|
|
279
260
|
this._clearConfirm();
|
|
280
261
|
this.syncStructure();
|
|
281
262
|
const ids = this._selectableIds(this._frozenTab());
|
|
282
|
-
if (ids.length === 0)
|
|
263
|
+
if (ids.length === 0) {
|
|
264
|
+
// A tab of informational rows only (pairing, planning): the arrows
|
|
265
|
+
// scroll it instead, so nothing below the fold is out of reach.
|
|
266
|
+
this.scrollOffset++;
|
|
267
|
+
this._clampScroll();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
283
270
|
const i = ids.indexOf(this.selectedRowId);
|
|
284
271
|
this.selectedRowId = ids[(i + 1) % ids.length]!;
|
|
285
272
|
this._clampScroll();
|
|
@@ -289,7 +276,11 @@ export class ConfigModal {
|
|
|
289
276
|
this._clearConfirm();
|
|
290
277
|
this.syncStructure();
|
|
291
278
|
const ids = this._selectableIds(this._frozenTab());
|
|
292
|
-
if (ids.length === 0)
|
|
279
|
+
if (ids.length === 0) {
|
|
280
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
281
|
+
this._clampScroll();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
293
284
|
const i = ids.indexOf(this.selectedRowId);
|
|
294
285
|
this.selectedRowId = ids[(i - 1 + ids.length) % ids.length]!;
|
|
295
286
|
this._clampScroll();
|
|
@@ -306,8 +297,7 @@ export class ConfigModal {
|
|
|
306
297
|
private _switchTab(dir: 1 | -1): void {
|
|
307
298
|
this._clearConfirm();
|
|
308
299
|
// A filter query is scoped to the tab it was typed against, switching
|
|
309
|
-
// tabs resets it
|
|
310
|
-
this.filterActive = false;
|
|
300
|
+
// tabs resets it, same as statusMessage below.
|
|
311
301
|
this.filterQuery = '';
|
|
312
302
|
this.syncStructure();
|
|
313
303
|
const tabs = this.frozenView?.tabs ?? [];
|
|
@@ -376,7 +366,6 @@ export class ConfigModal {
|
|
|
376
366
|
*/
|
|
377
367
|
jumpToRow(tabId: string, rowId: string): void {
|
|
378
368
|
this._clearConfirm();
|
|
379
|
-
this.filterActive = false;
|
|
380
369
|
this.filterQuery = '';
|
|
381
370
|
this.syncStructure();
|
|
382
371
|
const tab = this.frozenView?.tabs.find((t) => t.id === tabId);
|
|
@@ -418,9 +407,10 @@ export class ConfigModal {
|
|
|
418
407
|
* does NOT sync structure, so calling it repeatedly with only value mutations
|
|
419
408
|
* yields byte-identical layout (the liveness contract).
|
|
420
409
|
*
|
|
421
|
-
* `labelWrapWidth` is the wrap column
|
|
422
|
-
*
|
|
423
|
-
*
|
|
410
|
+
* `labelWrapWidth` is the wrap column the kit list uses for row labels
|
|
411
|
+
* (renderConfigModal computes and passes the real one for the current
|
|
412
|
+
* terminal size). It exists so the wrap-clamp below can pre-empt a live
|
|
413
|
+
* label growing past that wrap width
|
|
424
414
|
* mid-tick, a structural change (an extra visible line) without an
|
|
425
415
|
* interaction, by measuring wrapped line counts here, before the label
|
|
426
416
|
* ever reaches the renderer.
|
|
@@ -429,11 +419,12 @@ export class ConfigModal {
|
|
|
429
419
|
// Pre-first-interaction: async onOpen loads may restructure freely (the
|
|
430
420
|
// user hasn't engaged a cursor yet), sync so "Loading…" is replaced by
|
|
431
421
|
// real content on the load's own requestRender, not the next keypress.
|
|
422
|
+
this.lastWrapWidth = labelWrapWidth;
|
|
432
423
|
if (this.active && !this.interactedSinceOpen) this.syncStructure();
|
|
433
424
|
const live = this.surface?.buildView() ?? null;
|
|
434
425
|
const frozen = this.frozenView;
|
|
435
426
|
if (!frozen) {
|
|
436
|
-
return { title: '', tabs: [], header: [], rows: [], hints: [], scroll: { offset: 0, total: 0, visible: this.visibleRows } };
|
|
427
|
+
return { title: '', tabs: [], header: [], rows: [], hints: [], confirmPending: false, scroll: { offset: 0, total: 0, visible: this.visibleRows }, search: { query: this.filterQuery, matched: 0, total: 0 } };
|
|
437
428
|
}
|
|
438
429
|
|
|
439
430
|
const tabs: ConfigModalRenderTab[] = frozen.tabs.map((ft) => {
|
|
@@ -461,35 +452,25 @@ export class ConfigModal {
|
|
|
461
452
|
label: this._clampRowLabel(fr.label, src.label, labelWrapWidth),
|
|
462
453
|
style: src.style,
|
|
463
454
|
selected: fr.id === this.selectedRowId,
|
|
464
|
-
selectable: fr.selectable !== false,
|
|
455
|
+
selectable: fr.selectable !== false && fr.header !== true,
|
|
465
456
|
stale: lr === undefined,
|
|
457
|
+
header: fr.header === true,
|
|
466
458
|
};
|
|
467
459
|
});
|
|
468
460
|
|
|
469
461
|
const visible = this.visibleRows;
|
|
470
462
|
const windowed = this._windowRowsByLineBudget(allRows, this.scrollOffset, visible, labelWrapWidth);
|
|
471
463
|
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
frozenRows.filter((r) => r.selectable !== false).length,
|
|
483
|
-
(liveTab?.rows ?? []).filter((r) => r.selectable !== false).length,
|
|
484
|
-
),
|
|
485
|
-
hasQuery ? 'Esc clear · Esc close' : 'Esc close',
|
|
486
|
-
]
|
|
487
|
-
: [
|
|
488
|
-
...(frozen.hints ?? []),
|
|
489
|
-
...(frozenTab?.hints ?? []),
|
|
490
|
-
...this._actionHints(),
|
|
491
|
-
'Esc close',
|
|
492
|
-
];
|
|
464
|
+
// The search row is always live, so the surface, tab and action hints
|
|
465
|
+
// always show; Esc sits on the title row as a keycap, not in the hints.
|
|
466
|
+
const hints = [
|
|
467
|
+
...(frozen.hints ?? []),
|
|
468
|
+
...(frozenTab?.hints ?? []),
|
|
469
|
+
...this._actionHints(),
|
|
470
|
+
];
|
|
471
|
+
const unfilteredTab = this.surface?.buildView().tabs.find((t) => t.id === this.activeTabId);
|
|
472
|
+
const countSelectable = (rows: readonly ConfigModalRow[] | undefined): number =>
|
|
473
|
+
(rows ?? []).filter((r) => r.selectable !== false && r.header !== true).length;
|
|
493
474
|
|
|
494
475
|
return {
|
|
495
476
|
title: live?.title ?? frozen.title,
|
|
@@ -499,8 +480,14 @@ export class ConfigModal {
|
|
|
499
480
|
emptyText: frozenRows.length === 0 ? (frozenTab?.emptyText ?? 'Nothing to show.') : undefined,
|
|
500
481
|
degraded: live?.degraded ?? frozen.degraded,
|
|
501
482
|
status: this.statusMessage || undefined,
|
|
483
|
+
confirmPending: this.pendingConfirmKey !== null,
|
|
502
484
|
hints,
|
|
503
485
|
scroll: { offset: this.scrollOffset, total: allRows.length, visible },
|
|
486
|
+
search: {
|
|
487
|
+
query: this.filterQuery,
|
|
488
|
+
matched: countSelectable(frozenRows),
|
|
489
|
+
total: countSelectable(unfilteredTab?.rows),
|
|
490
|
+
},
|
|
504
491
|
};
|
|
505
492
|
}
|
|
506
493
|
|
|
@@ -534,9 +521,9 @@ export class ConfigModal {
|
|
|
534
521
|
* Modal sizing rule (owner, zero tolerance: a modal/list must never clip
|
|
535
522
|
* its full descriptive text, size to content or scroll, never clip).
|
|
536
523
|
* `scrollOffset`/`visible` are ROW-count based, which is exactly right when
|
|
537
|
-
* every row wraps to one line, but
|
|
538
|
-
* lines
|
|
539
|
-
*
|
|
524
|
+
* every row wraps to one line, but the kit list renders each row's WRAPPED
|
|
525
|
+
* lines inside a fixed list height (renderConfigModal passes that height
|
|
526
|
+
* to setViewportRows). A
|
|
540
527
|
* row whose label wraps to MULTIPLE lines can therefore push the total past
|
|
541
528
|
* that budget, and the LAST row handed to the renderer gets cut off
|
|
542
529
|
* mid-line instead of being deferred to the next scroll page, the
|
|
@@ -568,16 +555,9 @@ export class ConfigModal {
|
|
|
568
555
|
return out;
|
|
569
556
|
}
|
|
570
557
|
|
|
571
|
-
/** Footer text for the active filter: the query + a truthful match count. */
|
|
572
|
-
private _filterStatusHint(matched: number, total: number): string {
|
|
573
|
-
const q = this.filterQuery;
|
|
574
|
-
if (q.length === 0) return `/ type to filter (${total} row${total === 1 ? '' : 's'})`;
|
|
575
|
-
return `/${q}: ${matched} of ${total} match${matched === 1 ? '' : 'es'}`;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
558
|
/** Apply the active filter query to every tab's rows. A no-op passthrough when not filtering. */
|
|
579
559
|
private _applyFilter(view: ConfigModalView): ConfigModalView {
|
|
580
|
-
if (
|
|
560
|
+
if (this.filterQuery === '') return view;
|
|
581
561
|
const query = this.filterQuery.toLowerCase();
|
|
582
562
|
return { ...view, tabs: view.tabs.map((tab) => this._filterTab(tab, query)) };
|
|
583
563
|
}
|
|
@@ -593,9 +573,9 @@ export class ConfigModal {
|
|
|
593
573
|
* describes "no data at all", not "no data matches your filter").
|
|
594
574
|
*/
|
|
595
575
|
private _filterTab(tab: ConfigModalTab, query: string): ConfigModalTab {
|
|
596
|
-
const kept = tab.rows.filter((r) => r.selectable === false || r.label.toLowerCase().includes(query));
|
|
597
|
-
const hadSelectable = tab.rows.some((r) => r.selectable !== false);
|
|
598
|
-
const stillMatched = kept.some((r) => r.selectable !== false);
|
|
576
|
+
const kept = tab.rows.filter((r) => r.selectable === false || r.header === true || r.label.toLowerCase().includes(query));
|
|
577
|
+
const hadSelectable = tab.rows.some((r) => r.selectable !== false && r.header !== true);
|
|
578
|
+
const stillMatched = kept.some((r) => r.selectable !== false && r.header !== true);
|
|
599
579
|
if (hadSelectable && !stillMatched) {
|
|
600
580
|
return {
|
|
601
581
|
...tab,
|
|
@@ -632,23 +612,44 @@ export class ConfigModal {
|
|
|
632
612
|
}
|
|
633
613
|
|
|
634
614
|
private _selectableIds(tab: ConfigModalTab | undefined): string[] {
|
|
635
|
-
return (tab?.rows ?? []).filter((r) => r.selectable !== false).map((r) => r.id);
|
|
615
|
+
return (tab?.rows ?? []).filter((r) => r.selectable !== false && r.header !== true).map((r) => r.id);
|
|
636
616
|
}
|
|
637
617
|
|
|
638
618
|
private _firstSelectableId(tab: ConfigModalTab | undefined): string {
|
|
639
619
|
return this._selectableIds(tab)[0] ?? '';
|
|
640
620
|
}
|
|
641
621
|
|
|
622
|
+
/**
|
|
623
|
+
* Keep the selected row inside the visible line budget and never scroll
|
|
624
|
+
* past the point where the last rows fill the list. Counts wrapped lines
|
|
625
|
+
* (at the last render's wrap width), matching _windowRowsByLineBudget.
|
|
626
|
+
*/
|
|
642
627
|
private _clampScroll(): void {
|
|
643
628
|
const tab = this._frozenTab();
|
|
644
629
|
const rows = tab?.rows ?? [];
|
|
645
|
-
const
|
|
630
|
+
const lines = rows.map((r) => Math.max(1, wrapText(r.label, this.lastWrapWidth).length));
|
|
631
|
+
const span = (from: number, to: number): number => {
|
|
632
|
+
let n = 0;
|
|
633
|
+
for (let k = from; k <= to; k++) n += lines[k] ?? 0;
|
|
634
|
+
return n;
|
|
635
|
+
};
|
|
646
636
|
const visible = Math.max(3, this.visibleRows);
|
|
637
|
+
const idx = rows.findIndex((r) => r.id === this.selectedRowId);
|
|
647
638
|
if (idx >= 0) {
|
|
648
639
|
if (idx < this.scrollOffset) this.scrollOffset = idx;
|
|
649
|
-
|
|
640
|
+
while (this.scrollOffset < idx && span(this.scrollOffset, idx) > visible) this.scrollOffset++;
|
|
650
641
|
}
|
|
651
|
-
|
|
642
|
+
let maxOffset = rows.length;
|
|
643
|
+
while (maxOffset > 0 && span(maxOffset - 1, rows.length - 1) <= visible) maxOffset--;
|
|
644
|
+
// Informational rows before the first or after the last selectable row
|
|
645
|
+
// would otherwise never scroll into view: selecting the first selectable
|
|
646
|
+
// row shows the rows above it, the last one shows the rows below it.
|
|
647
|
+
const selectable = rows.map((r, k) => (r.selectable !== false && r.header !== true ? k : -1)).filter((k) => k >= 0);
|
|
648
|
+
if (idx >= 0 && idx === selectable[selectable.length - 1]) {
|
|
649
|
+
this.scrollOffset = Math.max(this.scrollOffset, Math.min(maxOffset, idx));
|
|
650
|
+
}
|
|
651
|
+
if (idx >= 0 && idx === selectable[0] && span(0, idx) <= visible) this.scrollOffset = 0;
|
|
652
652
|
this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxOffset));
|
|
653
653
|
}
|
|
654
|
+
|
|
654
655
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* confirm-dialog-types.ts, the shapes the confirm dialog and its renderer
|
|
3
|
+
* share (kept apart so the renderer never imports the modal it draws).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type ConfirmTone = 'danger' | 'warning' | 'primary';
|
|
7
|
+
|
|
8
|
+
export interface ConfirmOptions {
|
|
9
|
+
readonly title: string;
|
|
10
|
+
/** Wrapped in full; paragraphs split on '\n'. */
|
|
11
|
+
readonly body: string;
|
|
12
|
+
readonly confirmLabel: string;
|
|
13
|
+
readonly cancelLabel?: string;
|
|
14
|
+
/** danger: a red confirm chip; warning: amber; primary: the brand color. Default primary. */
|
|
15
|
+
readonly tone?: ConfirmTone;
|
|
16
|
+
/**
|
|
17
|
+
* A key chord that, pressed again while the dialog is open, confirms (the
|
|
18
|
+
* chord that opened it: ctrl+x twice stops an agent). Enter and y still work.
|
|
19
|
+
*/
|
|
20
|
+
readonly confirmChord?: { readonly key: string; readonly ctrl?: boolean };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** What the confirm renderer reads from the dialog. */
|
|
24
|
+
export interface ConfirmView {
|
|
25
|
+
readonly options: ConfirmOptions;
|
|
26
|
+
/** 0 = cancel (left), 1 = confirm (right). */
|
|
27
|
+
readonly chosen: 0 | 1;
|
|
28
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* confirm-dialog.ts, a small centered yes/no dialog on the kit surface.
|
|
3
|
+
*
|
|
4
|
+
* The safe choice sits on the left and is chosen by default for destructive
|
|
5
|
+
* and warning dialogs; the confirming button carries its tone (a red chip for
|
|
6
|
+
* destructive actions). Arrows or tab move between the buttons, Enter presses
|
|
7
|
+
* the chosen one, y and n answer directly, Esc cancels. An optional confirm
|
|
8
|
+
* chord (the chord that opened the dialog) pressed again also confirms.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
|
|
12
|
+
import type { SurfaceModal, SurfaceModalCloseReason, SurfaceModalHost } from './surface-modal-host.ts';
|
|
13
|
+
import type { SurfaceLayer } from '../renderer/surface-kit.ts';
|
|
14
|
+
import { renderConfirmDialog } from '../renderer/confirm-dialog.ts';
|
|
15
|
+
import type { ConfirmOptions, ConfirmView } from './confirm-dialog-types.ts';
|
|
16
|
+
export type { ConfirmOptions, ConfirmTone } from './confirm-dialog-types.ts';
|
|
17
|
+
|
|
18
|
+
export class ConfirmDialog implements SurfaceModal, ConfirmView {
|
|
19
|
+
readonly name = 'confirm';
|
|
20
|
+
/** 0 = cancel (left), 1 = confirm (right). */
|
|
21
|
+
chosen: 0 | 1;
|
|
22
|
+
private answered = false;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
readonly options: ConfirmOptions,
|
|
26
|
+
private readonly onResult: (confirmed: boolean) => void,
|
|
27
|
+
) {
|
|
28
|
+
this.chosen = options.tone === 'danger' || options.tone === 'warning' ? 0 : 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private answer(host: SurfaceModalHost, confirmed: boolean): void {
|
|
32
|
+
this.answered = true;
|
|
33
|
+
host.close(this, 'done');
|
|
34
|
+
this.onResult(confirmed);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
handleToken(token: InputToken, host: SurfaceModalHost): void {
|
|
38
|
+
if (token.type === 'text') {
|
|
39
|
+
const ch = token.value.toLowerCase();
|
|
40
|
+
if (ch === 'y') this.answer(host, true);
|
|
41
|
+
else if (ch === 'n') this.answer(host, false);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (token.type !== 'key') return;
|
|
45
|
+
const key = token.logicalName ?? '';
|
|
46
|
+
const chord = this.options.confirmChord;
|
|
47
|
+
if (chord && key === chord.key && Boolean(token.ctrl) === Boolean(chord.ctrl)) { this.answer(host, true); return; }
|
|
48
|
+
if (key === 'left' || key === 'right' || key === 'tab') this.chosen = this.chosen === 0 ? 1 : 0;
|
|
49
|
+
else if (key === 'enter') this.answer(host, this.chosen === 1);
|
|
50
|
+
else if (key === 'y') this.answer(host, true);
|
|
51
|
+
else if (key === 'n') this.answer(host, false);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
onClose(_reason: SurfaceModalCloseReason): void {
|
|
55
|
+
// Esc or a cleared stack is a "no": the safe answer to an unanswered question.
|
|
56
|
+
if (!this.answered) {
|
|
57
|
+
this.answered = true;
|
|
58
|
+
this.onResult(false);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
render(screenWidth: number, screenHeight: number): SurfaceLayer {
|
|
63
|
+
return renderConfirmDialog(this, screenWidth, screenHeight);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Ask through a host; resolves true only when the confirming button was pressed. */
|
|
68
|
+
export function confirmThrough(host: SurfaceModalHost, options: ConfirmOptions): Promise<boolean> {
|
|
69
|
+
return new Promise<boolean>((resolve) => host.push(new ConfirmDialog(options, resolve)));
|
|
70
|
+
}
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
// 'delete' → forward-delete (the character after cursor) when a moveable
|
|
8
8
|
// cursor exists; a no-op in cursorless/end-anchored contexts,
|
|
9
9
|
// EXCEPT where it opens a confirmation-gated clear action
|
|
10
|
-
// (see planning
|
|
10
|
+
// (see the planning modal: Delete opens the ConfirmState gate that
|
|
11
11
|
// lets the user clear their entire draft answer).
|
|
12
12
|
//
|
|
13
13
|
// Consequences for each surface:
|
|
14
|
-
//
|
|
14
|
+
// View search filters (end-anchored, no cursor)
|
|
15
15
|
// 'backspace' → remove last char ✓
|
|
16
16
|
// 'delete' → no-op ✓ (no cursor; nothing is "forward")
|
|
17
17
|
//
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// 'backspace' → remove last char ✓
|
|
20
20
|
// 'delete' → no-op ✓
|
|
21
21
|
//
|
|
22
|
-
// Planning
|
|
22
|
+
// Planning draft answer (end-anchored, no cursor)
|
|
23
23
|
// 'backspace' → remove last char ✓
|
|
24
24
|
// 'delete' → open ConfirmState gate (y/Enter confirms clear; n/Esc cancels)
|
|
25
25
|
// The draft is NOT wiped until the user confirms.
|
|
@@ -24,6 +24,7 @@ import type { InputHistory, HistorySearch } from './input-history.ts';
|
|
|
24
24
|
import type { ConversationManager } from '../core/conversation';
|
|
25
25
|
import type { BlockActionsMenu } from '../renderer/block-actions.ts';
|
|
26
26
|
import type { ContextInspectorModal } from '../renderer/context-inspector.ts';
|
|
27
|
+
import type { OverlayFilters } from './overlay-filter.ts';
|
|
27
28
|
import type { BookmarkModal } from './bookmark-modal.ts';
|
|
28
29
|
import type { SettingsModal } from './settings-modal.ts';
|
|
29
30
|
import type { McpWorkspace } from './mcp-workspace.ts';
|
|
@@ -32,14 +33,11 @@ import type { ConfigModal } from './config-modal.ts';
|
|
|
32
33
|
import type { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
33
34
|
import type { OnboardingWizardController } from './onboarding/onboarding-wizard.ts';
|
|
34
35
|
import type { WrappedPromptInfo } from './handler-prompt-buffer.ts';
|
|
35
|
-
import type { Panel } from '../panels/types.ts';
|
|
36
|
-
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
37
36
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
38
37
|
import type { ModelPickerTarget } from './model-picker.ts';
|
|
39
38
|
import type { KillRing } from './kill-ring.ts';
|
|
40
|
-
import type { PanelMouseLayout } from './handler-feed-routes.ts';
|
|
41
39
|
import type { FocusTracker } from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
42
|
-
import type {
|
|
40
|
+
import type { SurfaceModalHost } from './surface-modal-host.ts';
|
|
43
41
|
|
|
44
42
|
/**
|
|
45
43
|
* Initial mutable scalar values for InputFeedContext.
|
|
@@ -47,7 +45,7 @@ import type { PanelBurstGuardState } from './panel-paste-flood-guard.ts';
|
|
|
47
45
|
* **Mutable fields** (synced per-feed via syncFeedContextMutableFields or inside
|
|
48
46
|
* action closures that call syncFeedContextMutableFields):
|
|
49
47
|
* - `prompt`, `cursorPos`, current text buffer state
|
|
50
|
-
* - `commandMode`, `
|
|
48
|
+
* - `commandMode`, `indicatorFocused`, focus-mode flags
|
|
51
49
|
* - `helpOverlayActive`, `helpScrollOffset`, help overlay state
|
|
52
50
|
* - `shortcutsOverlayActive`, `shortcutsScrollOffset`, shortcuts overlay state
|
|
53
51
|
* - `nextPasteId`, `nextImageId`, monotonically increasing ID counters
|
|
@@ -60,7 +58,6 @@ export interface FeedContextMutableInit {
|
|
|
60
58
|
cursorPos: number;
|
|
61
59
|
inputScrollTop: number;
|
|
62
60
|
commandMode: boolean;
|
|
63
|
-
panelFocused: boolean;
|
|
64
61
|
indicatorFocused: boolean;
|
|
65
62
|
helpOverlayActive: boolean;
|
|
66
63
|
helpScrollOffset: number;
|
|
@@ -71,7 +68,6 @@ export interface FeedContextMutableInit {
|
|
|
71
68
|
mouseDownRow: number;
|
|
72
69
|
mouseDownCol: number;
|
|
73
70
|
contentWidth: number;
|
|
74
|
-
panelMouseLayout: PanelMouseLayout | null;
|
|
75
71
|
selectionCallback: ((result: SelectionResult | null) => void) | null;
|
|
76
72
|
}
|
|
77
73
|
|
|
@@ -84,7 +80,7 @@ export interface FeedContextMutableInit {
|
|
|
84
80
|
* `profilePickerModal`, modal objects constructed once
|
|
85
81
|
* - `filePicker`, `modelPicker`, `contextInspectorModal`, `blockActionsMenu`,
|
|
86
82
|
* `searchManager`, `historySearch`, `onboardingWizard`, service objects constructed once
|
|
87
|
-
* - `
|
|
83
|
+
* - `keybindingsManager`, from uiServices, stable
|
|
88
84
|
* - `modalStack`, reference to the handler's shared array
|
|
89
85
|
* - `getHistory`, `getViewportHeight`, `getScrollTop`, `scroll`, `exitApp`, callbacks
|
|
90
86
|
* - `commandRegistry`, `commandContext`, `autocomplete`, `inputHistory`,
|
|
@@ -117,17 +113,18 @@ export interface FeedContextStableRefs {
|
|
|
117
113
|
modelPicker: ModelPickerModal;
|
|
118
114
|
onboardingWizard: OnboardingWizardController;
|
|
119
115
|
contextInspectorModal: ContextInspectorModal;
|
|
116
|
+
/** Help / shortcuts overlay search rows (shared by reference with the renderer). */
|
|
117
|
+
overlayFilters: OverlayFilters;
|
|
120
118
|
blockActionsMenu: BlockActionsMenu;
|
|
119
|
+
/** Kit modals (command palette, confirm dialog, ...), see surface-modal-host.ts. */
|
|
120
|
+
surfaceModals?: SurfaceModalHost;
|
|
121
121
|
searchManager: SearchManager;
|
|
122
122
|
modalStack: string[];
|
|
123
123
|
inputHistory: InputHistory | null;
|
|
124
124
|
conversationManager: ConversationManager | null;
|
|
125
|
-
panelManager: PanelManager;
|
|
126
125
|
keybindingsManager: KeybindingsManager;
|
|
127
126
|
killRing: KillRing;
|
|
128
127
|
focusTracker: FocusTracker;
|
|
129
|
-
/** item 5, paste-flood guard state, mutated in place (never reallocated). */
|
|
130
|
-
panelBurstGuard: PanelBurstGuardState;
|
|
131
128
|
getHistory: () => InfiniteBuffer;
|
|
132
129
|
getViewportHeight: () => number;
|
|
133
130
|
getScrollTop: () => number;
|
|
@@ -138,6 +135,8 @@ export interface FeedContextStableRefs {
|
|
|
138
135
|
/** Bound method closures for InputFeedContext. Built in handler.ts where private members are accessible. */
|
|
139
136
|
export interface FeedContextClosures {
|
|
140
137
|
modalOpened: (name: string) => void;
|
|
138
|
+
/** The agent or process view controls (null before the shell wires them). */
|
|
139
|
+
getSessionView: () => import('./handler-session-view-route.ts').SessionViewControls | null;
|
|
141
140
|
handleEscape: () => void;
|
|
142
141
|
/** Deliver a concealed submission; returns true when concealed mode consumed it. */
|
|
143
142
|
submitConcealedInput: (value: string) => boolean;
|
|
@@ -158,11 +157,14 @@ export interface FeedContextClosures {
|
|
|
158
157
|
ensureInputCursorVisible: (contentWidth?: number) => void;
|
|
159
158
|
registerPaste: (content: string) => string;
|
|
160
159
|
executeBlockAction: (id: string) => void;
|
|
161
|
-
cyclePanelTab: (direction: 'next' | 'prev') => void;
|
|
162
|
-
onPanelInputConsumed: (activePanel: Panel | null, key: string) => void;
|
|
163
160
|
getWrappedPromptInfo: (contentWidth: number) => WrappedPromptInfo;
|
|
164
161
|
moveCursorVertical: (direction: -1 | 1) => boolean;
|
|
165
162
|
handlePathCompletion: () => boolean;
|
|
163
|
+
/** The view a clicked footer screen row opens (the usage rows open Usage), or undefined. */
|
|
164
|
+
footerTargetAt: (row: number) => import('../renderer/footer-targets.ts').FooterTarget | undefined;
|
|
165
|
+
openFooterTarget: (target: import('../renderer/footer-targets.ts').FooterTarget) => void;
|
|
166
|
+
/** Screen rows above the transcript (the header, the chips, the gap under them). */
|
|
167
|
+
bodyTopRow: () => number;
|
|
166
168
|
handleBlockToggle: () => void;
|
|
167
169
|
findMarkerAtPos: (pos: number) => { start: number; end: number } | null;
|
|
168
170
|
cleanupMarkerRegistry: (text: string) => void;
|
|
@@ -213,7 +215,6 @@ export function buildInitialFeedContext(
|
|
|
213
215
|
* - `prompt`, current prompt text buffer
|
|
214
216
|
* - `cursorPos`, caret position within prompt
|
|
215
217
|
* - `commandMode`, whether command-mode prefix is active
|
|
216
|
-
* - `panelFocused`, whether the active panel owns keyboard focus
|
|
217
218
|
* - `indicatorFocused`, whether the status indicator owns focus
|
|
218
219
|
* - `helpOverlayActive` / `helpScrollOffset`, help overlay visibility and scroll
|
|
219
220
|
* - `shortcutsOverlayActive` / `shortcutsScrollOffset`, shortcuts overlay state
|
|
@@ -248,7 +249,6 @@ export function syncFeedContextMutableFields(
|
|
|
248
249
|
ctx.cursorPos = fields.cursorPos;
|
|
249
250
|
ctx.inputScrollTop = fields.inputScrollTop;
|
|
250
251
|
ctx.commandMode = fields.commandMode;
|
|
251
|
-
ctx.panelFocused = fields.panelFocused;
|
|
252
252
|
ctx.indicatorFocused = fields.indicatorFocused;
|
|
253
253
|
ctx.helpOverlayActive = fields.helpOverlayActive;
|
|
254
254
|
ctx.helpScrollOffset = fields.helpScrollOffset;
|
|
@@ -260,5 +260,4 @@ export function syncFeedContextMutableFields(
|
|
|
260
260
|
ctx.mouseDownRow = fields.mouseDownRow;
|
|
261
261
|
ctx.mouseDownCol = fields.mouseDownCol;
|
|
262
262
|
ctx.contentWidth = fields.contentWidth;
|
|
263
|
-
ctx.panelMouseLayout = fields.panelMouseLayout;
|
|
264
263
|
}
|