@pellux/goodvibes-tui 0.27.0 → 0.28.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 +23 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation.ts +4 -6
- package/src/core/session-recovery.ts +2 -0
- package/src/core/turn-event-wiring.ts +1 -0
- package/src/input/commands/eval.ts +4 -3
- package/src/input/handler-feed-routes.ts +61 -4
- package/src/input/handler-feed.ts +13 -0
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-ui-state.ts +4 -0
- package/src/input/keybindings.ts +5 -0
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +10 -0
- package/src/main.ts +0 -1
- package/src/panels/agent-inspector-panel.ts +18 -13
- package/src/panels/agent-inspector-shared.ts +3 -3
- package/src/panels/agent-logs-panel.ts +22 -53
- package/src/panels/approval-panel.ts +146 -95
- package/src/panels/automation-control-panel.ts +24 -1
- package/src/panels/base-panel.ts +26 -2
- package/src/panels/builtin/development.ts +1 -1
- package/src/panels/cockpit-panel.ts +24 -5
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +108 -43
- package/src/panels/context-visualizer-panel.ts +25 -1
- package/src/panels/control-plane-panel.ts +27 -1
- package/src/panels/cost-tracker-panel.ts +82 -54
- package/src/panels/debug-panel.ts +51 -16
- package/src/panels/diff-panel.ts +59 -30
- package/src/panels/docs-panel.ts +19 -3
- package/src/panels/eval-panel.ts +130 -81
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +40 -41
- package/src/panels/file-preview-panel.ts +9 -2
- package/src/panels/forensics-panel.ts +27 -13
- package/src/panels/git-panel.ts +58 -70
- package/src/panels/hooks-panel.ts +36 -2
- package/src/panels/incident-review-panel.ts +31 -10
- package/src/panels/intelligence-panel.ts +152 -71
- package/src/panels/knowledge-graph-panel.ts +89 -27
- package/src/panels/local-auth-panel.ts +17 -11
- package/src/panels/marketplace-panel.ts +31 -12
- package/src/panels/memory-panel.ts +5 -4
- package/src/panels/ops-control-panel.ts +69 -7
- package/src/panels/ops-strategy-panel.ts +61 -43
- package/src/panels/orchestration-panel.ts +34 -4
- package/src/panels/panel-list-panel.ts +33 -8
- package/src/panels/panel-manager.ts +23 -4
- package/src/panels/plan-dashboard-panel.ts +183 -66
- package/src/panels/plugins-panel.ts +48 -10
- package/src/panels/policy-panel.ts +60 -23
- package/src/panels/polish-core.ts +157 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +44 -145
- package/src/panels/project-planning-panel.ts +22 -0
- package/src/panels/provider-accounts-panel.ts +55 -18
- package/src/panels/provider-health-panel.ts +65 -23
- package/src/panels/provider-stats-panel.ts +47 -22
- package/src/panels/qr-panel.ts +23 -11
- package/src/panels/remote-panel.ts +12 -5
- package/src/panels/routes-panel.ts +27 -5
- package/src/panels/sandbox-panel.ts +62 -27
- package/src/panels/schedule-panel.ts +40 -17
- package/src/panels/scrollable-list-panel.ts +124 -10
- package/src/panels/security-panel.ts +72 -20
- package/src/panels/services-panel.ts +68 -22
- package/src/panels/session-browser-panel.ts +29 -5
- package/src/panels/session-maintenance.ts +2 -2
- package/src/panels/settings-sync-panel.ts +30 -15
- package/src/panels/subscription-panel.ts +21 -3
- package/src/panels/symbol-outline-panel.ts +40 -47
- package/src/panels/system-messages-panel.ts +60 -27
- package/src/panels/tasks-panel.ts +34 -7
- package/src/panels/thinking-panel.ts +26 -25
- package/src/panels/token-budget-panel.ts +49 -38
- package/src/panels/tool-inspector-panel.ts +27 -21
- package/src/panels/types.ts +25 -0
- package/src/panels/watchers-panel.ts +25 -5
- package/src/panels/work-plan-panel.ts +125 -34
- package/src/panels/worktree-panel.ts +65 -20
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +31 -127
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/model-picker-overlay.ts +9 -4
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/progress.ts +3 -2
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/ui-factory.ts +4 -6
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/version.ts +1 -1
- package/src/panels/panel-picker.ts +0 -106
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BasePanel } from './base-panel.ts';
|
|
2
2
|
import { createEmptyLine, createStyledCell, type Line } from '../types/grid.ts';
|
|
3
|
+
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
4
|
import type { TurnEvent } from '@/runtime/index.ts';
|
|
4
5
|
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
5
6
|
import type { Orchestrator } from '../core/orchestrator';
|
|
@@ -14,6 +15,8 @@ import {
|
|
|
14
15
|
extendPalette,
|
|
15
16
|
type PanelWorkspaceSection,
|
|
16
17
|
} from './polish.ts';
|
|
18
|
+
import { abbreviateCount } from '../utils/format-number.ts';
|
|
19
|
+
import { formatLatencyMs } from '../utils/format-duration.ts';
|
|
17
20
|
|
|
18
21
|
// ---------------------------------------------------------------------------
|
|
19
22
|
// Types
|
|
@@ -68,16 +71,11 @@ const LATENCY_BAD_MS = 5_000;
|
|
|
68
71
|
// ---------------------------------------------------------------------------
|
|
69
72
|
|
|
70
73
|
function fmtTok(n: number): string {
|
|
71
|
-
|
|
72
|
-
if (n < 1_000_000) return (n / 1000).toFixed(1) + 'k';
|
|
73
|
-
return (n / 1_000_000).toFixed(2) + 'M';
|
|
74
|
+
return abbreviateCount(n, { guard: 10_000, decimals: 1, mDecimals: 2 });
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
function fmtMs(ms: number): string {
|
|
77
|
-
|
|
78
|
-
if (ms >= 10_000) return `${(ms / 1000).toFixed(1)}s`;
|
|
79
|
-
if (ms >= 1_000) return `${(ms / 1000).toFixed(2)}s`;
|
|
80
|
-
return `${Math.round(ms)}ms`;
|
|
78
|
+
return formatLatencyMs(ms);
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
function fmtAgo(ts: number): string {
|
|
@@ -315,10 +313,21 @@ export class DebugPanel extends BasePanel {
|
|
|
315
313
|
}
|
|
316
314
|
}
|
|
317
315
|
|
|
316
|
+
const latestError = this._errors[this._errors.length - 1];
|
|
317
|
+
const footerLines = latestError
|
|
318
|
+
? [buildStyledPanelLine(width, [
|
|
319
|
+
{ text: ' ✕ latest error ', fg: C.bad },
|
|
320
|
+
{ text: truncateDisplay(latestError.errorMessage ?? 'unknown error', Math.max(0, width - 18)), fg: C.dim },
|
|
321
|
+
])]
|
|
322
|
+
: [buildStyledPanelLine(width, [
|
|
323
|
+
{ text: ' Live feed — newest calls at the bottom; auto-follows.', fg: C.dim },
|
|
324
|
+
])];
|
|
325
|
+
|
|
318
326
|
return buildPanelWorkspace(width, height, {
|
|
319
327
|
title: ' API Debug',
|
|
320
328
|
intro: 'Recent provider calls, token deltas, latency, status codes, and error history.',
|
|
321
329
|
sections,
|
|
330
|
+
footerLines,
|
|
322
331
|
palette: DEFAULT_PANEL_PALETTE,
|
|
323
332
|
});
|
|
324
333
|
}
|
|
@@ -330,16 +339,42 @@ export class DebugPanel extends BasePanel {
|
|
|
330
339
|
private _renderSummary(width: number): Line[] {
|
|
331
340
|
const errCount = this._totalErrors;
|
|
332
341
|
const okCount = this._totalCalls - this._totalErrors;
|
|
333
|
-
|
|
342
|
+
const last = this._calls[this._calls.length - 1];
|
|
343
|
+
const recent = this._calls.slice(-10);
|
|
344
|
+
const avgLat = recent.length > 0
|
|
345
|
+
? Math.round(recent.reduce((s, c) => s + c.latencyMs, 0) / recent.length)
|
|
346
|
+
: 0;
|
|
347
|
+
const sessionTokens = this._calls.reduce((s, c) => s + c.inputTokens + c.outputTokens, 0);
|
|
348
|
+
|
|
349
|
+
const lines: Line[] = [
|
|
334
350
|
buildStyledPanelLine(width, [
|
|
335
|
-
{ text: ' Calls
|
|
351
|
+
{ text: ' Calls ', fg: C.label },
|
|
336
352
|
{ text: String(this._totalCalls), fg: C.value },
|
|
337
|
-
{ text: '
|
|
353
|
+
{ text: ' OK ', fg: C.label },
|
|
338
354
|
{ text: String(okCount), fg: C.good },
|
|
339
|
-
{ text: '
|
|
355
|
+
{ text: ' Errors ', fg: C.label },
|
|
340
356
|
{ text: String(errCount), fg: errCount > 0 ? C.bad : C.dim },
|
|
357
|
+
{ text: ' Avg latency ', fg: C.label },
|
|
358
|
+
{ text: fmtMs(avgLat), fg: avgLat > 0 ? latColor(avgLat) : C.dim },
|
|
359
|
+
{ text: ' Tokens ', fg: C.label },
|
|
360
|
+
{ text: fmtTok(sessionTokens), fg: C.value },
|
|
341
361
|
]),
|
|
342
362
|
];
|
|
363
|
+
// Live status: most recent call (latency / age) or wiring hint.
|
|
364
|
+
if (last) {
|
|
365
|
+
lines.push(buildStyledPanelLine(width, [
|
|
366
|
+
{ text: ' Last ', fg: C.label },
|
|
367
|
+
{ text: last.status === 'ok' ? '✓ ' : '✕ ', fg: last.status === 'ok' ? C.good : C.bad },
|
|
368
|
+
{ text: fitDisplay(last.model, 22), fg: C.value },
|
|
369
|
+
{ text: ' ' + fmtMs(last.latencyMs), fg: latColor(last.latencyMs) },
|
|
370
|
+
{ text: ' ' + fmtAgo(last.ts), fg: C.dim },
|
|
371
|
+
]));
|
|
372
|
+
} else if (!this._orchestrator) {
|
|
373
|
+
lines.push(buildStyledPanelLine(width, [
|
|
374
|
+
{ text: ' Per-call token deltas need the orchestrator wired (wireOrchestrator).', fg: C.dim },
|
|
375
|
+
]));
|
|
376
|
+
}
|
|
377
|
+
return lines;
|
|
343
378
|
}
|
|
344
379
|
|
|
345
380
|
private _renderCallLog(width: number): Line[] {
|
|
@@ -355,15 +390,15 @@ export class DebugPanel extends BasePanel {
|
|
|
355
390
|
private _callLogHeader(width: number): Line {
|
|
356
391
|
// Layout: time(8) status(2) provider(12) model(20) in(8) out(8) lat(8)
|
|
357
392
|
const header = ' Time S Provider Model In Out Lat';
|
|
358
|
-
return this._textLine(header
|
|
393
|
+
return this._textLine(truncateDisplay(header, width), C.colHdr, width, { dim: true });
|
|
359
394
|
}
|
|
360
395
|
|
|
361
396
|
private _callLogRow(e: ApiCallEntry, width: number): Line {
|
|
362
397
|
const timeStr = fmtAgo(e.ts).padEnd(8);
|
|
363
398
|
const statusChar = e.status === 'ok' ? '✓' : '✕';
|
|
364
399
|
const statusFg = e.status === 'ok' ? C.good : C.bad;
|
|
365
|
-
const provStr = e.provider
|
|
366
|
-
const modelStr = e.model
|
|
400
|
+
const provStr = fitDisplay(e.provider, 12);
|
|
401
|
+
const modelStr = fitDisplay(e.model, 20);
|
|
367
402
|
const inStr = fmtTok(e.inputTokens).padStart(8);
|
|
368
403
|
const outStr = fmtTok(e.outputTokens).padStart(8);
|
|
369
404
|
const latStr = fmtMs(e.latencyMs).padStart(8);
|
|
@@ -401,9 +436,9 @@ export class DebugPanel extends BasePanel {
|
|
|
401
436
|
private _errorRow(e: ApiCallEntry, width: number): Line {
|
|
402
437
|
const timeStr = fmtAgo(e.ts).padEnd(8);
|
|
403
438
|
const codeStr = e.statusCode > 0 ? `[${e.statusCode}] ` : '';
|
|
404
|
-
const msgStr = (e.errorMessage ?? 'unknown error'
|
|
439
|
+
const msgStr = truncateDisplay(e.errorMessage ?? 'unknown error', Math.max(0, width - 12 - codeStr.length));
|
|
405
440
|
const full = ` ${timeStr} ${codeStr}${msgStr}`;
|
|
406
|
-
return this._textLine(full
|
|
441
|
+
return this._textLine(truncateDisplay(full, width), C.bad, width);
|
|
407
442
|
}
|
|
408
443
|
|
|
409
444
|
// -------------------------------------------------------------------------
|
package/src/panels/diff-panel.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Line } from '../types/grid.ts';
|
|
6
6
|
import { createStyledCell, createEmptyLine } from '../types/grid.ts';
|
|
7
|
+
import { truncateDisplay, getDisplayWidth } from '../utils/terminal-width.ts';
|
|
7
8
|
import { BasePanel } from './base-panel.ts';
|
|
8
9
|
import {
|
|
9
10
|
buildBodyText,
|
|
@@ -21,14 +22,18 @@ import {
|
|
|
21
22
|
|
|
22
23
|
const COLOR = {
|
|
23
24
|
addition: '#00ff88',
|
|
25
|
+
additionBg: '#001a0d',
|
|
24
26
|
deletion: '#ff4444',
|
|
27
|
+
deletionBg: '#1a0000',
|
|
25
28
|
hunk: '#88aaff',
|
|
29
|
+
hunkBg: '#0a0a1a',
|
|
26
30
|
header: '#aaaaaa',
|
|
27
31
|
lineNum: '#555555',
|
|
28
32
|
lineNumAdd: '#00aa55',
|
|
29
33
|
lineNumDel: '#aa2222',
|
|
30
34
|
filename: '#ffffff',
|
|
31
35
|
tabActive: '#ffffff',
|
|
36
|
+
tabActiveBg: '#333333',
|
|
32
37
|
tabInactive: '#666666',
|
|
33
38
|
tabBg: '#222222',
|
|
34
39
|
context: '#888888',
|
|
@@ -47,6 +52,17 @@ interface DiffEntry {
|
|
|
47
52
|
semanticSummary?: string;
|
|
48
53
|
}
|
|
49
54
|
|
|
55
|
+
/** +added / -removed line counts for a parsed diff entry. */
|
|
56
|
+
function diffStat(entry: DiffEntry): { added: number; removed: number } {
|
|
57
|
+
let added = 0;
|
|
58
|
+
let removed = 0;
|
|
59
|
+
for (const line of entry.lines) {
|
|
60
|
+
if (line.kind === 'addition') added++;
|
|
61
|
+
else if (line.kind === 'deletion') removed++;
|
|
62
|
+
}
|
|
63
|
+
return { added, removed };
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
type LineKind = 'addition' | 'deletion' | 'context' | 'hunk' | 'header';
|
|
51
67
|
|
|
52
68
|
interface ParsedLine {
|
|
@@ -147,9 +163,7 @@ function makeLine(
|
|
|
147
163
|
const LEFT_W = 5;
|
|
148
164
|
const usedForNums = LEFT_W + 1 + LEFT_W + 1 + 2; // 14
|
|
149
165
|
const contentWidth = Math.max(0, width - usedForNums);
|
|
150
|
-
const truncated = content
|
|
151
|
-
? content.slice(0, contentWidth)
|
|
152
|
-
: content;
|
|
166
|
+
const truncated = truncateDisplay(content, contentWidth);
|
|
153
167
|
return buildStyledPanelLine(width, [
|
|
154
168
|
{ text: leftNum.padStart(LEFT_W), fg: numFg, bg, dim: true },
|
|
155
169
|
{ text: ' ', fg: '', bg },
|
|
@@ -161,8 +175,7 @@ function makeLine(
|
|
|
161
175
|
}
|
|
162
176
|
|
|
163
177
|
function renderText(width: number, text: string, fg: string, bg: string, bold = false): Line {
|
|
164
|
-
|
|
165
|
-
return buildStyledPanelLine(width, [{ text: truncated, fg, bg, bold }]);
|
|
178
|
+
return buildStyledPanelLine(width, [{ text: truncateDisplay(text, width), fg, bg, bold }]);
|
|
166
179
|
}
|
|
167
180
|
|
|
168
181
|
// ---------------------------------------------------------------------------
|
|
@@ -448,44 +461,60 @@ export class DiffPanel extends BasePanel {
|
|
|
448
461
|
|
|
449
462
|
private renderTabBar(width: number): Line {
|
|
450
463
|
const cells: Line = [];
|
|
464
|
+
const push = (ch: string, fg: string, bg: string, bold = false): void => {
|
|
465
|
+
const cw = getDisplayWidth(ch);
|
|
466
|
+
if (cells.length + cw > width) return;
|
|
467
|
+
cells.push(createStyledCell(ch, { fg, bg, bold }));
|
|
468
|
+
if (cw === 2 && cells.length < width) cells.push(createStyledCell('', { fg, bg }));
|
|
469
|
+
};
|
|
451
470
|
|
|
452
471
|
for (let i = 0; i < this.entries.length; i++) {
|
|
453
472
|
const entry = this.entries[i]!;
|
|
454
473
|
const active = i === this.selectedFile;
|
|
455
|
-
const
|
|
474
|
+
const stat = diffStat(entry);
|
|
456
475
|
const fg = active ? COLOR.tabActive : COLOR.tabInactive;
|
|
457
|
-
const bg = active ?
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
|
|
476
|
+
const bg = active ? COLOR.tabActiveBg : COLOR.tabBg;
|
|
477
|
+
// Active file gets a leading marker; every tab shows +adds/-dels at a glance.
|
|
478
|
+
const marker = active ? '▸ ' : ' ';
|
|
479
|
+
const label = `${marker}${basename(entry.filePath)} `;
|
|
480
|
+
for (const ch of label) push(ch, fg, bg, active);
|
|
481
|
+
if (stat.added > 0) for (const ch of `+${stat.added}`) push(ch, COLOR.addition, bg, active);
|
|
482
|
+
if (stat.added > 0 && stat.removed > 0) push(' ', fg, bg);
|
|
483
|
+
if (stat.removed > 0) for (const ch of `-${stat.removed}`) push(ch, COLOR.deletion, bg, active);
|
|
484
|
+
push(' ', fg, bg);
|
|
485
|
+
push('│', COLOR.lineNum, COLOR.tabBg);
|
|
467
486
|
}
|
|
468
487
|
|
|
469
|
-
// Fill remaining
|
|
470
488
|
while (cells.length < width) {
|
|
471
489
|
cells.push(createStyledCell(' ', { fg: '', bg: COLOR.tabBg }));
|
|
472
490
|
}
|
|
473
|
-
|
|
474
491
|
return cells.slice(0, width);
|
|
475
492
|
}
|
|
476
493
|
|
|
477
494
|
// ── Status bar ───────────────────────────────────────────────────────────
|
|
478
495
|
|
|
479
496
|
private renderStatusBar(width: number, entry: DiffEntry | null): Line {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
const text
|
|
488
|
-
|
|
497
|
+
if (!entry) {
|
|
498
|
+
return buildStyledPanelLine(width, [{ text: ' No file', fg: COLOR.tabInactive, bg: COLOR.statusBar }], { fillBg: COLOR.statusBar });
|
|
499
|
+
}
|
|
500
|
+
const stat = diffStat(entry);
|
|
501
|
+
// Keep the file path display-width-aware so a long/wide path can't overflow.
|
|
502
|
+
const pathBudget = Math.max(8, Math.floor(width / 2));
|
|
503
|
+
const fileInfo = truncateDisplay(entry.filePath, pathBudget);
|
|
504
|
+
const segments: Array<{ text: string; fg: string; bg?: string; bold?: boolean }> = [
|
|
505
|
+
{ text: ` ${fileInfo} `, fg: COLOR.filename, bg: COLOR.statusBar, bold: true },
|
|
506
|
+
{ text: `[${this.selectedFile + 1}/${this.entries.length}]`, fg: COLOR.tabInactive, bg: COLOR.statusBar },
|
|
507
|
+
{ text: ' +', fg: COLOR.tabInactive, bg: COLOR.statusBar },
|
|
508
|
+
{ text: String(stat.added), fg: COLOR.addition, bg: COLOR.statusBar },
|
|
509
|
+
{ text: ' -', fg: COLOR.tabInactive, bg: COLOR.statusBar },
|
|
510
|
+
{ text: String(stat.removed), fg: COLOR.deletion, bg: COLOR.statusBar },
|
|
511
|
+
{ text: ` L${this.scrollOffset + 1}/${entry.lines.length}`, fg: COLOR.tabInactive, bg: COLOR.statusBar },
|
|
512
|
+
{ text: ' Tab next ↑/↓ scroll', fg: COLOR.context, bg: COLOR.statusBar },
|
|
513
|
+
];
|
|
514
|
+
if (entry.semanticSummary) {
|
|
515
|
+
segments.push({ text: ` ◈ ${entry.semanticSummary}`, fg: COLOR.hunk, bg: COLOR.statusBar });
|
|
516
|
+
}
|
|
517
|
+
return buildStyledPanelLine(width, segments, { fillBg: COLOR.statusBar });
|
|
489
518
|
}
|
|
490
519
|
|
|
491
520
|
// ── Parsed line ──────────────────────────────────────────────────────────
|
|
@@ -496,11 +525,11 @@ export class DiffPanel extends BasePanel {
|
|
|
496
525
|
|
|
497
526
|
switch (pl.kind) {
|
|
498
527
|
case 'addition':
|
|
499
|
-
return makeLine(width, left, right, `+ ${pl.text}`, COLOR.addition,
|
|
528
|
+
return makeLine(width, left, right, `+ ${pl.text}`, COLOR.addition, COLOR.additionBg, COLOR.lineNumAdd, true);
|
|
500
529
|
case 'deletion':
|
|
501
|
-
return makeLine(width, left, right, `- ${pl.text}`, COLOR.deletion,
|
|
530
|
+
return makeLine(width, left, right, `- ${pl.text}`, COLOR.deletion, COLOR.deletionBg, COLOR.lineNumDel, false);
|
|
502
531
|
case 'hunk':
|
|
503
|
-
return renderText(width, pl.text, COLOR.hunk,
|
|
532
|
+
return renderText(width, pl.text, COLOR.hunk, COLOR.hunkBg, false);
|
|
504
533
|
case 'header':
|
|
505
534
|
return renderText(width, pl.text, COLOR.header, '', false);
|
|
506
535
|
case 'context':
|
package/src/panels/docs-panel.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Line } from '../types/grid.ts';
|
|
6
6
|
import { BasePanel } from './base-panel.ts';
|
|
7
|
-
import { buildPanelLine, buildPanelWorkspace, buildSearchInputLine, resolveScrollablePanelSection, extendPalette, DEFAULT_PANEL_PALETTE } from './polish.ts';
|
|
7
|
+
import { buildKeyboardHints, buildPanelLine, buildPanelWorkspace, buildSearchInputLine, resolveScrollablePanelSection, extendPalette, DEFAULT_PANEL_PALETTE } from './polish.ts';
|
|
8
8
|
import type { ProviderModelCatalogQuery, ToolCatalogQuery } from '../runtime/ui-service-queries.ts';
|
|
9
9
|
import {
|
|
10
10
|
getPanelSearchFocusTransition,
|
|
@@ -152,9 +152,24 @@ export class DocsPanel extends BasePanel {
|
|
|
152
152
|
buildSearchInputLine(width, '', searchLine.trimStart(), DEFAULT_PANEL_PALETTE, { active: this.searching }),
|
|
153
153
|
],
|
|
154
154
|
} as const;
|
|
155
|
+
// Context-aware footer: while searching, surface only the keys that work in
|
|
156
|
+
// the search field; otherwise surface section + navigation keys.
|
|
157
|
+
const footerLines = [this.searching
|
|
158
|
+
? buildKeyboardHints(width, [
|
|
159
|
+
{ keys: 'type', label: 'filter' },
|
|
160
|
+
{ keys: 'Enter/Esc', label: 'apply / exit search' },
|
|
161
|
+
{ keys: '↓', label: 'back to list' },
|
|
162
|
+
], DEFAULT_PANEL_PALETTE)
|
|
163
|
+
: buildKeyboardHints(width, [
|
|
164
|
+
{ keys: 't/m/k', label: 'tools / models / shortcuts' },
|
|
165
|
+
{ keys: '↑/↓', label: 'navigate' },
|
|
166
|
+
{ keys: '/', label: 'search' },
|
|
167
|
+
], DEFAULT_PANEL_PALETTE)];
|
|
168
|
+
|
|
155
169
|
const sectionWindow = resolveScrollablePanelSection(width, height, {
|
|
156
170
|
intro: 'Browse built-in tool docs, available models, and keyboard shortcuts from one shared reference surface.',
|
|
157
171
|
palette: DEFAULT_PANEL_PALETTE,
|
|
172
|
+
footerLines,
|
|
158
173
|
beforeSections: [controlsSection],
|
|
159
174
|
section: {
|
|
160
175
|
title: sectionLabel,
|
|
@@ -176,6 +191,7 @@ export class DocsPanel extends BasePanel {
|
|
|
176
191
|
controlsSection,
|
|
177
192
|
sectionWindow.section.lines.length > 0 ? sectionWindow.section : { title: sectionLabel, lines: [buildPanelLine(width, [[' No matching docs', DEFAULT_PANEL_PALETTE.dim]])] },
|
|
178
193
|
],
|
|
194
|
+
footerLines,
|
|
179
195
|
palette: DEFAULT_PANEL_PALETTE,
|
|
180
196
|
});
|
|
181
197
|
}
|
|
@@ -207,7 +223,7 @@ export class DocsPanel extends BasePanel {
|
|
|
207
223
|
const tools = this.toolRegistry?.list() ?? [];
|
|
208
224
|
const filtered = q ? tools.filter(t => t.definition.name.toLowerCase().includes(q) || (t.definition.description ?? '').toLowerCase().includes(q)) : tools;
|
|
209
225
|
if (filtered.length === 0) {
|
|
210
|
-
rows.push({ kind: 'empty', text:
|
|
226
|
+
rows.push({ kind: 'empty', text: q ? ` No tools match "${this.searchQuery}". Press Esc to clear the filter.` : ' Tool registry not wired into this session.', fg: C.dim, bg: '' });
|
|
211
227
|
} else {
|
|
212
228
|
rows.push({ kind: 'header', text: ` Tools (${filtered.length})`, fg: C.sectionFg, bg: C.sectionBg, bold: true });
|
|
213
229
|
for (const tool of filtered) {
|
|
@@ -237,7 +253,7 @@ export class DocsPanel extends BasePanel {
|
|
|
237
253
|
const models = this.providerRegistry?.listModels() ?? [];
|
|
238
254
|
const filtered = q ? models.filter(m => m.id.toLowerCase().includes(q) || m.displayName.toLowerCase().includes(q) || m.provider.toLowerCase().includes(q)) : models;
|
|
239
255
|
if (filtered.length === 0) {
|
|
240
|
-
rows.push({ kind: 'empty', text:
|
|
256
|
+
rows.push({ kind: 'empty', text: q ? ` No models match "${this.searchQuery}". Press Esc to clear the filter.` : ' Provider registry not wired into this session.', fg: C.dim, bg: '' });
|
|
241
257
|
} else {
|
|
242
258
|
// Group by provider
|
|
243
259
|
const byProvider = new Map<string, typeof filtered>();
|