@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
|
@@ -21,6 +21,11 @@ export class BookmarkModal {
|
|
|
21
21
|
public scrollOffset = 0;
|
|
22
22
|
/** Max visible list rows. */
|
|
23
23
|
public visibleRows = BookmarkModal.DEFAULT_VISIBLE_ROWS;
|
|
24
|
+
/**
|
|
25
|
+
* The always-live search row's query. Narrows the list by key and label;
|
|
26
|
+
* selectedIndex indexes the narrowed list (visibleEntries).
|
|
27
|
+
*/
|
|
28
|
+
public query = '';
|
|
24
29
|
|
|
25
30
|
public constructor(private readonly bookmarkManager: BookmarkManager) {}
|
|
26
31
|
|
|
@@ -29,6 +34,7 @@ export class BookmarkModal {
|
|
|
29
34
|
*/
|
|
30
35
|
open(): void {
|
|
31
36
|
this.entries = this.bookmarkManager.list();
|
|
37
|
+
this.query = '';
|
|
32
38
|
this.selectedIndex = 0;
|
|
33
39
|
this.scrollOffset = 0;
|
|
34
40
|
this.active = true;
|
|
@@ -36,6 +42,21 @@ export class BookmarkModal {
|
|
|
36
42
|
|
|
37
43
|
close(): void {
|
|
38
44
|
this.active = false;
|
|
45
|
+
this.query = '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The entries the search row lets through, in order. */
|
|
49
|
+
get visibleEntries(): BookmarkEntry[] {
|
|
50
|
+
const q = this.query.trim().toLowerCase();
|
|
51
|
+
if (!q) return this.entries;
|
|
52
|
+
return this.entries.filter((entry) => entry.key.toLowerCase().includes(q) || entry.label.toLowerCase().includes(q));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Replace the query; the selection moves to the first match. */
|
|
56
|
+
setQuery(query: string): void {
|
|
57
|
+
this.query = query;
|
|
58
|
+
this.selectedIndex = 0;
|
|
59
|
+
this.scrollOffset = 0;
|
|
39
60
|
}
|
|
40
61
|
|
|
41
62
|
setVisibleRows(rows: number): void {
|
|
@@ -44,19 +65,21 @@ export class BookmarkModal {
|
|
|
44
65
|
}
|
|
45
66
|
|
|
46
67
|
moveUp(): void {
|
|
47
|
-
|
|
48
|
-
|
|
68
|
+
const n = this.visibleEntries.length;
|
|
69
|
+
if (n === 0) return;
|
|
70
|
+
this.selectedIndex = (this.selectedIndex - 1 + n) % n;
|
|
49
71
|
this._clampScroll();
|
|
50
72
|
}
|
|
51
73
|
|
|
52
74
|
moveDown(): void {
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
const n = this.visibleEntries.length;
|
|
76
|
+
if (n === 0) return;
|
|
77
|
+
this.selectedIndex = (this.selectedIndex + 1) % n;
|
|
55
78
|
this._clampScroll();
|
|
56
79
|
}
|
|
57
80
|
|
|
58
81
|
getSelected(): BookmarkEntry | null {
|
|
59
|
-
return this.
|
|
82
|
+
return this.visibleEntries[this.selectedIndex] ?? null;
|
|
60
83
|
}
|
|
61
84
|
|
|
62
85
|
/**
|
|
@@ -69,10 +92,11 @@ export class BookmarkModal {
|
|
|
69
92
|
this.bookmarkManager.toggle(entry.key); // toggle off = remove
|
|
70
93
|
this.entries = this.bookmarkManager.list();
|
|
71
94
|
// Clamp selectedIndex after removal
|
|
72
|
-
|
|
95
|
+
const n = this.visibleEntries.length;
|
|
96
|
+
if (n === 0) {
|
|
73
97
|
this.selectedIndex = 0;
|
|
74
|
-
} else if (this.selectedIndex >=
|
|
75
|
-
this.selectedIndex =
|
|
98
|
+
} else if (this.selectedIndex >= n) {
|
|
99
|
+
this.selectedIndex = n - 1;
|
|
76
100
|
}
|
|
77
101
|
this._clampScroll();
|
|
78
102
|
return entry;
|
|
@@ -109,7 +133,7 @@ export class BookmarkModal {
|
|
|
109
133
|
} else if (this.selectedIndex >= this.scrollOffset + visRows) {
|
|
110
134
|
this.scrollOffset = this.selectedIndex - visRows + 1;
|
|
111
135
|
}
|
|
112
|
-
const maxOffset = Math.max(0, this.
|
|
136
|
+
const maxOffset = Math.max(0, this.visibleEntries.length - visRows);
|
|
113
137
|
this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxOffset));
|
|
114
138
|
}
|
|
115
139
|
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* changes-git.ts, the git reads and writes behind the Changes modal.
|
|
3
|
+
*
|
|
4
|
+
* Every spawn captures stdout and stderr: a child left with inherited stdio
|
|
5
|
+
* would write straight to the TUI's terminal, outside the renderer. Nothing
|
|
6
|
+
* here throws into the render loop; failures come back as text to show.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { isAbsolute, join, relative } from 'node:path';
|
|
10
|
+
import { GitService } from '@pellux/goodvibes-sdk/platform/git';
|
|
11
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
12
|
+
|
|
13
|
+
/** Which diff the modal shows. */
|
|
14
|
+
export type ChangesSource = 'session' | 'working' | 'staged' | 'head';
|
|
15
|
+
|
|
16
|
+
export const CHANGES_SOURCES: readonly ChangesSource[] = ['session', 'working', 'staged', 'head'];
|
|
17
|
+
|
|
18
|
+
export const CHANGES_SOURCE_LABELS: Readonly<Record<ChangesSource, string>> = {
|
|
19
|
+
session: 'this session',
|
|
20
|
+
working: 'not staged',
|
|
21
|
+
staged: 'staged',
|
|
22
|
+
head: 'all changes vs HEAD',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
interface RunResult { readonly out: string; readonly err: string; readonly ok: boolean }
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Pin the a/ and b/ path prefixes: a user's diff.mnemonicPrefix or
|
|
29
|
+
* diff.noprefix setting would otherwise turn paths into w/src/x.ts or
|
|
30
|
+
* src/x.ts, which the file list, hunk staging and the semantic reader all
|
|
31
|
+
* misread.
|
|
32
|
+
*/
|
|
33
|
+
const DIFF_PREFIX_ARGS: readonly string[] = ['--src-prefix=a/', '--dst-prefix=b/'];
|
|
34
|
+
|
|
35
|
+
async function run(args: string[], cwd: string, stdin?: string): Promise<RunResult> {
|
|
36
|
+
try {
|
|
37
|
+
const proc = Bun.spawn(['git', ...args], { cwd, stdout: 'pipe', stderr: 'pipe', stdin: stdin !== undefined ? 'pipe' : 'ignore' });
|
|
38
|
+
if (stdin !== undefined && proc.stdin) {
|
|
39
|
+
proc.stdin.write(stdin);
|
|
40
|
+
await proc.stdin.end();
|
|
41
|
+
}
|
|
42
|
+
const [out, err] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
|
|
43
|
+
const code = await proc.exited;
|
|
44
|
+
return { out, err: err.trim(), ok: code === 0 };
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return { out: '', err: summarizeError(error), ok: false };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isGitRepo(cwd: string): boolean {
|
|
51
|
+
return GitService.isGitRepo(cwd);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface LoadedDiff {
|
|
55
|
+
readonly raw: string;
|
|
56
|
+
/** What the diff is, in words ("this session: 3 files vs HEAD"). */
|
|
57
|
+
readonly label: string;
|
|
58
|
+
readonly error: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Untracked files larger than this show as added with a note instead of every line. */
|
|
62
|
+
export const MAX_UNTRACKED_BYTES = 256 * 1024;
|
|
63
|
+
/** Untracked files with more lines than this show as added with a note instead of every line. */
|
|
64
|
+
export const MAX_UNTRACKED_LINES = 4000;
|
|
65
|
+
/** At most this many untracked files are read into one diff; the label counts the rest. */
|
|
66
|
+
export const MAX_UNTRACKED_FILES = 200;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A new-file chunk with no hunks and a note line in place of the content. The
|
|
70
|
+
* note line starts with "# ", which no git header line does; parseChanges
|
|
71
|
+
* reads it into ChangeFile.note.
|
|
72
|
+
*/
|
|
73
|
+
function noteChunk(path: string, note: string): string {
|
|
74
|
+
return `diff --git a/${path} b/${path}\nnew file mode 100644\n# ${note}\n`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** One untracked file as an all-added diff chunk (paths relative to the repo root). */
|
|
78
|
+
async function untrackedChunk(repoRoot: string, path: string): Promise<string> {
|
|
79
|
+
let size = 0;
|
|
80
|
+
try {
|
|
81
|
+
size = Bun.file(join(repoRoot, path)).size;
|
|
82
|
+
} catch {
|
|
83
|
+
size = 0;
|
|
84
|
+
}
|
|
85
|
+
if (size > MAX_UNTRACKED_BYTES) return noteChunk(path, `large new file (${Math.round(size / 1024)} KB), not shown line by line`);
|
|
86
|
+
// --no-index against /dev/null is git's own new-file patch: binary detection,
|
|
87
|
+
// "new file mode", "--- /dev/null", and a hunk staging can apply as is.
|
|
88
|
+
const result = await run(['diff', '--no-index', '--no-color', ...DIFF_PREFIX_ARGS, '--', '/dev/null', path], repoRoot);
|
|
89
|
+
// --no-index exits 1 when the files differ, which is the expected case here.
|
|
90
|
+
if (!result.out.trim()) return result.err ? noteChunk(path, `could not read: ${result.err.split('\n')[0]}`) : noteChunk(path, 'empty new file');
|
|
91
|
+
const lines = result.out.split('\n').length;
|
|
92
|
+
if (lines > MAX_UNTRACKED_LINES) return noteChunk(path, `large new file (${lines} lines), not shown line by line`);
|
|
93
|
+
return result.out.endsWith('\n') ? result.out : `${result.out}\n`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Untracked, non-ignored files as all-added diff chunks, one per file. With
|
|
98
|
+
* `only` set, just the untracked files among those paths (the session's own
|
|
99
|
+
* new files); otherwise every untracked file in the repository.
|
|
100
|
+
*/
|
|
101
|
+
export async function loadUntrackedDiff(cwd: string, only?: readonly string[]): Promise<{ raw: string; count: number; skipped: number }> {
|
|
102
|
+
if (only && only.length === 0) return { raw: '', count: 0, skipped: 0 };
|
|
103
|
+
const top = await run(['rev-parse', '--show-toplevel'], cwd);
|
|
104
|
+
if (!top.ok) return { raw: '', count: 0, skipped: 0 };
|
|
105
|
+
const repoRoot = top.out.trim() || cwd;
|
|
106
|
+
// A session path outside this repository would fail the whole listing; leave it out.
|
|
107
|
+
const specs = only ? only.filter((path) => !isAbsolute(path) || path.startsWith(`${repoRoot}/`)) : [':/'];
|
|
108
|
+
if (specs.length === 0) return { raw: '', count: 0, skipped: 0 };
|
|
109
|
+
const listed = await run(['ls-files', '--others', '--exclude-standard', '--full-name', '-z', '--', ...specs], cwd);
|
|
110
|
+
if (!listed.ok) return { raw: '', count: 0, skipped: 0 };
|
|
111
|
+
const paths = listed.out.split('\0').filter((path) => path.length > 0).sort();
|
|
112
|
+
const shown = paths.slice(0, MAX_UNTRACKED_FILES);
|
|
113
|
+
const chunks: string[] = [];
|
|
114
|
+
// A few at a time: one git process per file, never hundreds at once.
|
|
115
|
+
for (let i = 0; i < shown.length; i += 8) {
|
|
116
|
+
chunks.push(...await Promise.all(shown.slice(i, i + 8).map((path) => untrackedChunk(repoRoot, path))));
|
|
117
|
+
}
|
|
118
|
+
return { raw: chunks.join(''), count: shown.length, skipped: paths.length - shown.length };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The diff for a source. `sessionFiles` are the files this session edited.
|
|
123
|
+
* Untracked files are part of every source except "staged": they show as
|
|
124
|
+
* added files with their whole content (the session source takes only the
|
|
125
|
+
* untracked files this session wrote).
|
|
126
|
+
*/
|
|
127
|
+
export async function loadDiff(cwd: string, source: ChangesSource, sessionFiles: readonly string[]): Promise<LoadedDiff> {
|
|
128
|
+
let args: string[];
|
|
129
|
+
let label: string;
|
|
130
|
+
let untrackedOnly: readonly string[] | undefined;
|
|
131
|
+
if (source === 'working') {
|
|
132
|
+
args = ['diff', '--no-color', ...DIFF_PREFIX_ARGS];
|
|
133
|
+
label = 'not staged';
|
|
134
|
+
} else if (source === 'staged') {
|
|
135
|
+
args = ['diff', '--cached', '--no-color', ...DIFF_PREFIX_ARGS];
|
|
136
|
+
label = 'staged';
|
|
137
|
+
} else if (source === 'session' && sessionFiles.length > 0) {
|
|
138
|
+
args = ['diff', '--no-color', ...DIFF_PREFIX_ARGS, 'HEAD', '--', ...sessionFiles];
|
|
139
|
+
label = `this session · ${sessionFiles.length} file${sessionFiles.length === 1 ? '' : 's'} vs HEAD`;
|
|
140
|
+
untrackedOnly = sessionFiles;
|
|
141
|
+
} else {
|
|
142
|
+
args = ['diff', '--no-color', ...DIFF_PREFIX_ARGS, 'HEAD'];
|
|
143
|
+
label = source === 'session' ? 'no files tracked this session · all changes vs HEAD' : 'all changes vs HEAD';
|
|
144
|
+
}
|
|
145
|
+
const [result, untracked] = await Promise.all([
|
|
146
|
+
run(args, cwd),
|
|
147
|
+
source === 'staged' ? Promise.resolve({ raw: '', count: 0, skipped: 0 }) : loadUntrackedDiff(cwd, untrackedOnly),
|
|
148
|
+
]);
|
|
149
|
+
if (!result.ok) return { raw: '', label, error: result.err || 'git diff failed' };
|
|
150
|
+
const total = untracked.count + untracked.skipped;
|
|
151
|
+
if (total > 0) {
|
|
152
|
+
label += ` · ${total} untracked`;
|
|
153
|
+
if (untracked.skipped > 0) label += ` (${untracked.skipped} not shown)`;
|
|
154
|
+
}
|
|
155
|
+
const tracked = result.out && !result.out.endsWith('\n') ? `${result.out}\n` : result.out;
|
|
156
|
+
return { raw: tracked + untracked.raw, label, error: null };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface CommitEntry {
|
|
160
|
+
readonly hash: string;
|
|
161
|
+
readonly message: string;
|
|
162
|
+
readonly date: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function loadRecentCommits(cwd: string, count = 10): Promise<CommitEntry[]> {
|
|
166
|
+
try {
|
|
167
|
+
const entries = await new GitService(cwd).log(count);
|
|
168
|
+
return entries.map((entry) => ({ hash: entry.hash, message: entry.message, date: entry.date }));
|
|
169
|
+
} catch {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** A commit's patch (works for root commits too). */
|
|
175
|
+
export async function loadCommitDiff(cwd: string, hash: string): Promise<LoadedDiff> {
|
|
176
|
+
const result = await run(['show', '--no-color', '--format=', '--patch', ...DIFF_PREFIX_ARGS, hash], cwd);
|
|
177
|
+
const label = `commit ${hash.slice(0, 7)}`;
|
|
178
|
+
if (!result.ok) return { raw: '', label, error: result.err || 'git show failed' };
|
|
179
|
+
return { raw: result.out, label, error: null };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface RepoSummary {
|
|
183
|
+
readonly branch: string;
|
|
184
|
+
readonly staged: number;
|
|
185
|
+
readonly unstaged: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function loadRepoSummary(cwd: string): Promise<RepoSummary | null> {
|
|
189
|
+
try {
|
|
190
|
+
const git = new GitService(cwd);
|
|
191
|
+
const [status, branch] = await Promise.all([git.status(), git.branch()]);
|
|
192
|
+
let staged = 0;
|
|
193
|
+
let unstaged = 0;
|
|
194
|
+
for (const f of status.files) {
|
|
195
|
+
if (f.index === '?' && f.working_dir === '?') { unstaged++; continue; }
|
|
196
|
+
if (f.index !== ' ') staged++;
|
|
197
|
+
if (f.working_dir !== ' ') unstaged++;
|
|
198
|
+
}
|
|
199
|
+
return { branch: branch.current || (branch.detached ? 'detached' : 'no branch'), staged, unstaged };
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Stage (or with reverse, unstage) exactly one hunk. */
|
|
206
|
+
export async function applyHunkToIndex(cwd: string, patch: string, reverse: boolean): Promise<string | null> {
|
|
207
|
+
const args = ['apply', '--cached', '--recount', ...(reverse ? ['-R'] : []), '-'];
|
|
208
|
+
const result = await run(args, cwd, patch);
|
|
209
|
+
return result.ok ? null : (result.err || 'git apply failed');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Stage or unstage a whole file. */
|
|
213
|
+
export async function setFileStaged(cwd: string, path: string, staged: boolean): Promise<string | null> {
|
|
214
|
+
const result = staged ? await run(['add', '--', path], cwd) : await run(['reset', '-q', '--', path], cwd);
|
|
215
|
+
return result.ok ? null : (result.err || `git ${staged ? 'add' : 'reset'} failed`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function commitStaged(cwd: string, message: string): Promise<{ hash: string; error: string | null }> {
|
|
219
|
+
try {
|
|
220
|
+
const result = await new GitService(cwd).commit(message);
|
|
221
|
+
return { hash: result.hash, error: null };
|
|
222
|
+
} catch (error) {
|
|
223
|
+
return { hash: '', error: summarizeError(error) };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function initRepo(cwd: string): string | null {
|
|
228
|
+
const result = GitService.initRepo(cwd);
|
|
229
|
+
return result.success ? null : (result.error ?? 'git init failed');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The semantic changes in one file (functions, classes, imports added,
|
|
234
|
+
* removed or changed), comparing `ref` against the file on disk. Null when it
|
|
235
|
+
* cannot be computed (a new file, an unsupported language, no repo).
|
|
236
|
+
*/
|
|
237
|
+
export async function loadSemanticDiff(cwd: string, filePath: string, ref: string): Promise<import('../renderer/semantic-diff.ts').SemanticDiff | null> {
|
|
238
|
+
try {
|
|
239
|
+
const top = await run(['rev-parse', '--show-toplevel'], cwd);
|
|
240
|
+
const repoRoot = top.ok ? top.out.trim() || cwd : cwd;
|
|
241
|
+
const absPath = filePath.startsWith('/') ? filePath : join(repoRoot, filePath);
|
|
242
|
+
const repoRel = filePath.startsWith('/') ? relative(repoRoot, filePath) : filePath;
|
|
243
|
+
const before = await run(['show', `${ref}:${repoRel}`], repoRoot);
|
|
244
|
+
if (!before.ok) return null;
|
|
245
|
+
const after = await Bun.file(absPath).text();
|
|
246
|
+
const { computeSemanticDiff } = await import('../renderer/semantic-diff.ts');
|
|
247
|
+
return await computeSemanticDiff(filePath, before.out, after);
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|