@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
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* process-view.ts, a background process opened full screen: its live output.
|
|
3
|
+
*
|
|
4
|
+
* row 0 blank
|
|
5
|
+
* rows HH:MM:SS at column 3 (faint), the line from column 13,
|
|
6
|
+
* wrapped to width-4 with continuations at column 13; a
|
|
7
|
+
* line that reads as an error in the error color
|
|
8
|
+
* second-last blank
|
|
9
|
+
* last the follow state: "▾ following · new output scrolls in",
|
|
10
|
+
* or how far up you are, or how the process ended; while
|
|
11
|
+
* searching, the search line in its place
|
|
12
|
+
*
|
|
13
|
+
* Follow mode is on by default: the newest output stays in view. Scrolling
|
|
14
|
+
* up leaves it; End (or scrolling back to the bottom) returns to it. A search
|
|
15
|
+
* leaves follow mode and jumps between matching lines.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createEmptyLine, type Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
19
|
+
import { getDisplayWidth, truncateDisplay, wrapText } from '../utils/terminal-width.ts';
|
|
20
|
+
import { activeTokens } from './theme.ts';
|
|
21
|
+
import { keycapHintsWidth, paintKeycapHints } from './surface-kit-parts.ts';
|
|
22
|
+
import type { ProcessLine } from '../core/process-output-log.ts';
|
|
23
|
+
|
|
24
|
+
const TIME_X = 3;
|
|
25
|
+
const TEXT_X = 13;
|
|
26
|
+
|
|
27
|
+
/** One screen row of output. */
|
|
28
|
+
export interface ProcessRow {
|
|
29
|
+
/** The source line's index in the log. */
|
|
30
|
+
readonly source: number;
|
|
31
|
+
/** Set on the first row of a line. */
|
|
32
|
+
readonly time?: string;
|
|
33
|
+
readonly text: string;
|
|
34
|
+
readonly error: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ProcessSearchState {
|
|
38
|
+
readonly query: string;
|
|
39
|
+
/** Typing the query (the search line takes the keys). */
|
|
40
|
+
readonly editing: boolean;
|
|
41
|
+
/** The log line of the current match (-1 when nothing matches). */
|
|
42
|
+
readonly current: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ProcessViewInput {
|
|
46
|
+
readonly width: number;
|
|
47
|
+
/** Rows the view may use. */
|
|
48
|
+
readonly height: number;
|
|
49
|
+
readonly rows: readonly ProcessRow[];
|
|
50
|
+
/** Rows scrolled up from the newest; 0 follows the output. */
|
|
51
|
+
readonly scrollFromBottom: number;
|
|
52
|
+
readonly color: string;
|
|
53
|
+
/** How the process ended, once it has ("exited with code 0 · 12m 04s"). */
|
|
54
|
+
readonly ended?: { readonly text: string; readonly ok: boolean } | null;
|
|
55
|
+
readonly search?: ProcessSearchState | null;
|
|
56
|
+
/** Lines the log dropped off the front (its bound). */
|
|
57
|
+
readonly dropped?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function twoDigits(n: number): string {
|
|
61
|
+
return n < 10 ? `0${n}` : String(n);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** HH:MM:SS in local time. */
|
|
65
|
+
function clockTime(at: number): string {
|
|
66
|
+
const d = new Date(at);
|
|
67
|
+
return `${twoDigits(d.getHours())}:${twoDigits(d.getMinutes())}:${twoDigits(d.getSeconds())}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Wrap log lines into screen rows (the caller caches this per width and line count). */
|
|
71
|
+
export function processRows(lines: readonly ProcessLine[], width: number, formatTime: (at: number) => string = clockTime): ProcessRow[] {
|
|
72
|
+
const room = Math.max(10, width - 4 - TEXT_X);
|
|
73
|
+
const rows: ProcessRow[] = [];
|
|
74
|
+
lines.forEach((line, source) => {
|
|
75
|
+
const parts = line.text.length === 0 ? [''] : wrapText(line.text, room);
|
|
76
|
+
parts.forEach((text, k) => rows.push({ source, time: k === 0 ? formatTime(line.at) : undefined, text, error: line.error }));
|
|
77
|
+
});
|
|
78
|
+
return rows;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Source indexes of lines containing the query (case-insensitive). */
|
|
82
|
+
export function processMatches(lines: readonly ProcessLine[], query: string): number[] {
|
|
83
|
+
const q = query.toLowerCase();
|
|
84
|
+
if (!q) return [];
|
|
85
|
+
const out: number[] = [];
|
|
86
|
+
lines.forEach((line, i) => { if (line.text.toLowerCase().includes(q)) out.push(i); });
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function put(line: Line, x: number, endX: number, text: string, style: { fg: string; bg?: string; bold?: boolean }): number {
|
|
91
|
+
let cx = x;
|
|
92
|
+
for (const ch of text) {
|
|
93
|
+
const w = getDisplayWidth(ch);
|
|
94
|
+
if (w <= 0) continue;
|
|
95
|
+
if (cx + w > endX || cx >= line.length) break;
|
|
96
|
+
line[cx] = { char: ch, fg: style.fg, bg: style.bg ?? '', bold: style.bold ?? false, dim: false, underline: false, italic: false, strikethrough: false };
|
|
97
|
+
if (w === 2 && cx + 1 < line.length) line[cx + 1] = { ...line[cx]!, char: '' };
|
|
98
|
+
cx += w;
|
|
99
|
+
}
|
|
100
|
+
return cx;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function blank(width: number): Line {
|
|
104
|
+
const line = createEmptyLine(width);
|
|
105
|
+
for (const cell of line) cell.bg = '';
|
|
106
|
+
return line;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Tint every occurrence of the query on a drawn row. */
|
|
110
|
+
function markMatches(line: Line, text: string, query: string, bg: string): void {
|
|
111
|
+
if (!query) return;
|
|
112
|
+
const lower = text.toLowerCase();
|
|
113
|
+
const q = query.toLowerCase();
|
|
114
|
+
let from = 0;
|
|
115
|
+
for (;;) {
|
|
116
|
+
const at = lower.indexOf(q, from);
|
|
117
|
+
if (at < 0) return;
|
|
118
|
+
const x0 = TEXT_X + getDisplayWidth(text.slice(0, at));
|
|
119
|
+
const w = getDisplayWidth(text.slice(at, at + q.length));
|
|
120
|
+
for (let x = x0; x < x0 + w && x < line.length; x++) line[x] = { ...line[x]!, bg };
|
|
121
|
+
from = at + q.length;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The largest scrollFromBottom that still shows rows. */
|
|
126
|
+
export function processMaxScroll(rowCount: number, height: number): number {
|
|
127
|
+
return Math.max(0, rowCount - Math.max(1, height - 3));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function renderProcessView(input: ProcessViewInput): Line[] {
|
|
131
|
+
const t = activeTokens();
|
|
132
|
+
const { width } = input;
|
|
133
|
+
const logRows = Math.max(1, input.height - 3);
|
|
134
|
+
const end = width - 3;
|
|
135
|
+
const scroll = Math.max(0, Math.min(input.scrollFromBottom, processMaxScroll(input.rows.length, input.height)));
|
|
136
|
+
const last = input.rows.length - scroll;
|
|
137
|
+
const first = Math.max(0, last - logRows);
|
|
138
|
+
const search = input.search ?? null;
|
|
139
|
+
|
|
140
|
+
const out: Line[] = [blank(width)];
|
|
141
|
+
if (input.rows.length === 0) {
|
|
142
|
+
const line = blank(width);
|
|
143
|
+
put(line, TIME_X, end, input.ended ? 'The process printed nothing.' : 'No output yet.', { fg: t.textFaint });
|
|
144
|
+
out.push(line);
|
|
145
|
+
}
|
|
146
|
+
if (first === 0 && (input.dropped ?? 0) > 0 && input.rows.length > 0) {
|
|
147
|
+
const line = blank(width);
|
|
148
|
+
put(line, TIME_X, end, `… ${input.dropped} earlier lines are no longer kept`, { fg: t.textFaint });
|
|
149
|
+
out.push(line);
|
|
150
|
+
}
|
|
151
|
+
for (let r = first; r < last; r++) {
|
|
152
|
+
const row = input.rows[r]!;
|
|
153
|
+
const line = blank(width);
|
|
154
|
+
if (row.time) put(line, TIME_X, TEXT_X - 1, row.time, { fg: t.textFaint });
|
|
155
|
+
put(line, TEXT_X, end, row.text, { fg: row.error ? t.error : t.text });
|
|
156
|
+
if (search && search.query) markMatches(line, row.text, search.query, row.source === search.current ? t.searchCurrentBg : t.searchMatchBg);
|
|
157
|
+
out.push(line);
|
|
158
|
+
}
|
|
159
|
+
while (out.length < input.height - 2) out.push(blank(width));
|
|
160
|
+
out.length = Math.min(out.length, Math.max(0, input.height - 2));
|
|
161
|
+
out.push(blank(width));
|
|
162
|
+
out.push(statusRow(input, scroll, end));
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function statusRow(input: ProcessViewInput, scroll: number, end: number): Line {
|
|
167
|
+
const t = activeTokens();
|
|
168
|
+
const line = blank(input.width);
|
|
169
|
+
const search = input.search ?? null;
|
|
170
|
+
if (search && (search.editing || search.query)) {
|
|
171
|
+
let x = put(line, TIME_X, end, '/', { fg: input.color, bold: true });
|
|
172
|
+
x = put(line, x + 1, end, search.query, { fg: t.text });
|
|
173
|
+
if (search.editing) x = put(line, x, end, '▏', { fg: input.color });
|
|
174
|
+
const hints: Array<[string, string]> = search.editing ? [['⏎', 'next'], ['esc', 'close']] : [['n', 'next'], ['/', 'edit'], ['esc', 'close']];
|
|
175
|
+
const hintsW = keycapHintsWidth(hints);
|
|
176
|
+
if (x + 3 + hintsW <= end) paintKeycapHints(line, x + 3, end, hints, { fg: t.textFaint });
|
|
177
|
+
return line;
|
|
178
|
+
}
|
|
179
|
+
if (scroll > 0) {
|
|
180
|
+
const x = put(line, TIME_X, end, '▴', { fg: input.color, bold: true });
|
|
181
|
+
put(line, x + 1, end, truncateDisplay(`paused · ${scroll} newer row${scroll === 1 ? '' : 's'} below · end follows again`, Math.max(0, end - x - 1)), { fg: t.textFaint });
|
|
182
|
+
return line;
|
|
183
|
+
}
|
|
184
|
+
if (input.ended) {
|
|
185
|
+
const x = put(line, TIME_X, end, '■', { fg: input.ended.ok ? t.success : t.error, bold: true });
|
|
186
|
+
put(line, x + 1, end, truncateDisplay(input.ended.text, Math.max(0, end - x - 1)), { fg: input.ended.ok ? t.textFaint : t.error });
|
|
187
|
+
return line;
|
|
188
|
+
}
|
|
189
|
+
const x = put(line, TIME_X, end, '▾', { fg: input.color, bold: true });
|
|
190
|
+
put(line, x + 1, end, 'following · new output scrolls in', { fg: t.textFaint });
|
|
191
|
+
return line;
|
|
192
|
+
}
|
|
@@ -1,128 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* renderProfilePickerModal,
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Footer hints: [Up/Down] Navigate [Enter] Load [d] Arm/Delete [s] Save current [Esc] Close
|
|
2
|
+
* renderProfilePickerModal, the /profiles picker drawn with the modal surface
|
|
3
|
+
* kit: the always-live search row (filters by name), one kit row per saved
|
|
4
|
+
* profile (name, saved time right-aligned), an armed delete shown in the
|
|
5
|
+
* error color with its "press d again" prompt, the status line, and keycap
|
|
6
|
+
* hints: ↑↓ move · ⏎ load · d delete · s save current.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
|
-
import type { Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
11
|
-
import { ModalFactory } from './modal-factory.ts';
|
|
12
9
|
import type { ProfilePickerModal } from '../input/profile-picker-modal.ts';
|
|
13
10
|
import { formatTimestamp } from './modal-utils.ts';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
11
|
+
import { activeTokens } from './theme.ts';
|
|
12
|
+
import {
|
|
13
|
+
beginModal,
|
|
14
|
+
finishModal,
|
|
15
|
+
searchRow,
|
|
16
|
+
scrollCountText,
|
|
17
|
+
type KitHint,
|
|
18
|
+
type SurfaceLayer,
|
|
19
|
+
} from './surface-kit.ts';
|
|
20
|
+
import { drawList, type KitRow } from './surface-kit-list.ts';
|
|
21
|
+
import { drawTextBlock, listHeight, modalHeightFor, modalTextWidth, textBlockHeight, type TextLine } from './surface-kit-extra.ts';
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
// Renderer
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
23
|
+
const HINTS: readonly KitHint[] = [['↑↓', 'move'], ['⏎', 'load'], ['d', 'delete'], ['s', 'save current']];
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
|
-
* Render the profile picker modal as
|
|
23
|
-
*
|
|
24
|
-
* @param modal ProfilePickerModal state object.
|
|
25
|
-
* @param width Terminal width.
|
|
26
|
+
* Render the profile picker modal as a SurfaceLayer in screen coordinates.
|
|
26
27
|
*/
|
|
27
28
|
export function renderProfilePickerModal(
|
|
28
29
|
modal: ProfilePickerModal,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
):
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
screenWidth: number,
|
|
31
|
+
screenHeight = 24,
|
|
32
|
+
): SurfaceLayer {
|
|
33
|
+
const t = activeTokens();
|
|
34
|
+
const visible = modal.visibleProfiles;
|
|
35
|
+
const armed = modal.deleteConfirmationTarget;
|
|
36
|
+
const rows: KitRow[] = visible.map((profile, i) => ({
|
|
37
|
+
label: profile.name,
|
|
38
|
+
right: formatTimestamp(profile.timestamp),
|
|
39
|
+
selected: i === modal.selectedIndex,
|
|
40
|
+
danger: armed === profile.name,
|
|
41
|
+
mark: armed === profile.name ? '✕' : undefined,
|
|
42
|
+
markFg: t.error,
|
|
43
|
+
}));
|
|
43
44
|
|
|
44
|
-
const
|
|
45
|
+
const notes: TextLine[] = [];
|
|
46
|
+
if (armed) notes.push({ text: `Press d again to permanently delete ${armed}.`, style: { fg: t.warning, bold: true } });
|
|
47
|
+
else if (modal.statusMessage) notes.push({ text: modal.statusMessage, style: { fg: t.accent } });
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
style: { fg: '240', dim: true },
|
|
56
|
-
});
|
|
57
|
-
} else {
|
|
58
|
-
// Proportional column widths that adapt to the modal's content width:
|
|
59
|
-
// timestamp ~22% (clamped 10..16); the name column absorbs the remainder
|
|
60
|
-
// (ported from session-picker-modal.ts). Reserves 4 cols, 2 for the
|
|
61
|
-
// name/timestamp separator and 2 for the list row's selection indicator
|
|
62
|
-
// ("▸ ") that ModalFactory prepends outside the wrapped label, so the
|
|
63
|
-
// row never spills the timestamp's embedded space onto a wrapped line.
|
|
64
|
-
const tsW = Math.min(16, Math.max(10, Math.floor(contentW * 0.22)));
|
|
65
|
-
const nameW = Math.max(8, contentW - tsW - 4);
|
|
66
|
-
|
|
67
|
-
// Column header
|
|
68
|
-
const nameHdr = fitDisplay('Name', nameW);
|
|
69
|
-
const tsHdr = fitDisplay('Saved', tsW);
|
|
70
|
-
sections.push({
|
|
71
|
-
type: 'text',
|
|
72
|
-
content: `${nameHdr} ${tsHdr}`,
|
|
73
|
-
style: { fg: '240', dim: true },
|
|
74
|
-
});
|
|
75
|
-
sections.push({ type: 'separator' });
|
|
76
|
-
|
|
77
|
-
const visibleProfiles = modal.profiles.slice(modal.scrollOffset, modal.scrollOffset + visibleRows);
|
|
78
|
-
const listItems: import('./modal-factory.ts').ModalListItem[] = visibleProfiles.map((prof, idx) => {
|
|
79
|
-
const isSelected = modal.scrollOffset + idx === modal.selectedIndex;
|
|
49
|
+
const width = modalTextWidth(screenWidth, screenHeight);
|
|
50
|
+
const allRows = modal.profiles.map((profile): KitRow => ({ label: profile.name, right: formatTimestamp(profile.timestamp) }));
|
|
51
|
+
const emptyLines: TextLine[] = [
|
|
52
|
+
{ text: 'No saved profiles.', style: { fg: t.textMuted } },
|
|
53
|
+
{ text: 'Press s to save the current settings as a profile.', style: { fg: t.textFaint } },
|
|
54
|
+
];
|
|
55
|
+
const listRows = allRows.length > 0 ? listHeight(allRows, 0, width - 1) : textBlockHeight(emptyLines, width);
|
|
56
|
+
const noteRows = notes.length > 0 ? textBlockHeight(notes, width) + 1 : 0;
|
|
57
|
+
const height = modalHeightFor(screenWidth, screenHeight, { hints: HINTS }, 2 + listRows + noteRows);
|
|
80
58
|
|
|
81
|
-
|
|
59
|
+
const f = beginModal(screenWidth, screenHeight, { title: 'Profiles', hints: HINTS, height });
|
|
60
|
+
const total = modal.profiles.length;
|
|
61
|
+
searchRow(f, f.top, modal.query, 'Filter profiles', modal.query ? `${visible.length} of ${total}` : `${total} saved`);
|
|
82
62
|
|
|
83
|
-
|
|
63
|
+
const top = f.top + 2;
|
|
64
|
+
const listBottom = f.bottom - noteRows;
|
|
65
|
+
if (notes.length > 0) drawTextBlock(f.canvas, f.l, listBottom + 2, f.r - f.l + 1, notes, f.bottom);
|
|
84
66
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
sections.push({
|
|
93
|
-
type: 'text',
|
|
94
|
-
content: `[${modal.scrollOffset + 1}-${Math.min(modal.profiles.length, modal.scrollOffset + visibleRows)} of ${modal.profiles.length}]`,
|
|
95
|
-
style: { fg: '244', dim: true },
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Status message if present
|
|
101
|
-
if (modal.statusMessage) {
|
|
102
|
-
sections.push({ type: 'separator' });
|
|
103
|
-
sections.push({
|
|
104
|
-
type: 'text',
|
|
105
|
-
content: modal.statusMessage,
|
|
106
|
-
style: { fg: '#00ffcc' },
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
if (modal.deleteConfirmationTarget) {
|
|
110
|
-
sections.push({
|
|
111
|
-
type: 'text',
|
|
112
|
-
content: `Press [d] again to permanently delete ${modal.deleteConfirmationTarget}.`,
|
|
113
|
-
style: { fg: '#f59e0b', dim: true },
|
|
114
|
-
});
|
|
67
|
+
if (total === 0) {
|
|
68
|
+
drawTextBlock(f.canvas, f.l, top, f.r - f.l + 1, emptyLines, listBottom);
|
|
69
|
+
} else if (rows.length === 0) {
|
|
70
|
+
drawTextBlock(f.canvas, f.l, top, f.r - f.l + 1, [{ text: `No profiles match "${modal.query}".`, style: { fg: t.textMuted } }], listBottom);
|
|
71
|
+
} else {
|
|
72
|
+
const res = drawList(f.canvas, { rows, top, bottom: listBottom, x0: f.l, x1: f.r });
|
|
73
|
+
f.hintRight = scrollCountText(res.above, res.below);
|
|
115
74
|
}
|
|
116
|
-
|
|
117
|
-
return ModalFactory.createModal(
|
|
118
|
-
{
|
|
119
|
-
title: 'Profiles',
|
|
120
|
-
width: boxW,
|
|
121
|
-
margin: boxMargin,
|
|
122
|
-
targetContentRows,
|
|
123
|
-
sections,
|
|
124
|
-
hints: ['[Up/Down] Navigate', '[Enter] Load', '[d] Arm/Delete', '[s] Save current', '[Esc] Close'],
|
|
125
|
-
},
|
|
126
|
-
width,
|
|
127
|
-
);
|
|
75
|
+
return finishModal(f);
|
|
128
76
|
}
|
|
@@ -2,6 +2,7 @@ import type { Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
|
2
2
|
import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
3
|
import type { SearchManager } from '../input/search.ts';
|
|
4
4
|
import { createBottomBarLine, writeBottomBarText } from '@pellux/goodvibes-terminal-shell';
|
|
5
|
+
import { activeTokens } from './theme.ts';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Render the search bar as a single Line[] overlay at the bottom of the viewport.
|
|
@@ -65,13 +66,15 @@ export function renderSearchOverlay(
|
|
|
65
66
|
|
|
66
67
|
// Build the full line text (match count embedded for positional tracking)
|
|
67
68
|
const fullLine = truncatedLeft + matchStr + hints + ' ';
|
|
68
|
-
const
|
|
69
|
-
|
|
69
|
+
const p = activeTokens();
|
|
70
|
+
const line = createBottomBarLine(width, { fg: p.selectedListItemText, bg: p.accent });
|
|
71
|
+
writeBottomBarText(line, 0, width, fitDisplay(truncateDisplay(fullLine, width), width), { fg: p.selectedListItemText, bg: p.accent });
|
|
70
72
|
|
|
71
73
|
// Overwrite match count segment with dim grey styling
|
|
72
74
|
if (matchStr.length > 0) {
|
|
73
75
|
const matchStart = getDisplayWidth(truncatedLeft);
|
|
74
|
-
|
|
76
|
+
// dim kept: de-emphasis on the accent bar, where a faint grey would not read.
|
|
77
|
+
writeBottomBarText(line, matchStart, matchStrW, matchStr, { fg: p.selectedListItemText, bg: p.accent, dim: true });
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
return [line];
|