@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
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* surface-kit-parts.ts, the smaller pieces of the modal surface kit:
|
|
3
|
+
*
|
|
4
|
+
* - element insets (a lighter fill inside a modal: 2 columns of side padding,
|
|
5
|
+
* a padding row above and below)
|
|
6
|
+
* - tabs (the active tab carries the gradient)
|
|
7
|
+
* - button chips (arrows pick, Enter confirms; the danger chip is red)
|
|
8
|
+
* - meters (▰▱)
|
|
9
|
+
* - toasts (top right, a colored ┃ on both sides, wrapped text)
|
|
10
|
+
* - composer popups (the slash and @ lists: anchored to the composer, no dim,
|
|
11
|
+
* ┃ bar, surface fill, selected row gradient, no frame and no hint rows)
|
|
12
|
+
*
|
|
13
|
+
* Colors come only from the active theme's tokens.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
17
|
+
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
18
|
+
import { activeTokens } from './theme.ts';
|
|
19
|
+
import {
|
|
20
|
+
NARROW_MODAL_BREAKPOINT,
|
|
21
|
+
SurfaceCanvas,
|
|
22
|
+
clipText,
|
|
23
|
+
type KitHint,
|
|
24
|
+
wrapLines,
|
|
25
|
+
type KitStyle,
|
|
26
|
+
type SurfaceLayer,
|
|
27
|
+
} from './surface-kit.ts';
|
|
28
|
+
import { drawList, drawRow, drawScrollingList, measureRow, type KitListResult, type KitRow } from './surface-kit-list.ts';
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Element insets
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/** Side padding inside an inset. */
|
|
35
|
+
const INSET_PAD_X = 2;
|
|
36
|
+
|
|
37
|
+
export interface KitInset {
|
|
38
|
+
/** Inset fill rectangle (canvas coordinates). */
|
|
39
|
+
readonly x: number;
|
|
40
|
+
readonly y: number;
|
|
41
|
+
readonly w: number;
|
|
42
|
+
readonly h: number;
|
|
43
|
+
/** Text area: columns l..r, rows top..bottom (inclusive). */
|
|
44
|
+
readonly l: number;
|
|
45
|
+
readonly r: number;
|
|
46
|
+
readonly top: number;
|
|
47
|
+
readonly bottom: number;
|
|
48
|
+
readonly bg: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Fill a lighter element inset and return its text area. */
|
|
52
|
+
export function inset(canvas: SurfaceCanvas, x: number, y: number, w: number, h: number, bg = activeTokens().backgroundElement): KitInset {
|
|
53
|
+
canvas.fill(x, y, w, h, bg);
|
|
54
|
+
return {
|
|
55
|
+
x, y, w, h, bg,
|
|
56
|
+
l: x + INSET_PAD_X,
|
|
57
|
+
r: x + w - 1 - INSET_PAD_X,
|
|
58
|
+
top: y + 1,
|
|
59
|
+
bottom: y + h - 2,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Put text inside an inset (keeps the inset bg). Returns the column after it. */
|
|
64
|
+
export function insetPut(canvas: SurfaceCanvas, p: KitInset, x: number, y: number, text: string, style: KitStyle = {}): number {
|
|
65
|
+
if (y < p.top || y > p.bottom) return x;
|
|
66
|
+
return canvas.put(x, y, clipText(text, p.r - x + 1), { ...style, bg: p.bg });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Wrapped text inside an inset from row y; returns the row after the last line drawn. */
|
|
70
|
+
export function insetWrap(canvas: SurfaceCanvas, p: KitInset, x: number, y: number, text: string, style: KitStyle = {}): number {
|
|
71
|
+
let yy = y;
|
|
72
|
+
for (const line of wrapLines(text, p.r - x + 1)) {
|
|
73
|
+
if (yy > p.bottom) break;
|
|
74
|
+
canvas.put(x, yy, line, { ...style, bg: p.bg });
|
|
75
|
+
yy++;
|
|
76
|
+
}
|
|
77
|
+
return yy;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** One line of inset text. */
|
|
81
|
+
export interface InsetLine { readonly text: string; readonly style: KitStyle }
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Wrap and draw paragraphs inside an inset from row y. When they do not all
|
|
85
|
+
* fit, the last visible row says how many lines are hidden instead of the text
|
|
86
|
+
* stopping silently. Returns the row after the last one drawn.
|
|
87
|
+
*/
|
|
88
|
+
export function insetLines(canvas: SurfaceCanvas, p: KitInset, x: number, y: number, paragraphs: readonly InsetLine[]): number {
|
|
89
|
+
const t = activeTokens();
|
|
90
|
+
const lines: InsetLine[] = [];
|
|
91
|
+
for (const para of paragraphs) {
|
|
92
|
+
if (para.text === '') lines.push(para);
|
|
93
|
+
else for (const text of wrapLines(para.text, p.r - x + 1)) lines.push({ text, style: para.style });
|
|
94
|
+
}
|
|
95
|
+
const room = Math.max(0, p.bottom - y + 1);
|
|
96
|
+
const overflow = lines.length > room;
|
|
97
|
+
const shown = overflow ? lines.slice(0, Math.max(0, room - 1)) : lines;
|
|
98
|
+
let yy = y;
|
|
99
|
+
for (const line of shown) canvas.put(x, yy++, line.text, { ...line.style, bg: p.bg });
|
|
100
|
+
if (overflow && room > 0) canvas.put(x, yy++, `${lines.length - shown.length} more lines`, { fg: t.textFaint, bg: p.bg });
|
|
101
|
+
return yy;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Keycap hints in a plain row (bars and status rows outside a modal)
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
/** Display width of a row of keycap hints (3 columns between hints). */
|
|
109
|
+
export function keycapHintsWidth(hints: readonly KitHint[]): number {
|
|
110
|
+
return hints.reduce((sum, [key, action], i) => sum + (i > 0 ? 3 : 0) + getDisplayWidth(key) + 3 + getDisplayWidth(action), 0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Paint keycap hints into a Line from column x (each key a bold chip on the
|
|
115
|
+
* keycap fill, its action in `actionStyle`), the same look as a modal's hint
|
|
116
|
+
* row. Hints that do not fit before `endX` are left out whole, never cut.
|
|
117
|
+
* Returns the column after the last hint drawn.
|
|
118
|
+
*/
|
|
119
|
+
export function paintKeycapHints(line: Line, x: number, endX: number, hints: readonly KitHint[], actionStyle: { readonly fg: string; readonly bg?: string }): number {
|
|
120
|
+
const t = activeTokens();
|
|
121
|
+
let cx = x;
|
|
122
|
+
const put = (text: string, style: { fg: string; bg?: string; bold?: boolean }): void => {
|
|
123
|
+
for (const ch of text) {
|
|
124
|
+
const w = getDisplayWidth(ch);
|
|
125
|
+
if (cx + w > endX || cx >= line.length) return;
|
|
126
|
+
const prev = line[cx]!;
|
|
127
|
+
line[cx] = { ...prev, char: ch, fg: style.fg, bg: style.bg ?? prev.bg, bold: style.bold ?? false, dim: false, underline: false, italic: false, strikethrough: false };
|
|
128
|
+
if (w === 2 && cx + 1 < line.length) line[cx + 1] = { ...line[cx]!, char: '' };
|
|
129
|
+
cx += w;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
hints.forEach(([key, action], i) => {
|
|
133
|
+
const need = (i > 0 ? 3 : 0) + getDisplayWidth(key) + 3 + getDisplayWidth(action);
|
|
134
|
+
if (cx + need > endX) return;
|
|
135
|
+
if (i > 0) cx += 3;
|
|
136
|
+
put(` ${key} `, { fg: t.text, bg: t.border, bold: true });
|
|
137
|
+
cx += 1;
|
|
138
|
+
put(action, { fg: actionStyle.fg, bg: actionStyle.bg });
|
|
139
|
+
});
|
|
140
|
+
return cx;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Tabs
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
/** Tabs across a row; the active one carries the gradient with dark bold text. Returns the column after the last tab. */
|
|
148
|
+
export function tabs(canvas: SurfaceCanvas, x: number, y: number, labels: readonly string[], active: number, maxX: number): number {
|
|
149
|
+
const t = activeTokens();
|
|
150
|
+
let cx = x;
|
|
151
|
+
labels.forEach((label, k) => {
|
|
152
|
+
const w = getDisplayWidth(label);
|
|
153
|
+
if (cx + w > maxX) return;
|
|
154
|
+
if (k === active) {
|
|
155
|
+
canvas.grad(cx - 1, y, w + 2, t.brand, t.brandEnd);
|
|
156
|
+
canvas.put(cx, y, label, { fg: t.selectedListItemText, bold: true });
|
|
157
|
+
} else {
|
|
158
|
+
canvas.put(cx, y, label, { fg: t.textMuted });
|
|
159
|
+
}
|
|
160
|
+
cx += w + 4;
|
|
161
|
+
});
|
|
162
|
+
return cx;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Buttons, chips and meters
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
export type ButtonTone = 'primary' | 'warning' | 'danger';
|
|
170
|
+
|
|
171
|
+
/** A button chip. The chosen one is filled in its tone with dark bold text; the rest are quiet chips (the keycap fill). */
|
|
172
|
+
export function button(canvas: SurfaceCanvas, x: number, y: number, label: string, chosen: boolean, tone: ButtonTone = 'primary'): number {
|
|
173
|
+
const t = activeTokens();
|
|
174
|
+
const toneBg = tone === 'danger' ? t.error : tone === 'warning' ? t.warning : t.brand;
|
|
175
|
+
const style: KitStyle = chosen
|
|
176
|
+
? { fg: t.selectedListItemText, bg: toneBg, bold: true }
|
|
177
|
+
: { fg: t.textMuted, bg: t.border };
|
|
178
|
+
return canvas.put(x, y, ` ${label} `, style);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Width of a button chip. */
|
|
182
|
+
export function buttonWidth(label: string): number {
|
|
183
|
+
return getDisplayWidth(label) + 2;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** A small colored chip (e.g. a risk level). */
|
|
187
|
+
export function chip(canvas: SurfaceCanvas, x: number, y: number, label: string, bg: string): number {
|
|
188
|
+
const t = activeTokens();
|
|
189
|
+
return canvas.put(x, y, ` ${label} `, { fg: t.selectedListItemText, bg, bold: true });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** A 10-step meter (▰ filled, ▱ empty) in `color`. Returns the column after it. */
|
|
193
|
+
export function meter(canvas: SurfaceCanvas, x: number, y: number, fraction: number, color: string, bg?: string): number {
|
|
194
|
+
const t = activeTokens();
|
|
195
|
+
const n = Math.max(0, Math.min(10, Math.round(fraction * 10)));
|
|
196
|
+
let cx = x;
|
|
197
|
+
for (let q = 0; q < 10; q++) cx = canvas.put(cx, y, q < n ? '▰' : '▱', { fg: q < n ? color : t.border, bg });
|
|
198
|
+
return cx;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Toasts
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
export type ToastTone = 'info' | 'success' | 'warning' | 'error';
|
|
206
|
+
|
|
207
|
+
export interface ToastSpec {
|
|
208
|
+
readonly title: string;
|
|
209
|
+
readonly body?: string;
|
|
210
|
+
readonly tone: ToastTone;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function toastToneColor(tone: ToastTone): string {
|
|
214
|
+
const t = activeTokens();
|
|
215
|
+
return tone === 'error' ? t.error : tone === 'warning' ? t.warning : tone === 'success' ? t.success : t.info;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Width of a toast on a screen of width screenW. */
|
|
219
|
+
function toastWidth(screenW: number): number {
|
|
220
|
+
return Math.max(20, Math.min(50, screenW - 4));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Rows toasts may use: from `top` down to (not including) `bottom`. */
|
|
224
|
+
export interface ToastArea {
|
|
225
|
+
readonly top: number;
|
|
226
|
+
/** First row toasts must not cover: the composer's top (the status line and composer sit below it). */
|
|
227
|
+
readonly bottom: number;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Cleared columns left of each toast: text beside it keeps 2 columns from its fill (Measurements). */
|
|
231
|
+
const TOAST_GAP_COLS = 2;
|
|
232
|
+
/** Cleared rows between two stacked toasts and under the lowest one. */
|
|
233
|
+
const TOAST_GAP_ROWS = 1;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Toasts stacked in the top right corner (below the header row), newest first.
|
|
237
|
+
* Each toast: ┃ in its tone on both sides, a padding row above and below, the
|
|
238
|
+
* title bold, the body muted and wrapped in full. They stay inside `area`, so
|
|
239
|
+
* they never cover the composer or the status line: a toast that does not fit
|
|
240
|
+
* below the ones above it waits in the notification history, and a single
|
|
241
|
+
* toast taller than the area shows what fits and ends with where the full
|
|
242
|
+
* text is.
|
|
243
|
+
*
|
|
244
|
+
* Toasts float over the transcript, so the layer carries its own gap: a
|
|
245
|
+
* cleared row between stacked toasts and under the lowest one, and
|
|
246
|
+
* TOAST_GAP_COLS cleared columns on their left. The layer is `clearBlank`, so
|
|
247
|
+
* those cells show the plain screen, never a transcript block's fill or text
|
|
248
|
+
* touching a toast's edge.
|
|
249
|
+
*/
|
|
250
|
+
export function renderToasts(screenW: number, screenH: number, toasts: readonly ToastSpec[], area: ToastArea = { top: 1, bottom: screenH - 2 }): SurfaceLayer | null {
|
|
251
|
+
const top = Math.max(0, area.top);
|
|
252
|
+
// Room for toasts: the area less the gap row the lowest toast keeps under it.
|
|
253
|
+
const maxH = Math.min(screenH, area.bottom) - top - TOAST_GAP_ROWS;
|
|
254
|
+
if (toasts.length === 0 || screenW < 24 || maxH < 3) return null;
|
|
255
|
+
const t = activeTokens();
|
|
256
|
+
const w = toastWidth(screenW);
|
|
257
|
+
const textW = w - 6;
|
|
258
|
+
const blocks = toasts.map((toast) => {
|
|
259
|
+
const lines = [
|
|
260
|
+
...wrapLines(toast.title, textW).map((text) => ({ text, bold: true, fg: t.text })),
|
|
261
|
+
...(toast.body ? wrapLines(toast.body, textW).map((text) => ({ text, bold: false, fg: t.textMuted })) : []),
|
|
262
|
+
];
|
|
263
|
+
return { toast, lines, h: lines.length + 2 };
|
|
264
|
+
});
|
|
265
|
+
let total = 0;
|
|
266
|
+
const shown: typeof blocks = [];
|
|
267
|
+
for (const block of blocks) {
|
|
268
|
+
const need = total + (shown.length > 0 ? TOAST_GAP_ROWS : 0) + block.h;
|
|
269
|
+
if (need > maxH) break;
|
|
270
|
+
shown.push(block);
|
|
271
|
+
total = need;
|
|
272
|
+
}
|
|
273
|
+
if (shown.length === 0) {
|
|
274
|
+
// The newest toast alone is taller than the area: show what fits, and say
|
|
275
|
+
// where the rest is instead of cutting it off silently.
|
|
276
|
+
const first = blocks[0]!;
|
|
277
|
+
const room = maxH - 2;
|
|
278
|
+
const pointer = { text: 'full text in /notifications', bold: false, fg: t.textFaint };
|
|
279
|
+
const lines = room <= 1 ? [pointer] : [...first.lines.slice(0, room - 1), pointer];
|
|
280
|
+
shown.push({ toast: first.toast, lines, h: lines.length + 2 });
|
|
281
|
+
total = lines.length + 2;
|
|
282
|
+
}
|
|
283
|
+
// Blank cells (the left gap columns, the rows between toasts, the gap row
|
|
284
|
+
// under the lowest one) stay blank: the layer clears them on the screen.
|
|
285
|
+
const canvas = new SurfaceCanvas(w + TOAST_GAP_COLS, total + TOAST_GAP_ROWS);
|
|
286
|
+
let y = 0;
|
|
287
|
+
shown.forEach((block, k) => {
|
|
288
|
+
if (k > 0) y += TOAST_GAP_ROWS;
|
|
289
|
+
const bar = toastToneColor(block.toast.tone);
|
|
290
|
+
const x0 = TOAST_GAP_COLS;
|
|
291
|
+
for (let r = 0; r < block.h; r++) {
|
|
292
|
+
canvas.fill(x0, y + r, w, 1, t.backgroundPanel);
|
|
293
|
+
canvas.put(x0, y + r, '┃', { fg: bar });
|
|
294
|
+
canvas.put(x0 + w - 1, y + r, '┃', { fg: bar });
|
|
295
|
+
}
|
|
296
|
+
block.lines.forEach((line, i) => canvas.put(x0 + 3, y + 1 + i, line.text, { fg: line.fg, bold: line.bold }));
|
|
297
|
+
y += block.h;
|
|
298
|
+
});
|
|
299
|
+
return { x: screenW - w - 2 - TOAST_GAP_COLS, y: top, lines: canvas.lines, dim: false, clearBlank: true };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Composer popups (slash commands, @ files)
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
/** Column of the popup's ┃ bar (the composer's own left edge). */
|
|
307
|
+
export const POPUP_BAR_X = 2;
|
|
308
|
+
|
|
309
|
+
export interface PopupOptions {
|
|
310
|
+
/** Width of the lines to produce (the conversation width). */
|
|
311
|
+
readonly width: number;
|
|
312
|
+
readonly rows: readonly KitRow[];
|
|
313
|
+
/** A row pinned above the list (it never scrolls away), e.g. the typed query. */
|
|
314
|
+
readonly header?: KitRow;
|
|
315
|
+
/** Maximum rows the content may use (padding rows excluded). */
|
|
316
|
+
readonly maxRows: number;
|
|
317
|
+
/** Remembers the list's scroll position between renders (the picker's state object). */
|
|
318
|
+
readonly scrollOwner?: object;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface PopupResult {
|
|
322
|
+
readonly lines: Line[];
|
|
323
|
+
readonly list: KitListResult;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* A composer popup: the ┃ bar at column 2 in the faint color, a surface fill
|
|
328
|
+
* from column 3 to width-3 with a padding row above and below, text from
|
|
329
|
+
* column 5 and right-aligned text ending 2 columns inside the fill. Returns
|
|
330
|
+
* full-width lines (cells left of the bar and right of the fill are empty)
|
|
331
|
+
* for docking right above the composer. A list longer than the popup keeps
|
|
332
|
+
* the selected row in view and says what is hidden on its last row.
|
|
333
|
+
*/
|
|
334
|
+
export function renderPopup(options: PopupOptions): PopupResult {
|
|
335
|
+
const t = activeTokens();
|
|
336
|
+
const { width } = options;
|
|
337
|
+
const fillX = POPUP_BAR_X + 1;
|
|
338
|
+
const fillW = Math.max(6, width - fillX - 2);
|
|
339
|
+
const x0 = fillX + 2;
|
|
340
|
+
const x1 = fillX + fillW - 3;
|
|
341
|
+
const headerRows = options.header ? measureRow(options.header, x0, x1) : 0;
|
|
342
|
+
const listMax = Math.max(1, options.maxRows - headerRows);
|
|
343
|
+
const scrollKey = options.scrollOwner ? { owner: options.scrollOwner, name: 'popup' } : undefined;
|
|
344
|
+
// Measure on a scratch canvas: how many rows the list really needs.
|
|
345
|
+
const compact = width < NARROW_MODAL_BREAKPOINT;
|
|
346
|
+
const scratch = new SurfaceCanvas(width, listMax + 1);
|
|
347
|
+
scratch.compact = compact;
|
|
348
|
+
const trial = drawList(scratch, { rows: options.rows, top: 0, bottom: listMax - 1, x0, x1, scrollKey });
|
|
349
|
+
const overflow = trial.above > 0 || trial.below > 0;
|
|
350
|
+
const listUsed = overflow ? listMax : Math.max(1, trial.endY);
|
|
351
|
+
const used = headerRows + listUsed;
|
|
352
|
+
const canvas = new SurfaceCanvas(width, used + 2);
|
|
353
|
+
canvas.compact = compact;
|
|
354
|
+
canvas.fill(fillX, 0, fillW, used + 2, t.backgroundPanel);
|
|
355
|
+
if (options.header) drawRow(canvas, 1, options.header, x0, x1);
|
|
356
|
+
const listOptions = { rows: options.rows, top: 1 + headerRows, bottom: used, x0, x1, scrollKey };
|
|
357
|
+
const list = overflow ? drawScrollingList(canvas, listOptions) : drawList(canvas, listOptions);
|
|
358
|
+
for (let r = 0; r < used + 2; r++) canvas.put(POPUP_BAR_X, r, '┃', { fg: t.textFaint });
|
|
359
|
+
return { lines: canvas.lines, list };
|
|
360
|
+
}
|