@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
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// DiffReviewPanel, comment-on-hunk review loop.
|
|
3
|
-
//
|
|
4
|
-
// Shows the current session's file changes per-file and hunk-boundaried, lets
|
|
5
|
-
// the user select a hunk, attach a comment, and submit that comment to the
|
|
6
|
-
// session as a steering message carrying structured context (file path, new-file
|
|
7
|
-
// line range, and a short patch excerpt) so the model knows exactly what the
|
|
8
|
-
// comment targets.
|
|
9
|
-
//
|
|
10
|
-
// Data source (labelled honestly in the panel): `git diff <base> -- <files>`,
|
|
11
|
-
// where <files> is the set the SDK SessionChangeTracker recorded this session.
|
|
12
|
-
// That is CURRENT working-tree content vs the base ref, not a turn-start
|
|
13
|
-
// snapshot. The panel never presents a stale diff as current.
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
import type { Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
17
|
-
import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types';
|
|
18
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
19
|
-
import { GitService } from '@pellux/goodvibes-sdk/platform/git';
|
|
20
|
-
import { BasePanel } from './base-panel.ts';
|
|
21
|
-
import { DIFF_TONES, UI_TONES } from '../renderer/ui-primitives.ts';
|
|
22
|
-
import {
|
|
23
|
-
buildEmptyState,
|
|
24
|
-
buildPanelWorkspace,
|
|
25
|
-
buildStyledPanelLine,
|
|
26
|
-
resolveScrollablePanelSection,
|
|
27
|
-
DEFAULT_PANEL_PALETTE,
|
|
28
|
-
extendPalette,
|
|
29
|
-
} from './polish.ts';
|
|
30
|
-
import { isPanelSearchBackspace, isPanelSearchCancel, isPanelSearchCommit, isPanelSearchPrintable } from './search-focus.ts';
|
|
31
|
-
import { appendSteerText } from './fleet-tabs.ts';
|
|
32
|
-
import {
|
|
33
|
-
parseReviewDiff,
|
|
34
|
-
flattenHunks,
|
|
35
|
-
hunkLineRange,
|
|
36
|
-
buildSteerMessage,
|
|
37
|
-
type ReviewFile,
|
|
38
|
-
type ReviewHunk,
|
|
39
|
-
type HunkComment,
|
|
40
|
-
} from './diff-review-model.ts';
|
|
41
|
-
|
|
42
|
-
const COLOR = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
43
|
-
addition: DIFF_TONES.add,
|
|
44
|
-
deletion: DIFF_TONES.del,
|
|
45
|
-
hunk: DIFF_TONES.hunk,
|
|
46
|
-
} as const);
|
|
47
|
-
|
|
48
|
-
/** A comment attached to a hunk plus whether it has already been steered to the session. */
|
|
49
|
-
interface CommentState {
|
|
50
|
-
comment: string;
|
|
51
|
-
submitted: boolean;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function basename(p: string): string {
|
|
55
|
-
const parts = p.replace(/\\/g, '/').split('/');
|
|
56
|
-
return parts[parts.length - 1] ?? p;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export class DiffReviewPanel extends BasePanel {
|
|
60
|
-
public override isTransient = true;
|
|
61
|
-
|
|
62
|
-
private readonly workingDirectory: string;
|
|
63
|
-
private readonly getChangedFiles: (() => string[]) | undefined;
|
|
64
|
-
private files: ReviewFile[] = [];
|
|
65
|
-
private hunks: ReviewHunk[] = [];
|
|
66
|
-
private cursor = 0;
|
|
67
|
-
private scrollOffset = 0;
|
|
68
|
-
/** Comments keyed by `${fileIndex}:${hunkIndex}`. */
|
|
69
|
-
private comments = new Map<string, CommentState>();
|
|
70
|
-
private composing = false;
|
|
71
|
-
private draft = '';
|
|
72
|
-
private submitSteer: ((text: string) => void) | null = null;
|
|
73
|
-
/** Injected by the /review command: reverse-apply (reject) the given hunk via checkpoints.revertHunk. */
|
|
74
|
-
private revertHunk: ((hunk: ReviewHunk) => void) | null = null;
|
|
75
|
-
private sourceLabel = 'working tree vs HEAD (files edited this session)';
|
|
76
|
-
private status: string | null = null;
|
|
77
|
-
/** True once a load has completed (so the empty state can distinguish "no changes" from "not loaded"). */
|
|
78
|
-
private loaded = false;
|
|
79
|
-
|
|
80
|
-
constructor(
|
|
81
|
-
workingDirectory: string,
|
|
82
|
-
private readonly requestRender: () => void = () => {},
|
|
83
|
-
getChangedFiles?: () => string[],
|
|
84
|
-
) {
|
|
85
|
-
super('review', 'Review', 'R', 'development');
|
|
86
|
-
this.workingDirectory = workingDirectory;
|
|
87
|
-
this.getChangedFiles = getChangedFiles;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// -------------------------------------------------------------------------
|
|
91
|
-
// Public API, driven by the /review command
|
|
92
|
-
// -------------------------------------------------------------------------
|
|
93
|
-
|
|
94
|
-
/** Wire the steering submit path (the /review command passes ctx.submitInput). */
|
|
95
|
-
setSubmit(fn: (text: string) => void): void {
|
|
96
|
-
this.submitSteer = fn;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** Wire the hunk-revert path (the /review command drives checkpoints.revertHunk). */
|
|
100
|
-
setRevertHandler(fn: (hunk: ReviewHunk) => void): void {
|
|
101
|
-
this.revertHunk = fn;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Surface a status line (the revert flow reports conflicts/outcomes here). */
|
|
105
|
-
note(message: string): void {
|
|
106
|
-
this.status = message;
|
|
107
|
-
this.markDirty();
|
|
108
|
-
this.requestRender();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Re-load the session diff, used after a revert mutates the working tree. */
|
|
112
|
-
async refresh(): Promise<void> {
|
|
113
|
-
await this.loadSessionReview();
|
|
114
|
-
this.requestRender();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private keyFor(hunk: ReviewHunk): string {
|
|
118
|
-
return `${hunk.fileIndex}:${hunk.hunkIndex}`;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private currentHunk(): ReviewHunk | null {
|
|
122
|
-
return this.hunks[this.cursor] ?? null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Adopt a parsed review set with an explicit provenance label. */
|
|
126
|
-
loadReview(files: ReviewFile[], sourceLabel: string): void {
|
|
127
|
-
this.files = files;
|
|
128
|
-
this.hunks = flattenHunks(files);
|
|
129
|
-
this.cursor = 0;
|
|
130
|
-
this.scrollOffset = 0;
|
|
131
|
-
this.comments.clear();
|
|
132
|
-
this.composing = false;
|
|
133
|
-
this.draft = '';
|
|
134
|
-
this.sourceLabel = sourceLabel;
|
|
135
|
-
this.loaded = true;
|
|
136
|
-
this.markDirty();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Load the session's changed files as a git diff against `ref` (default HEAD)
|
|
141
|
-
* and parse it into per-file hunks. Honest source: current working-tree
|
|
142
|
-
* content vs the base ref, restricted to files this session edited.
|
|
143
|
-
*/
|
|
144
|
-
async loadSessionReview(ref = 'HEAD'): Promise<void> {
|
|
145
|
-
if (!GitService.isGitRepo(this.workingDirectory)) {
|
|
146
|
-
this.status = 'Not a git repository: no working-tree diff to review.';
|
|
147
|
-
this.loaded = true;
|
|
148
|
-
this.markDirty();
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const files = (this.getChangedFiles?.() ?? []).filter(Boolean);
|
|
152
|
-
const args = ['diff', ref, '--', ...files];
|
|
153
|
-
const wholeRepo = files.length === 0;
|
|
154
|
-
const proc = Bun.spawn(['git', ...(wholeRepo ? ['diff', ref] : args)], { stdout: 'pipe', stderr: 'pipe', cwd: this.workingDirectory });
|
|
155
|
-
const [raw, errText] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
|
|
156
|
-
const exitCode = await proc.exited;
|
|
157
|
-
if (exitCode !== 0) {
|
|
158
|
-
this.status = `git diff failed: ${errText.trim() || 'unknown error'}`;
|
|
159
|
-
this.loaded = true;
|
|
160
|
-
this.markDirty();
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const label = wholeRepo
|
|
164
|
-
? `working tree vs ${ref} (no session-tracked files; showing whole working tree)`
|
|
165
|
-
: `working tree vs ${ref} (${files.length} file${files.length === 1 ? '' : 's'} edited this session)`;
|
|
166
|
-
this.loadReview(parseReviewDiff(raw), label);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// -------------------------------------------------------------------------
|
|
170
|
-
// Input
|
|
171
|
-
// -------------------------------------------------------------------------
|
|
172
|
-
|
|
173
|
-
handleInput(key: string): boolean {
|
|
174
|
-
if (this.composing) return this.handleComposerInput(key);
|
|
175
|
-
switch (key) {
|
|
176
|
-
case 'up': case 'k': this.moveCursor(-1); return true;
|
|
177
|
-
case 'down': case 'j': this.moveCursor(1); return true;
|
|
178
|
-
case 'tab': this.jumpFile(1); return true;
|
|
179
|
-
case '\x1b[Z': case 'shift-tab': case 'backtab': this.jumpFile(-1); return true;
|
|
180
|
-
case 'pageup': this.moveCursor(-5); return true;
|
|
181
|
-
case 'pagedown': this.moveCursor(5); return true;
|
|
182
|
-
case 'c': this.openComposer(); return true;
|
|
183
|
-
case 'x': this.clearComment(); return true;
|
|
184
|
-
case 'a': this.submitAll(); return true;
|
|
185
|
-
case 'r': this.rejectCurrent(); return true;
|
|
186
|
-
case 'enter': case 'return': this.submitCurrent(); return true;
|
|
187
|
-
default: return false;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
private handleComposerInput(key: string): boolean {
|
|
192
|
-
if (isPanelSearchCancel(key)) {
|
|
193
|
-
this.composing = false;
|
|
194
|
-
this.draft = '';
|
|
195
|
-
this.status = 'Comment cancelled.';
|
|
196
|
-
this.markDirty();
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
if (isPanelSearchCommit(key)) {
|
|
200
|
-
const hunk = this.currentHunk();
|
|
201
|
-
const text = this.draft.trim();
|
|
202
|
-
if (hunk && text.length > 0) {
|
|
203
|
-
this.comments.set(this.keyFor(hunk), { comment: text, submitted: false });
|
|
204
|
-
this.status = 'Comment attached. Press Enter to send it, or a to send all.';
|
|
205
|
-
} else {
|
|
206
|
-
this.status = 'Empty comment discarded.';
|
|
207
|
-
}
|
|
208
|
-
this.composing = false;
|
|
209
|
-
this.draft = '';
|
|
210
|
-
this.markDirty();
|
|
211
|
-
return true;
|
|
212
|
-
}
|
|
213
|
-
if (isPanelSearchBackspace(key)) {
|
|
214
|
-
this.draft = this.draft.slice(0, -1);
|
|
215
|
-
this.markDirty();
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
if (key.length === 1 && (isPanelSearchPrintable(key) || key === '\r' || key === '\n')) {
|
|
219
|
-
this.draft = appendSteerText(this.draft, key);
|
|
220
|
-
this.markDirty();
|
|
221
|
-
return true;
|
|
222
|
-
}
|
|
223
|
-
return true; // swallow other keys while composing
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private moveCursor(delta: number): void {
|
|
227
|
-
if (this.hunks.length === 0) return;
|
|
228
|
-
this.cursor = Math.max(0, Math.min(this.hunks.length - 1, this.cursor + delta));
|
|
229
|
-
this.markDirty();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private jumpFile(direction: number): void {
|
|
233
|
-
const cur = this.currentHunk();
|
|
234
|
-
if (!cur) return;
|
|
235
|
-
const targetFile = cur.fileIndex + direction;
|
|
236
|
-
const idx = this.hunks.findIndex((h) => h.fileIndex === targetFile);
|
|
237
|
-
if (idx >= 0) {
|
|
238
|
-
this.cursor = idx;
|
|
239
|
-
this.markDirty();
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private openComposer(): void {
|
|
244
|
-
const hunk = this.currentHunk();
|
|
245
|
-
if (!hunk) return;
|
|
246
|
-
this.composing = true;
|
|
247
|
-
this.draft = this.comments.get(this.keyFor(hunk))?.comment ?? '';
|
|
248
|
-
this.status = null;
|
|
249
|
-
this.markDirty();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private clearComment(): void {
|
|
253
|
-
const hunk = this.currentHunk();
|
|
254
|
-
if (!hunk) return;
|
|
255
|
-
if (this.comments.delete(this.keyFor(hunk))) {
|
|
256
|
-
this.status = 'Comment removed.';
|
|
257
|
-
this.markDirty();
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
private steer(items: HunkComment[]): boolean {
|
|
262
|
-
if (!this.submitSteer) {
|
|
263
|
-
this.status = 'No session is wired to receive steering here.';
|
|
264
|
-
this.markDirty();
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
if (items.length === 0) {
|
|
268
|
-
this.status = 'No comment to send. Press c to write one.';
|
|
269
|
-
this.markDirty();
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
this.submitSteer(buildSteerMessage(items, this.sourceLabel));
|
|
273
|
-
for (const { hunk } of items) {
|
|
274
|
-
const state = this.comments.get(this.keyFor(hunk));
|
|
275
|
-
if (state) state.submitted = true;
|
|
276
|
-
}
|
|
277
|
-
this.status = items.length === 1
|
|
278
|
-
? 'Comment sent to the session as a steering message.'
|
|
279
|
-
: `${items.length} comments sent to the session as one steering message.`;
|
|
280
|
-
this.markDirty();
|
|
281
|
-
this.requestRender();
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private submitCurrent(): void {
|
|
286
|
-
const hunk = this.currentHunk();
|
|
287
|
-
if (!hunk) return;
|
|
288
|
-
const state = this.comments.get(this.keyFor(hunk));
|
|
289
|
-
if (!state || state.comment.length === 0) {
|
|
290
|
-
this.status = 'This hunk has no comment yet. Press c to write one.';
|
|
291
|
-
this.markDirty();
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
this.steer([{ hunk, comment: state.comment }]);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/** Reject the selected hunk: hand it to the revert flow (preview → confirm → revertHunk). */
|
|
298
|
-
private rejectCurrent(): void {
|
|
299
|
-
const hunk = this.currentHunk();
|
|
300
|
-
if (!hunk) return;
|
|
301
|
-
if (!this.revertHunk) {
|
|
302
|
-
this.note('Hunk revert is not wired in this session.');
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
this.revertHunk(hunk);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
private submitAll(): void {
|
|
309
|
-
const items: HunkComment[] = [];
|
|
310
|
-
for (const hunk of this.hunks) {
|
|
311
|
-
const state = this.comments.get(this.keyFor(hunk));
|
|
312
|
-
if (state && !state.submitted && state.comment.length > 0) items.push({ hunk, comment: state.comment });
|
|
313
|
-
}
|
|
314
|
-
this.steer(items);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// -------------------------------------------------------------------------
|
|
318
|
-
// Render
|
|
319
|
-
// -------------------------------------------------------------------------
|
|
320
|
-
|
|
321
|
-
private hunkRow(hunk: ReviewHunk, isCursor: boolean, width: number): Line {
|
|
322
|
-
const state = this.comments.get(this.keyFor(hunk));
|
|
323
|
-
const marker = !state ? ' ' : state.submitted ? '[✓]' : '[c]';
|
|
324
|
-
const range = hunkLineRange(hunk);
|
|
325
|
-
const label = `${marker} ${basename(hunk.filePath)}:${range.start}-${range.end} +${hunk.added} -${hunk.removed}`;
|
|
326
|
-
return buildStyledPanelLine(width, [{
|
|
327
|
-
text: truncateDisplay(`${isCursor ? '▸ ' : ' '}${label}`, width),
|
|
328
|
-
fg: state?.submitted ? COLOR.addition : isCursor ? COLOR.value : COLOR.dim,
|
|
329
|
-
bg: isCursor ? UI_TONES.bg.selected : undefined,
|
|
330
|
-
bold: isCursor,
|
|
331
|
-
}]);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
private detailLines(hunk: ReviewHunk, width: number): Line[] {
|
|
335
|
-
const lines: Line[] = [];
|
|
336
|
-
lines.push(buildStyledPanelLine(width, [{ text: truncateDisplay(` ${hunk.filePath}`, width), fg: COLOR.value, bold: true }]));
|
|
337
|
-
lines.push(buildStyledPanelLine(width, [{ text: truncateDisplay(` ${hunk.header}`, width), fg: COLOR.hunk }]));
|
|
338
|
-
for (const raw of hunk.bodyLines.slice(0, 8)) {
|
|
339
|
-
const fg = raw.startsWith('+') ? COLOR.addition : raw.startsWith('-') ? COLOR.deletion : COLOR.dim;
|
|
340
|
-
lines.push(buildStyledPanelLine(width, [{ text: truncateDisplay(` ${raw}`, width), fg }]));
|
|
341
|
-
}
|
|
342
|
-
const state = this.comments.get(this.keyFor(hunk));
|
|
343
|
-
if (this.composing) {
|
|
344
|
-
lines.push(buildStyledPanelLine(width, [{ text: truncateDisplay(` comment> ${this.draft}_`, width), fg: COLOR.value, bold: true }]));
|
|
345
|
-
} else if (state) {
|
|
346
|
-
const tag = state.submitted ? '(sent)' : '(unsent)';
|
|
347
|
-
lines.push(buildStyledPanelLine(width, [{ text: truncateDisplay(` comment ${tag}: ${state.comment}`, width), fg: state.submitted ? COLOR.addition : COLOR.value }]));
|
|
348
|
-
}
|
|
349
|
-
return lines;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
private footer(width: number): Line {
|
|
353
|
-
const hints = this.composing
|
|
354
|
-
? ' Enter attach Esc cancel'
|
|
355
|
-
: ' ↑/↓ hunk Tab file c comment Enter send a send all r revert x clear';
|
|
356
|
-
const text = this.status ? `${hints} • ${this.status}` : hints;
|
|
357
|
-
return buildStyledPanelLine(width, [{ text: truncateDisplay(text, width), fg: COLOR.dim, bg: COLOR.sectionBg }], { fillBg: COLOR.sectionBg });
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
render(width: number, height: number): Line[] {
|
|
361
|
-
return this.trackedRender(() => {
|
|
362
|
-
if (height <= 0 || width <= 0) return [];
|
|
363
|
-
if (this.hunks.length === 0) {
|
|
364
|
-
const detail = this.loaded
|
|
365
|
-
? (this.status ?? 'No changes to review. Files edited this session appear here as hunks.')
|
|
366
|
-
: 'Loading session changes…';
|
|
367
|
-
return buildPanelWorkspace(width, height, {
|
|
368
|
-
title: 'Review',
|
|
369
|
-
palette: COLOR,
|
|
370
|
-
sections: [{ title: 'Review', lines: buildEmptyState(width, ' Nothing to review yet.', detail, [{ command: '/review', summary: 'load this session\'s file changes as commentable hunks' }], COLOR) }],
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const hunk = this.currentHunk();
|
|
375
|
-
const listLines = this.hunks.map((h, i) => this.hunkRow(h, i === this.cursor, width));
|
|
376
|
-
const section = resolveScrollablePanelSection(width, height, {
|
|
377
|
-
palette: COLOR,
|
|
378
|
-
footerLines: [this.footer(width)],
|
|
379
|
-
beforeSections: [{ title: `Selected hunk (${this.sourceLabel})`, lines: hunk ? this.detailLines(hunk, width) : [] }],
|
|
380
|
-
section: { title: `Hunks (${this.cursor + 1}/${this.hunks.length})`, scrollableLines: listLines, scrollOffset: this.scrollOffset, selectedIndex: this.cursor, minRows: 1 },
|
|
381
|
-
});
|
|
382
|
-
this.scrollOffset = section.scrollOffset;
|
|
383
|
-
|
|
384
|
-
return buildPanelWorkspace(width, height, {
|
|
385
|
-
title: 'Review',
|
|
386
|
-
palette: COLOR,
|
|
387
|
-
footerLines: [this.footer(width)],
|
|
388
|
-
sections: [
|
|
389
|
-
{ title: `Selected hunk (${this.sourceLabel})`, lines: hunk ? this.detailLines(hunk, width) : [createEmptyLine(width)] },
|
|
390
|
-
{ title: section.section.title, lines: section.section.lines },
|
|
391
|
-
],
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import type { Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
|
-
import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types';
|
|
3
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
|
-
import {
|
|
5
|
-
buildKeyboardHints,
|
|
6
|
-
buildPanelWorkspace,
|
|
7
|
-
resolveScrollablePanelSection,
|
|
8
|
-
} from './polish.ts';
|
|
9
|
-
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
// ExpandableListPanel<T>
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* A list panel with a built-in master → detail drill-down.
|
|
16
|
-
*
|
|
17
|
-
* Encapsulates the collapse/expand + detail-view pattern that was previously
|
|
18
|
-
* hand-rolled in git, tool-inspector, agent-inspector, approval, and wrfc
|
|
19
|
-
* panels: a scrollable list where pressing Enter/→ on a row opens a scrollable
|
|
20
|
-
* detail view for that item, and Esc/← returns to the list.
|
|
21
|
-
*
|
|
22
|
-
* Subclasses implement (in addition to the `ScrollableListPanel` contract):
|
|
23
|
-
* - `getDetailLines(item, width)`, the detail body for the expanded item.
|
|
24
|
-
*
|
|
25
|
-
* Optionally override:
|
|
26
|
-
* - `getDetailTitle(item)`, detail-view title (defaults to `this.name`).
|
|
27
|
-
* - `getListHints()` / `getDetailHints()`, footer keyboard hints per mode.
|
|
28
|
-
* - `renderListView(width, height)`, the list-mode body (defaults to
|
|
29
|
-
* `renderList` with the list hints footer).
|
|
30
|
-
*
|
|
31
|
-
* Do NOT override `render()`; it dispatches between list and detail modes.
|
|
32
|
-
*/
|
|
33
|
-
export abstract class ExpandableListPanel<T> extends ScrollableListPanel<T> {
|
|
34
|
-
/** The item currently expanded into the detail view, or null in list mode. */
|
|
35
|
-
protected expandedItem: T | null = null;
|
|
36
|
-
/** First visible row of the detail view. */
|
|
37
|
-
protected detailScroll = 0;
|
|
38
|
-
|
|
39
|
-
/** Render the detail body lines for an expanded item. */
|
|
40
|
-
protected abstract getDetailLines(item: T, width: number): readonly Line[];
|
|
41
|
-
|
|
42
|
-
/** Title shown above the detail view. */
|
|
43
|
-
protected getDetailTitle(_item: T): string {
|
|
44
|
-
return this.name;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Keyboard hints shown in list mode. */
|
|
48
|
-
protected getListHints(): ReadonlyArray<{ keys: string; label: string }> {
|
|
49
|
-
return [
|
|
50
|
-
{ keys: '↑/↓', label: 'move' },
|
|
51
|
-
{ keys: 'Enter', label: 'open' },
|
|
52
|
-
];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Keyboard hints shown in detail mode. */
|
|
56
|
-
protected getDetailHints(): ReadonlyArray<{ keys: string; label: string }> {
|
|
57
|
-
return [
|
|
58
|
-
{ keys: '↑/↓', label: 'scroll' },
|
|
59
|
-
{ keys: 'Esc', label: 'back' },
|
|
60
|
-
];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
protected isExpanded(): boolean {
|
|
64
|
-
return this.expandedItem !== null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Expand the currently selected item into the detail view. */
|
|
68
|
-
protected expandSelected(): void {
|
|
69
|
-
const items = this.getItems();
|
|
70
|
-
const item = items[this.selectedIndex];
|
|
71
|
-
if (item === undefined) return;
|
|
72
|
-
this.expandedItem = item;
|
|
73
|
-
this.detailScroll = 0;
|
|
74
|
-
this.needsRender = true;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Collapse the detail view back to the list. */
|
|
78
|
-
protected collapse(): void {
|
|
79
|
-
if (this.expandedItem === null) return;
|
|
80
|
-
this.expandedItem = null;
|
|
81
|
-
this.detailScroll = 0;
|
|
82
|
-
this.needsRender = true;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// -------------------------------------------------------------------------
|
|
86
|
-
// Input
|
|
87
|
-
// -------------------------------------------------------------------------
|
|
88
|
-
|
|
89
|
-
handleInput(key: string): boolean {
|
|
90
|
-
if (this.expandedItem !== null) {
|
|
91
|
-
// Detail mode: scroll the detail body; Esc/← collapses.
|
|
92
|
-
if (this.lastError !== null) this.clearError();
|
|
93
|
-
switch (key) {
|
|
94
|
-
case 'escape':
|
|
95
|
-
case 'left':
|
|
96
|
-
case 'h':
|
|
97
|
-
this.collapse();
|
|
98
|
-
return true;
|
|
99
|
-
case 'up':
|
|
100
|
-
case 'k':
|
|
101
|
-
this.detailScroll = Math.max(0, this.detailScroll - 1);
|
|
102
|
-
this.needsRender = true;
|
|
103
|
-
return true;
|
|
104
|
-
case 'down':
|
|
105
|
-
case 'j':
|
|
106
|
-
this.detailScroll += 1;
|
|
107
|
-
this.needsRender = true;
|
|
108
|
-
return true;
|
|
109
|
-
case 'pageup':
|
|
110
|
-
this.detailScroll = Math.max(0, this.detailScroll - this.getPageSize());
|
|
111
|
-
this.needsRender = true;
|
|
112
|
-
return true;
|
|
113
|
-
case 'pagedown':
|
|
114
|
-
this.detailScroll += this.getPageSize();
|
|
115
|
-
this.needsRender = true;
|
|
116
|
-
return true;
|
|
117
|
-
case 'home':
|
|
118
|
-
case 'g':
|
|
119
|
-
this.detailScroll = 0;
|
|
120
|
-
this.needsRender = true;
|
|
121
|
-
return true;
|
|
122
|
-
default:
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// List mode: Enter/→ expands; everything else is list navigation.
|
|
128
|
-
if (key === 'enter' || key === 'return' || key === 'right' || key === 'l') {
|
|
129
|
-
if (this.getItems().length === 0) return false;
|
|
130
|
-
this.expandSelected();
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
return super.handleInput(key);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
handleScroll(deltaRows: number): boolean {
|
|
137
|
-
if (this.expandedItem !== null) {
|
|
138
|
-
const rows = Math.trunc(deltaRows);
|
|
139
|
-
if (rows === 0) return false;
|
|
140
|
-
this.detailScroll = Math.max(0, this.detailScroll + rows);
|
|
141
|
-
this.needsRender = true;
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
return super.handleScroll(deltaRows);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// -------------------------------------------------------------------------
|
|
148
|
-
// Render
|
|
149
|
-
// -------------------------------------------------------------------------
|
|
150
|
-
|
|
151
|
-
render(width: number, height: number): Line[] {
|
|
152
|
-
if (this.expandedItem !== null) {
|
|
153
|
-
return this.renderDetailView(width, height, this.expandedItem);
|
|
154
|
-
}
|
|
155
|
-
return this.renderListView(width, height);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** List-mode body. Override for a custom header; defaults to renderList. */
|
|
159
|
-
protected renderListView(width: number, height: number): Line[] {
|
|
160
|
-
return this.renderList(width, height, { hints: this.getListHints() });
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** Detail-mode body, scrollable detail lines + standardized chrome. */
|
|
164
|
-
protected renderDetailView(width: number, height: number, item: T): Line[] {
|
|
165
|
-
this.needsRender = false;
|
|
166
|
-
const palette = this.getPalette();
|
|
167
|
-
const detailLines = [...this.getDetailLines(item, width)];
|
|
168
|
-
const footer = buildKeyboardHints(width, this.getDetailHints(), palette);
|
|
169
|
-
|
|
170
|
-
const resolved = resolveScrollablePanelSection(width, height, {
|
|
171
|
-
palette,
|
|
172
|
-
afterSections: [{ lines: [footer] }],
|
|
173
|
-
section: {
|
|
174
|
-
scrollableLines: detailLines,
|
|
175
|
-
scrollOffset: this.detailScroll,
|
|
176
|
-
appendWindowSummary: detailLines.length > 5 ? { dimColor: palette.dim } : undefined,
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
this.detailScroll = resolved.scrollOffset;
|
|
180
|
-
|
|
181
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
182
|
-
title: this.getDetailTitle(item),
|
|
183
|
-
sections: [resolved.section, { lines: [footer] }],
|
|
184
|
-
palette,
|
|
185
|
-
});
|
|
186
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
187
|
-
return lines.slice(0, height);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// fleet-deep-link.ts, item 4.
|
|
3
|
-
//
|
|
4
|
-
// PanelManager.open('fleet', pane, target) hands FleetPanel a
|
|
5
|
-
// PanelDeepLinkTarget (a process id + optional ProcessKind) so a cross-panel
|
|
6
|
-
// jump (the work-plan modal's 'i'/'w' agent/WRFC-chain jumps, the docs
|
|
7
|
-
// modal's tools-tab Enter) can select + reveal a SPECIFIC node instead of
|
|
8
|
-
// just opening Fleet generically. Extracted from fleet-panel.ts (already at
|
|
9
|
-
// the 800-line architecture cap) so the panel stays a thin caller.
|
|
10
|
-
//
|
|
11
|
-
// `kind`, when given, must also match: ProcessNode ids are unique per the
|
|
12
|
-
// SDK's own contract, so this is a cheap extra honesty check, not the
|
|
13
|
-
// primary lookup key. Reveal is "free" once selectedIndex is set,
|
|
14
|
-
// ScrollableListPanel's render-time scroll-window resolution keeps the
|
|
15
|
-
// selected row visible, so FleetPanel.receiveDeepLink does not need its own
|
|
16
|
-
// scroll math.
|
|
17
|
-
//
|
|
18
|
-
// A target that isn't found (pruned, killed, never existed, or a domain,
|
|
19
|
-
// like the docs Tools tab's static tool definitions, that has no
|
|
20
|
-
// corresponding fleet node at all) resolves to -1; the caller degrades
|
|
21
|
-
// honestly (FleetPanel.setError('node no longer present.')) rather than
|
|
22
|
-
// silently landing on an unrelated row.
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
import type { PanelDeepLinkTarget } from './types.ts';
|
|
26
|
-
import type { FleetTreeRow } from './fleet-read-model.ts';
|
|
27
|
-
|
|
28
|
-
/** The row index matching `target`'s id (+ kind, if given), or -1. Pure, no I/O, no mutation. */
|
|
29
|
-
export function resolveFleetDeepLinkIndex(rows: readonly FleetTreeRow[], target: PanelDeepLinkTarget): number {
|
|
30
|
-
return rows.findIndex((row) => row.node.id === target.id && (!target.kind || row.node.kind === target.kind));
|
|
31
|
-
}
|