@pellux/goodvibes-tui 1.0.0 → 1.7.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 +130 -127
- package/README.md +32 -14
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/cli/ensure-goodvibes-gitignore.ts +32 -0
- package/src/cli/entrypoint.ts +2 -0
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/conversation-rendering.ts +19 -0
- package/src/core/conversation.ts +58 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +104 -2
- package/src/core/stream-stall-watchdog.ts +41 -17
- package/src/core/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +126 -8
- package/src/input/command-registry.ts +54 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/diff-runtime.ts +61 -30
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +12 -0
- package/src/input/commands/session-workflow.ts +19 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/share-runtime.ts +9 -2
- package/src/input/commands/shell-core.ts +15 -7
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-content-actions.ts +22 -8
- package/src/input/handler-feed-routes.ts +107 -7
- package/src/input/handler-feed.ts +49 -13
- package/src/input/handler-interactions.ts +3 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +3 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +10 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +41 -44
- package/src/panels/agent-inspector-shared.ts +35 -11
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +16 -62
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +9 -43
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +63 -14
- package/src/panels/diff-panel.ts +123 -60
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/git-panel.ts +9 -9
- package/src/panels/index.ts +7 -31
- package/src/panels/local-auth-panel.ts +10 -0
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +151 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/scrollable-list-panel.ts +9 -0
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/token-budget-panel.ts +5 -3
- package/src/panels/types.ts +26 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/turn-injection.ts +114 -0
- package/src/renderer/ui-factory.ts +91 -7
- package/src/runtime/bootstrap-command-context.ts +24 -1
- package/src/runtime/bootstrap-command-parts.ts +36 -7
- package/src/runtime/bootstrap-core.ts +19 -4
- package/src/runtime/bootstrap-hook-bridge.ts +5 -0
- package/src/runtime/bootstrap-shell.ts +41 -17
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -233
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -609
- package/src/panels/docs-panel.ts +0 -375
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -664
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -506
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -557
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -721
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -306
- package/src/panels/provider-health-views.ts +0 -560
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -487
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -530
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -465
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -656
- package/src/renderer/qr-renderer.ts +0 -120
package/src/panels/diff-panel.ts
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
import type { Line } from '../types/grid.ts';
|
|
6
6
|
import { createStyledCell, createEmptyLine } from '../types/grid.ts';
|
|
7
7
|
import { truncateDisplay, getDisplayWidth } from '../utils/terminal-width.ts';
|
|
8
|
+
import { GitService } from '@pellux/goodvibes-sdk/platform/git';
|
|
8
9
|
import { BasePanel } from './base-panel.ts';
|
|
9
10
|
import { UI_TONES, DIFF_TONES } from '../renderer/ui-primitives.ts';
|
|
10
|
-
import { FilePreviewPanel } from './file-preview-panel.ts';
|
|
11
|
-
import type { PanelIntegrationContext } from './types.ts';
|
|
12
11
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
12
|
+
import { PanelConfirmOverlay } from './panel-confirm-overlay.ts';
|
|
13
13
|
import {
|
|
14
14
|
buildBodyText,
|
|
15
15
|
buildEmptyState,
|
|
@@ -166,20 +166,49 @@ function parseDiff(raw: string): ParsedLine[] {
|
|
|
166
166
|
// Split a full `git diff` output into per-file entries
|
|
167
167
|
// ---------------------------------------------------------------------------
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Synthetic placeholder entries ('(error)', '(no changes)', '(no staged
|
|
171
|
+
* changes)', '(not a git repo)') are not real diffs — parenthesized so they
|
|
172
|
+
* can be styled distinctly in the tab/status bar instead of reading as a fake
|
|
173
|
+
* diff of a real file.
|
|
174
|
+
*/
|
|
175
|
+
function isPlaceholderPath(filePath: string): boolean {
|
|
176
|
+
return filePath.startsWith('(') && filePath.endsWith(')');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Extract the file path from one `diff --git`-delimited chunk. Tries, in
|
|
181
|
+
* order: the standard two-sided header (`diff --git a/x b/y`, bare or
|
|
182
|
+
* quoted), the combined/merge-conflict header (`diff --cc <path>` /
|
|
183
|
+
* `diff --combined <path>`, which carries no a/b prefixes), then falls back
|
|
184
|
+
* to the `+++`/`---` file lines (present across every variant, uniformly) —
|
|
185
|
+
* skipping a `/dev/null` side (deleted/new file). 'unknown' is reserved for
|
|
186
|
+
* genuinely unparseable input.
|
|
187
|
+
*/
|
|
188
|
+
function extractDiffFilePath(trimmed: string): string {
|
|
189
|
+
const quotedGit = trimmed.match(/^diff --git "a\/(.+)" "b\/(.+)"$/m);
|
|
190
|
+
if (quotedGit) return quotedGit[2]!;
|
|
191
|
+
const plainGit = trimmed.match(/^diff --git a\/.+? b\/(.+)$/m);
|
|
192
|
+
if (plainGit) return plainGit[1]!;
|
|
193
|
+
const combined = trimmed.match(/^diff --(?:cc|combined) "?([^"\n]+?)"?$/m);
|
|
194
|
+
if (combined) return combined[1]!;
|
|
195
|
+
const plus = trimmed.match(/^\+\+\+ (?:b\/)?"?([^"\n]+?)"?\s*$/m);
|
|
196
|
+
if (plus && plus[1] !== '/dev/null') return plus[1]!;
|
|
197
|
+
const minus = trimmed.match(/^--- (?:a\/)?"?([^"\n]+?)"?\s*$/m);
|
|
198
|
+
if (minus && minus[1] !== '/dev/null') return minus[1]!;
|
|
199
|
+
return 'unknown';
|
|
200
|
+
}
|
|
201
|
+
|
|
169
202
|
function splitIntoDiffEntries(raw: string): DiffEntry[] {
|
|
170
203
|
const entries: DiffEntry[] = [];
|
|
171
|
-
// Split on "diff --git" lines
|
|
172
|
-
const chunks = raw.split(/(?=^diff --git )/m);
|
|
204
|
+
// Split on "diff --git" / "diff --cc" / "diff --combined" lines
|
|
205
|
+
const chunks = raw.split(/(?=^diff --git |^diff --cc |^diff --combined )/m);
|
|
173
206
|
for (const chunk of chunks) {
|
|
174
207
|
const trimmed = chunk.trim();
|
|
175
208
|
if (!trimmed) continue;
|
|
176
209
|
|
|
177
|
-
// Extract file path from "diff --git a/foo b/foo"
|
|
178
|
-
const match = trimmed.match(/^diff --git a\/.+? b\/(.+)$/m);
|
|
179
|
-
const filePath = match ? match[1]! : 'unknown';
|
|
180
|
-
|
|
181
210
|
entries.push({
|
|
182
|
-
filePath,
|
|
211
|
+
filePath: extractDiffFilePath(trimmed),
|
|
183
212
|
raw: chunk,
|
|
184
213
|
lines: parseDiff(chunk),
|
|
185
214
|
});
|
|
@@ -232,10 +261,16 @@ export class DiffPanel extends BasePanel {
|
|
|
232
261
|
private selectedFile = 0;
|
|
233
262
|
private scrollOffset = 0;
|
|
234
263
|
|
|
235
|
-
/**
|
|
236
|
-
|
|
264
|
+
/** One-line confirmation for the 'w'/'h'/'s' self-load hotkeys — otherwise a
|
|
265
|
+
* reload that produces identical content is visually indistinguishable
|
|
266
|
+
* from the keypress doing nothing at all. */
|
|
267
|
+
private hotkeyStatus: string | null = null;
|
|
237
268
|
|
|
238
|
-
|
|
269
|
+
public readonly confirmOverlay = new PanelConfirmOverlay(() => this.markDirty()); // armed by a command handler (e.g. /rewind); see panel-confirm-overlay.ts
|
|
270
|
+
constructor(
|
|
271
|
+
workingDirectory: string,
|
|
272
|
+
private readonly requestRender: () => void = () => {},
|
|
273
|
+
) {
|
|
239
274
|
super('diff', 'Diff', 'D', 'development');
|
|
240
275
|
this.workingDirectory = workingDirectory;
|
|
241
276
|
}
|
|
@@ -271,12 +306,33 @@ export class DiffPanel extends BasePanel {
|
|
|
271
306
|
this.markDirty();
|
|
272
307
|
}
|
|
273
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Defensive gate mirroring GitPanel's own notGitRepo messaging — short-
|
|
311
|
+
* circuits before any of this panel's self-load git spawns run, instead of
|
|
312
|
+
* letting git fail per-subcommand with an inconsistent error shape.
|
|
313
|
+
*/
|
|
314
|
+
private showNotGitRepo(): void {
|
|
315
|
+
this.showDiff('(not a git repo)', '@@ -0,0 +0,0 @@\n Not a git repository here.');
|
|
316
|
+
}
|
|
317
|
+
|
|
274
318
|
/** Run `git diff` against specific files and populate entries. */
|
|
275
319
|
async showFileDiffs(files: string[], ref?: string): Promise<void> {
|
|
320
|
+
if (!GitService.isGitRepo(this.workingDirectory)) {
|
|
321
|
+
this.showNotGitRepo();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
276
324
|
const args = ['diff', ...(ref ? [ref] : []), '--', ...files];
|
|
277
|
-
const proc = Bun.spawn(['git', ...args], { stdout: 'pipe', cwd: this.workingDirectory });
|
|
278
|
-
const raw = await
|
|
279
|
-
|
|
325
|
+
const proc = Bun.spawn(['git', ...args], { stdout: 'pipe', stderr: 'pipe', cwd: this.workingDirectory });
|
|
326
|
+
const [raw, errText] = await Promise.all([
|
|
327
|
+
new Response(proc.stdout).text(),
|
|
328
|
+
new Response(proc.stderr).text(),
|
|
329
|
+
]);
|
|
330
|
+
const exitCode = await proc.exited;
|
|
331
|
+
if (exitCode !== 0) {
|
|
332
|
+
const errorText = errText.trim() || 'git diff failed';
|
|
333
|
+
this.showDiff('(error)', `--- error\n+++ error\n@@ -0,0 +1,1 @@\n+${errorText}`);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
280
336
|
this.loadRawDiff(raw);
|
|
281
337
|
this.enrichSemanticDiffs(files, ref ?? 'HEAD').catch((err) => { logger.debug('DiffPanel: semantic diff enrichment failed', { err }); });
|
|
282
338
|
}
|
|
@@ -287,6 +343,10 @@ export class DiffPanel extends BasePanel {
|
|
|
287
343
|
* enriches every loaded file with a semantic-diff summary in the background.
|
|
288
344
|
*/
|
|
289
345
|
async showGitDiff(ref?: string): Promise<void> {
|
|
346
|
+
if (!GitService.isGitRepo(this.workingDirectory)) {
|
|
347
|
+
this.showNotGitRepo();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
290
350
|
const args = ['diff', ...(ref ? [ref] : [])];
|
|
291
351
|
const proc = Bun.spawn(['git', ...args], { stdout: 'pipe', stderr: 'pipe', cwd: this.workingDirectory });
|
|
292
352
|
const [raw, errText] = await Promise.all([
|
|
@@ -325,6 +385,10 @@ export class DiffPanel extends BasePanel {
|
|
|
325
385
|
* `/diff staged` command handler.
|
|
326
386
|
*/
|
|
327
387
|
async showStagedDiff(): Promise<void> {
|
|
388
|
+
if (!GitService.isGitRepo(this.workingDirectory)) {
|
|
389
|
+
this.showNotGitRepo();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
328
392
|
const proc = Bun.spawn(['/bin/sh', '-c', 'git diff --cached'], { stdout: 'pipe', stderr: 'pipe', cwd: this.workingDirectory });
|
|
329
393
|
const [raw, errText] = await Promise.all([
|
|
330
394
|
new Response(proc.stdout).text(),
|
|
@@ -353,7 +417,7 @@ export class DiffPanel extends BasePanel {
|
|
|
353
417
|
if (files.length === 0) return;
|
|
354
418
|
const { computeSemanticDiff, formatSemanticDiffSummary } = await import('../renderer/semantic-diff.ts');
|
|
355
419
|
const { join, relative: pathRelative } = await import('path');
|
|
356
|
-
const repoRootProc = Bun.spawn(['git', 'rev-parse', '--show-toplevel'], { stdout: 'pipe', cwd: this.workingDirectory });
|
|
420
|
+
const repoRootProc = Bun.spawn(['git', 'rev-parse', '--show-toplevel'], { stdout: 'pipe', stderr: 'pipe', cwd: this.workingDirectory });
|
|
357
421
|
await repoRootProc.exited;
|
|
358
422
|
const repoRoot = (await new Response(repoRootProc.stdout).text()).trim() || this.workingDirectory;
|
|
359
423
|
await Promise.allSettled(
|
|
@@ -418,7 +482,24 @@ export class DiffPanel extends BasePanel {
|
|
|
418
482
|
// Input
|
|
419
483
|
// -------------------------------------------------------------------------
|
|
420
484
|
|
|
485
|
+
/**
|
|
486
|
+
* Wraps a self-load hotkey ('w'/'h'/'s') with visible before/after status
|
|
487
|
+
* text in the status bar — otherwise a reload whose content is unchanged
|
|
488
|
+
* from what's already shown is indistinguishable from the keypress having
|
|
489
|
+
* done nothing at all (contrast with `/diff working`'s ctx.print() calls).
|
|
490
|
+
*/
|
|
491
|
+
private runHotkeyReload(label: string, load: () => Promise<void>): void {
|
|
492
|
+
this.hotkeyStatus = `Loading ${label} diff…`;
|
|
493
|
+
this.markDirty();
|
|
494
|
+
void load().then(() => {
|
|
495
|
+
this.hotkeyStatus = `Reloaded ${label} diff.`;
|
|
496
|
+
this.markDirty();
|
|
497
|
+
this.requestRender();
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
421
501
|
handleInput(key: string): boolean {
|
|
502
|
+
if (this.confirmOverlay.handleInput(key)) return true; // confirm takes priority (git-panel.ts:284-303 precedent)
|
|
422
503
|
switch (key) {
|
|
423
504
|
case 'up': this.scrollUp(); return true;
|
|
424
505
|
case 'down': this.scrollDown(); return true;
|
|
@@ -433,51 +514,20 @@ export class DiffPanel extends BasePanel {
|
|
|
433
514
|
case 'backtab': this.prevFile(); return true;
|
|
434
515
|
case 'pageup': this.scrollPageUp(); return true;
|
|
435
516
|
case 'pagedown': this.scrollPageDown(); return true;
|
|
436
|
-
case 'w':
|
|
437
|
-
case 'h':
|
|
438
|
-
case 's':
|
|
439
|
-
case 'o': {
|
|
440
|
-
if (!this.currentEntry()) return false;
|
|
441
|
-
this.pendingOpenPreview = true;
|
|
442
|
-
return true;
|
|
443
|
-
}
|
|
517
|
+
case 'w': this.runHotkeyReload('working tree', () => this.showGitDiff()); return true;
|
|
518
|
+
case 'h': this.runHotkeyReload('HEAD', () => this.showGitDiff('HEAD')); return true;
|
|
519
|
+
case 's': this.runHotkeyReload('staged', () => this.showStagedDiff()); return true;
|
|
444
520
|
default: return false;
|
|
445
521
|
}
|
|
446
522
|
}
|
|
447
523
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (!this.pendingOpenPreview) return false;
|
|
456
|
-
this.pendingOpenPreview = false;
|
|
457
|
-
const entry = this.currentEntry();
|
|
458
|
-
if (!entry) return false;
|
|
459
|
-
|
|
460
|
-
const pm = ctx.panelManager;
|
|
461
|
-
let previewPanel = pm.getPanel('preview');
|
|
462
|
-
if (previewPanel instanceof FilePreviewPanel) {
|
|
463
|
-
const pane = pm.getPaneOf('preview');
|
|
464
|
-
pm.activateById('preview');
|
|
465
|
-
if (pane) pm.focusPane(pane);
|
|
466
|
-
} else {
|
|
467
|
-
const targetPane: 'top' | 'bottom' = pm.isBottomPaneVisible()
|
|
468
|
-
? (pm.getFocusedPane() === 'top' ? 'bottom' : 'top')
|
|
469
|
-
: 'bottom';
|
|
470
|
-
const opened = pm.open('preview', targetPane);
|
|
471
|
-
pm.show();
|
|
472
|
-
pm.focusPane(targetPane);
|
|
473
|
-
previewPanel = opened instanceof FilePreviewPanel ? opened : null;
|
|
474
|
-
}
|
|
475
|
-
if (previewPanel instanceof FilePreviewPanel) {
|
|
476
|
-
previewPanel.openFile(entry.filePath);
|
|
477
|
-
return true;
|
|
478
|
-
}
|
|
479
|
-
return false;
|
|
480
|
-
}
|
|
524
|
+
// W6.1 (the purge): 'o' used to open the currently selected file in the
|
|
525
|
+
// preview panel via a staged pendingOpenPreview flag + a
|
|
526
|
+
// handlePanelIntegrationAction cross-panel hook (the same bridge
|
|
527
|
+
// FileExplorerPanel used). 'preview' is DELETE-disposition with no
|
|
528
|
+
// successor surface (no file-picker-overlay preview to repoint to
|
|
529
|
+
// either — verified), so the key and the hook were removed rather than
|
|
530
|
+
// repointed; diff no longer has an 'o' action.
|
|
481
531
|
|
|
482
532
|
private scrollUp(): void {
|
|
483
533
|
if (this.scrollOffset > 0) {
|
|
@@ -535,6 +585,8 @@ export class DiffPanel extends BasePanel {
|
|
|
535
585
|
return this.trackedRender(() => {
|
|
536
586
|
if (height <= 0 || width <= 0) return [];
|
|
537
587
|
|
|
588
|
+
const confirmLines = this.confirmOverlay.renderLines(width);
|
|
589
|
+
if (confirmLines) return buildPanelWorkspace(width, height, { title: 'Diff Workspace', palette: COLOR, sections: [{ title: 'Confirmation', lines: confirmLines }] });
|
|
538
590
|
if (this.entries.length === 0) {
|
|
539
591
|
return buildPanelWorkspace(width, height, {
|
|
540
592
|
title: 'Diff Workspace',
|
|
@@ -623,11 +675,15 @@ export class DiffPanel extends BasePanel {
|
|
|
623
675
|
for (let i = 0; i < this.entries.length; i++) {
|
|
624
676
|
const entry = this.entries[i]!;
|
|
625
677
|
const active = i === this.selectedFile;
|
|
678
|
+
const placeholder = isPlaceholderPath(entry.filePath);
|
|
626
679
|
const stat = diffStat(entry);
|
|
627
|
-
|
|
680
|
+
// Placeholder entries ('(error)', '(no changes)', ...) get a distinct
|
|
681
|
+
// warn color regardless of active/inactive — they are not a real diff
|
|
682
|
+
// of a real file and must not read as one.
|
|
683
|
+
const fg = placeholder ? COLOR.warn : (active ? COLOR.tabActive : COLOR.tabInactive);
|
|
628
684
|
const bg = active ? COLOR.tabActiveBg : COLOR.tabBg;
|
|
629
685
|
// Active file gets a leading marker; every tab shows +adds/-dels at a glance.
|
|
630
|
-
const marker = active ? '▸ ' : ' ';
|
|
686
|
+
const marker = active ? '▸ ' : (placeholder ? '⚠ ' : ' ');
|
|
631
687
|
const label = `${marker}${basename(entry.filePath)} `;
|
|
632
688
|
for (const ch of label) push(ch, fg, bg, active);
|
|
633
689
|
if (stat.added > 0) for (const ch of `+${stat.added}`) push(ch, COLOR.addition, bg, active);
|
|
@@ -650,11 +706,15 @@ export class DiffPanel extends BasePanel {
|
|
|
650
706
|
return buildStyledPanelLine(width, [{ text: ' No file', fg: COLOR.tabInactive, bg: COLOR.statusBar }], { fillBg: COLOR.statusBar });
|
|
651
707
|
}
|
|
652
708
|
const stat = diffStat(entry);
|
|
709
|
+
const placeholder = isPlaceholderPath(entry.filePath);
|
|
653
710
|
// Keep the file path display-width-aware so a long/wide path can't overflow.
|
|
654
711
|
const pathBudget = Math.max(8, Math.floor(width / 2));
|
|
655
712
|
const fileInfo = truncateDisplay(entry.filePath, pathBudget);
|
|
656
713
|
const segments: Array<{ text: string; fg: string; bg?: string; bold?: boolean }> = [
|
|
657
|
-
|
|
714
|
+
// Placeholder states ('(error)', '(no changes)', ...) are not a real
|
|
715
|
+
// diff of a real file — a distinct warn color keeps them from being
|
|
716
|
+
// mistaken for one.
|
|
717
|
+
{ text: ` ${fileInfo} `, fg: placeholder ? COLOR.warn : COLOR.filename, bg: COLOR.statusBar, bold: true },
|
|
658
718
|
{ text: `[${this.selectedFile + 1}/${this.entries.length}]`, fg: COLOR.tabInactive, bg: COLOR.statusBar },
|
|
659
719
|
{ text: ' +', fg: COLOR.tabInactive, bg: COLOR.statusBar },
|
|
660
720
|
{ text: String(stat.added), fg: COLOR.addition, bg: COLOR.statusBar },
|
|
@@ -666,6 +726,9 @@ export class DiffPanel extends BasePanel {
|
|
|
666
726
|
if (entry.semanticSummary) {
|
|
667
727
|
segments.push({ text: ` ◈ ${entry.semanticSummary}`, fg: COLOR.hunk, bg: COLOR.statusBar });
|
|
668
728
|
}
|
|
729
|
+
if (this.hotkeyStatus) {
|
|
730
|
+
segments.push({ text: ` ${this.hotkeyStatus}`, fg: COLOR.info, bg: COLOR.statusBar, bold: true });
|
|
731
|
+
}
|
|
669
732
|
return buildStyledPanelLine(width, segments, { fillBg: COLOR.statusBar });
|
|
670
733
|
}
|
|
671
734
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EvalRegistry — holds the latest evaluation harness run state.
|
|
3
|
+
*
|
|
4
|
+
* W6.1 (the purge): extracted out of eval-panel.ts. 'eval' was
|
|
5
|
+
* DELETE-disposition (no surviving human surface — the evaluation harness
|
|
6
|
+
* is driven and reviewed via the `/eval` CLI command, input/commands/eval.ts)
|
|
7
|
+
* but this registry is still a live consumer of `/eval run` output, so it
|
|
8
|
+
* survives as a standalone read-model rather than being deleted with the
|
|
9
|
+
* view. See .goodvibes/audit/2026-07-04-wave6-briefs.json (W6.1).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { EvalSuiteResult, EvalGateResult } from '@/runtime/index.ts';
|
|
13
|
+
|
|
14
|
+
export class EvalRegistry {
|
|
15
|
+
private _suiteResults: EvalSuiteResult[] = [];
|
|
16
|
+
private _gateResults: EvalGateResult[] = [];
|
|
17
|
+
private _running = false;
|
|
18
|
+
private _lastRunAt: number | null = null;
|
|
19
|
+
private readonly _subscribers = new Set<() => void>();
|
|
20
|
+
|
|
21
|
+
push(result: EvalSuiteResult): void {
|
|
22
|
+
const idx = this._suiteResults.findIndex((r) => r.suite === result.suite);
|
|
23
|
+
if (idx >= 0) {
|
|
24
|
+
this._suiteResults[idx] = result;
|
|
25
|
+
} else {
|
|
26
|
+
this._suiteResults.push(result);
|
|
27
|
+
}
|
|
28
|
+
this._lastRunAt = Date.now();
|
|
29
|
+
this._notify();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pushGate(gate: EvalGateResult): void {
|
|
33
|
+
const idx = this._gateResults.findIndex((g) => g.suite === gate.suite);
|
|
34
|
+
if (idx >= 0) {
|
|
35
|
+
this._gateResults[idx] = gate;
|
|
36
|
+
} else {
|
|
37
|
+
this._gateResults.push(gate);
|
|
38
|
+
}
|
|
39
|
+
this._notify();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setRunning(running: boolean): void {
|
|
43
|
+
this._running = running;
|
|
44
|
+
this._notify();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
isRunning(): boolean { return this._running; }
|
|
48
|
+
getLastRunAt(): number | null { return this._lastRunAt; }
|
|
49
|
+
getSuiteResults(): EvalSuiteResult[] { return this._suiteResults; }
|
|
50
|
+
getGateResults(): EvalGateResult[] { return this._gateResults; }
|
|
51
|
+
|
|
52
|
+
subscribe(cb: () => void): () => void {
|
|
53
|
+
this._subscribers.add(cb);
|
|
54
|
+
return () => this._subscribers.delete(cb);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private _notify(): void {
|
|
58
|
+
for (const cb of this._subscribers) cb();
|
|
59
|
+
}
|
|
60
|
+
}
|