@pellux/goodvibes-tui 0.27.0 → 0.29.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 +124 -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/cost-runtime.ts +49 -0
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-feed-routes.ts +60 -26
- package/src/input/handler-feed.ts +17 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler-ui-state.ts +4 -0
- package/src/input/handler.ts +43 -6
- package/src/input/keybindings.ts +53 -8
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +117 -1
- package/src/main.ts +5 -2
- package/src/panels/agent-inspector-panel.ts +139 -45
- package/src/panels/agent-inspector-shared.ts +147 -3
- package/src/panels/approval-panel.ts +204 -102
- package/src/panels/automation-control-panel.ts +370 -103
- package/src/panels/base-panel.ts +26 -2
- package/src/panels/builtin/agent.ts +28 -37
- package/src/panels/builtin/development.ts +40 -32
- package/src/panels/builtin/knowledge.ts +15 -6
- package/src/panels/builtin/operations.ts +178 -117
- package/src/panels/builtin/session.ts +35 -9
- package/src/panels/builtin/shared.ts +98 -6
- package/src/panels/cockpit-panel.ts +253 -75
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +159 -56
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +354 -95
- package/src/panels/cost-tracker-panel.ts +243 -57
- package/src/panels/debug-panel.ts +347 -159
- package/src/panels/diff-panel.ts +257 -82
- package/src/panels/docs-panel.ts +166 -64
- package/src/panels/eval-panel.ts +336 -109
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +238 -159
- package/src/panels/file-preview-panel.ts +141 -59
- package/src/panels/git-panel.ts +360 -191
- package/src/panels/hooks-panel.ts +181 -19
- package/src/panels/incident-review-panel.ts +252 -43
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +310 -103
- package/src/panels/knowledge-graph-panel.ts +480 -58
- package/src/panels/local-auth-panel.ts +251 -33
- package/src/panels/marketplace-panel.ts +219 -33
- package/src/panels/memory-panel.ts +83 -81
- package/src/panels/ops-control-panel.ts +211 -32
- package/src/panels/ops-strategy-panel.ts +131 -45
- package/src/panels/orchestration-panel.ts +259 -67
- package/src/panels/panel-list-panel.ts +184 -136
- package/src/panels/panel-manager.ts +120 -13
- package/src/panels/plan-dashboard-panel.ts +507 -74
- package/src/panels/plugins-panel.ts +227 -34
- package/src/panels/policy-panel.ts +264 -55
- package/src/panels/polish-core.ts +162 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +67 -149
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +84 -113
- package/src/panels/provider-health-panel.ts +438 -480
- package/src/panels/provider-health-routes.ts +203 -0
- package/src/panels/provider-health-tracker.ts +194 -6
- package/src/panels/provider-health-views.ts +560 -0
- package/src/panels/qr-panel.ts +136 -38
- package/src/panels/remote-panel.ts +123 -38
- package/src/panels/routes-panel.ts +87 -24
- package/src/panels/sandbox-panel.ts +232 -65
- package/src/panels/scrollable-list-panel.ts +143 -145
- package/src/panels/security-panel.ts +204 -52
- package/src/panels/services-panel.ts +174 -76
- package/src/panels/session-browser-panel.ts +101 -6
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +346 -68
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +105 -34
- package/src/panels/symbol-outline-panel.ts +287 -154
- package/src/panels/system-messages-panel.ts +172 -38
- package/src/panels/tasks-panel.ts +348 -134
- package/src/panels/thinking-panel.ts +66 -32
- package/src/panels/token-budget-panel.ts +233 -46
- package/src/panels/tool-inspector-panel.ts +170 -54
- package/src/panels/types.ts +65 -4
- package/src/panels/work-plan-panel.ts +393 -39
- package/src/panels/worktree-panel.ts +239 -61
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +146 -150
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +22 -16
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +30 -11
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +8 -6
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +41 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +45 -36
- package/src/renderer/ui-primitives.ts +23 -2
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/utils/splash-lines.ts +44 -3
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -666
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -214
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/panel-picker.ts +0 -106
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -218
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/schedule-panel.ts +0 -342
- package/src/panels/watchers-panel.ts +0 -193
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
package/src/panels/docs-panel.ts
CHANGED
|
@@ -4,15 +4,13 @@
|
|
|
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';
|
|
8
|
-
import type {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
isPanelSearchPrintable,
|
|
15
|
-
} from './search-focus.ts';
|
|
7
|
+
import { buildKeyboardHints, buildPanelLine, buildPanelWorkspace, buildSearchInputLine, resolveScrollablePanelSection, extendPalette, DEFAULT_PANEL_PALETTE } from './polish.ts';
|
|
8
|
+
import type { ToolCatalogQuery } from '../runtime/ui-service-queries.ts';
|
|
9
|
+
import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers';
|
|
10
|
+
import type { KeybindingsManager } from '../input/keybindings.ts';
|
|
11
|
+
import type { PanelIntegrationContext } from './types.ts';
|
|
12
|
+
import { ToolInspectorPanel } from './tool-inspector-panel.ts';
|
|
13
|
+
import { isPanelSearchBackspace, isPanelSearchPrintable } from './search-focus.ts';
|
|
16
14
|
|
|
17
15
|
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
18
16
|
// Panel-specific domain colors with no clean shared equivalent.
|
|
@@ -21,31 +19,17 @@ const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
|
21
19
|
selected: '#00ffff',
|
|
22
20
|
});
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{ key: 'PageUp/Down', desc: 'Scroll conversation' },
|
|
36
|
-
{ key: 'Alt+PgUp/Dn', desc: 'Scroll panel' },
|
|
37
|
-
{ key: 'Tab', desc: 'Path completion / tab panels' },
|
|
38
|
-
{ key: '/', desc: 'Start slash command' },
|
|
39
|
-
{ key: 'Enter', desc: 'Submit prompt' },
|
|
40
|
-
{ key: 'Esc', desc: 'Close overlay / cancel search' },
|
|
41
|
-
{ key: 'Up/Down', desc: 'Scroll history / navigate list' },
|
|
42
|
-
{ key: 'Alt+Up/Down', desc: 'Move cursor in multi-line input' },
|
|
43
|
-
{ key: 'Ctrl+A/E', desc: 'Jump to start/end of line' },
|
|
44
|
-
{ key: 'Ctrl+W', desc: 'Delete word backward' },
|
|
45
|
-
{ key: 'Ctrl+U', desc: 'Clear line' },
|
|
46
|
-
{ key: 'F1', desc: 'Toggle help overlay' },
|
|
47
|
-
{ key: 'F2', desc: 'Toggle shortcuts overlay' },
|
|
48
|
-
];
|
|
22
|
+
/**
|
|
23
|
+
* Minimal provider-registry surface DocsPanel needs: list models, read the
|
|
24
|
+
* live active model (to mark it in the list), and switch models (Enter on a
|
|
25
|
+
* model row is a real in-panel action, not a printed signpost). Structurally
|
|
26
|
+
* satisfied by the full ProviderRegistry passed in from bootstrap.
|
|
27
|
+
*/
|
|
28
|
+
interface DocsProviderRegistry {
|
|
29
|
+
listModels(): ModelDefinition[];
|
|
30
|
+
getCurrentModel?(): ModelDefinition | undefined;
|
|
31
|
+
setCurrentModel?(registryKey: string): void;
|
|
32
|
+
}
|
|
49
33
|
|
|
50
34
|
type DocSection = 'tools' | 'models' | 'shortcuts';
|
|
51
35
|
|
|
@@ -55,6 +39,12 @@ interface FlatRow {
|
|
|
55
39
|
fg: string;
|
|
56
40
|
bg: string;
|
|
57
41
|
bold?: boolean;
|
|
42
|
+
/** Present on tool 'item' rows — the tool name Enter should filter the inspector to. */
|
|
43
|
+
toolName?: string;
|
|
44
|
+
/** Present on model 'item' rows — the model's registryKey (provider:id) for setCurrentModel. */
|
|
45
|
+
modelKey?: string;
|
|
46
|
+
/** Present on model 'item' rows — whether Enter is allowed to switch to this model. */
|
|
47
|
+
modelSelectable?: boolean;
|
|
58
48
|
}
|
|
59
49
|
|
|
60
50
|
function renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
|
|
@@ -67,18 +57,22 @@ function renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
|
|
|
67
57
|
|
|
68
58
|
export class DocsPanel extends BasePanel {
|
|
69
59
|
private toolRegistry: ToolCatalogQuery | null = null;
|
|
70
|
-
private providerRegistry:
|
|
60
|
+
private providerRegistry: DocsProviderRegistry | null = null;
|
|
61
|
+
private keybindingsManager: KeybindingsManager | null = null;
|
|
71
62
|
private section: DocSection = 'tools';
|
|
72
63
|
private searchQuery = '';
|
|
73
64
|
private searching = false;
|
|
74
65
|
private rows: FlatRow[] = [];
|
|
75
66
|
private cursorIndex = 0;
|
|
76
67
|
private scrollOffset = 0;
|
|
68
|
+
/** Set by handleInput('enter') on a tool row; consumed by handlePanelIntegrationAction, which has the PanelManager reference needed to open the sibling inspector. */
|
|
69
|
+
private _pendingToolJump: string | null = null;
|
|
77
70
|
|
|
78
|
-
constructor(toolRegistry?: ToolCatalogQuery, providerRegistry?:
|
|
71
|
+
constructor(toolRegistry?: ToolCatalogQuery, providerRegistry?: DocsProviderRegistry, keybindingsManager?: KeybindingsManager) {
|
|
79
72
|
super('docs', 'Docs', '?', 'session');
|
|
80
73
|
this.toolRegistry = toolRegistry ?? null;
|
|
81
74
|
this.providerRegistry = providerRegistry ?? null;
|
|
75
|
+
this.keybindingsManager = keybindingsManager ?? null;
|
|
82
76
|
}
|
|
83
77
|
|
|
84
78
|
override onActivate(): void {
|
|
@@ -86,17 +80,21 @@ export class DocsPanel extends BasePanel {
|
|
|
86
80
|
this._buildRows();
|
|
87
81
|
}
|
|
88
82
|
|
|
89
|
-
|
|
83
|
+
/**
|
|
84
|
+
* WO-153: converged modal '/' filter (mirrors ScrollableListPanel's
|
|
85
|
+
* _handleFilterKey). Returns `true`/`false` when consumed/ignored in
|
|
86
|
+
* filter context, or `null` to fall through to section hotkeys/navigation.
|
|
87
|
+
*/
|
|
88
|
+
private _handleSearchKey(key: string): boolean | null {
|
|
90
89
|
if (this.searching) {
|
|
91
|
-
|
|
92
|
-
if (transition === 'focus-list') {
|
|
90
|
+
if (key === 'escape') {
|
|
93
91
|
this.searching = false;
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
92
|
+
this.searchQuery = '';
|
|
93
|
+
this._buildRows();
|
|
96
94
|
return true;
|
|
97
95
|
}
|
|
98
|
-
if (
|
|
99
|
-
this.searching = false;
|
|
96
|
+
if (key === 'return' || key === 'enter') {
|
|
97
|
+
this.searching = false; // commit; keep the query applied
|
|
100
98
|
this._buildRows();
|
|
101
99
|
return true;
|
|
102
100
|
}
|
|
@@ -105,6 +103,11 @@ export class DocsPanel extends BasePanel {
|
|
|
105
103
|
this._buildRows();
|
|
106
104
|
return true;
|
|
107
105
|
}
|
|
106
|
+
// Arrow/paging keys navigate the filtered rows — fall through.
|
|
107
|
+
if (key === 'up' || key === 'down' || key === 'pageup' || key === 'pagedown') {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
// Any printable character (including section hotkeys like t/m/k) extends the query.
|
|
108
111
|
if (isPanelSearchPrintable(key)) {
|
|
109
112
|
this.searchQuery += key;
|
|
110
113
|
this._buildRows();
|
|
@@ -112,12 +115,16 @@ export class DocsPanel extends BasePanel {
|
|
|
112
115
|
}
|
|
113
116
|
return false;
|
|
114
117
|
}
|
|
115
|
-
|
|
116
|
-
const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.cursorIndex, itemCount: this.rows.length });
|
|
117
|
-
if (transition === 'focus-search') {
|
|
118
|
+
if (key === '/') {
|
|
118
119
|
this._startSearch();
|
|
119
120
|
return true;
|
|
120
121
|
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
handleInput(key: string): boolean {
|
|
126
|
+
const searchResult = this._handleSearchKey(key);
|
|
127
|
+
if (searchResult !== null) return searchResult;
|
|
121
128
|
|
|
122
129
|
switch (key) {
|
|
123
130
|
case 'up': this._move(-1); return true;
|
|
@@ -127,18 +134,54 @@ export class DocsPanel extends BasePanel {
|
|
|
127
134
|
case 't': this._setSection('tools'); return true;
|
|
128
135
|
case 'm': this._setSection('models'); return true;
|
|
129
136
|
case 'k': this._setSection('shortcuts'); return true;
|
|
137
|
+
case 'enter':
|
|
138
|
+
case 'return': return this._activateSelected();
|
|
130
139
|
default: return false;
|
|
131
140
|
}
|
|
132
141
|
}
|
|
133
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Enter on the cursor row. Tool rows can't act directly — opening the tool
|
|
145
|
+
* inspector needs the PanelManager, which only handlePanelIntegrationAction
|
|
146
|
+
* has — so this just records intent and lets that hook finish the jump.
|
|
147
|
+
* Model rows need no cross-panel access: switching the active model is a
|
|
148
|
+
* direct providerRegistry call, so it happens right here.
|
|
149
|
+
*/
|
|
150
|
+
private _activateSelected(): boolean {
|
|
151
|
+
const row = this.rows[this.cursorIndex];
|
|
152
|
+
if (!row || row.kind !== 'item') return false;
|
|
153
|
+
if (this.section === 'tools' && row.toolName) {
|
|
154
|
+
this._pendingToolJump = row.toolName;
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
if (this.section === 'models' && row.modelKey) {
|
|
158
|
+
if (row.modelSelectable && this.providerRegistry?.setCurrentModel) {
|
|
159
|
+
this.providerRegistry.setCurrentModel(row.modelKey);
|
|
160
|
+
this._buildRows();
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Cross-panel integration hook — opens ToolInspectorPanel filtered to the
|
|
169
|
+
* tool selected on Enter (session.ts:102 sibling-open pattern), consuming
|
|
170
|
+
* the intent recorded by _activateSelected.
|
|
171
|
+
*/
|
|
172
|
+
handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
173
|
+
if (!this._pendingToolJump) return false;
|
|
174
|
+
const tool = this._pendingToolJump;
|
|
175
|
+
this._pendingToolJump = null;
|
|
176
|
+
const inspector = ctx.panelManager.open('tools');
|
|
177
|
+
if (!(inspector instanceof ToolInspectorPanel)) return false;
|
|
178
|
+
inspector.filterByTool(tool);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
134
182
|
render(width: number, height: number): Line[] {
|
|
135
183
|
if (height <= 0 || width <= 0) return [];
|
|
136
184
|
const sectionLabel = this.section === 'tools' ? 'Tools' : this.section === 'models' ? 'Models' : 'Shortcuts';
|
|
137
|
-
const searchLine = this.searching
|
|
138
|
-
? ` Search: ${this.searchQuery}\u258a`
|
|
139
|
-
: this.searchQuery
|
|
140
|
-
? ` Filter: ${this.searchQuery} (/ or up at top to edit)`
|
|
141
|
-
: ` / or up at top to search`;
|
|
142
185
|
this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, Math.max(0, this.rows.length - 1)));
|
|
143
186
|
const controlsSection = {
|
|
144
187
|
title: 'Controls',
|
|
@@ -147,14 +190,31 @@ export class DocsPanel extends BasePanel {
|
|
|
147
190
|
[' t', DEFAULT_PANEL_PALETTE.info], [' tools', DEFAULT_PANEL_PALETTE.dim],
|
|
148
191
|
[' m', DEFAULT_PANEL_PALETTE.info], [' models', DEFAULT_PANEL_PALETTE.dim],
|
|
149
192
|
[' k', DEFAULT_PANEL_PALETTE.info], [' shortcuts', DEFAULT_PANEL_PALETTE.dim],
|
|
150
|
-
[' /', DEFAULT_PANEL_PALETTE.info], ['
|
|
193
|
+
[' /', DEFAULT_PANEL_PALETTE.info], [' filter', DEFAULT_PANEL_PALETTE.dim],
|
|
151
194
|
]),
|
|
152
|
-
|
|
195
|
+
this._buildFilterLine(width),
|
|
153
196
|
],
|
|
154
197
|
} as const;
|
|
198
|
+
// Context-aware footer: while filtering, surface only the keys that work in
|
|
199
|
+
// the filter field; otherwise surface section + navigation keys.
|
|
200
|
+
const footerLines = [this.searching
|
|
201
|
+
? buildKeyboardHints(width, [
|
|
202
|
+
{ keys: 'type', label: 'filter' },
|
|
203
|
+
{ keys: 'Enter', label: 'apply' },
|
|
204
|
+
{ keys: 'Esc', label: 'clear' },
|
|
205
|
+
], DEFAULT_PANEL_PALETTE)
|
|
206
|
+
: buildKeyboardHints(width, [
|
|
207
|
+
{ keys: 't/m/k', label: 'tools / models / shortcuts' },
|
|
208
|
+
{ keys: '↑/↓', label: 'navigate' },
|
|
209
|
+
...(this.section === 'tools' ? [{ keys: 'Enter', label: 'open in tool inspector' }] : []),
|
|
210
|
+
...(this.section === 'models' ? [{ keys: 'Enter', label: 'set active model' }] : []),
|
|
211
|
+
{ keys: '/', label: 'filter' },
|
|
212
|
+
], DEFAULT_PANEL_PALETTE)];
|
|
213
|
+
|
|
155
214
|
const sectionWindow = resolveScrollablePanelSection(width, height, {
|
|
156
215
|
intro: 'Browse built-in tool docs, available models, and keyboard shortcuts from one shared reference surface.',
|
|
157
216
|
palette: DEFAULT_PANEL_PALETTE,
|
|
217
|
+
footerLines,
|
|
158
218
|
beforeSections: [controlsSection],
|
|
159
219
|
section: {
|
|
160
220
|
title: sectionLabel,
|
|
@@ -176,6 +236,7 @@ export class DocsPanel extends BasePanel {
|
|
|
176
236
|
controlsSection,
|
|
177
237
|
sectionWindow.section.lines.length > 0 ? sectionWindow.section : { title: sectionLabel, lines: [buildPanelLine(width, [[' No matching docs', DEFAULT_PANEL_PALETTE.dim]])] },
|
|
178
238
|
],
|
|
239
|
+
footerLines,
|
|
179
240
|
palette: DEFAULT_PANEL_PALETTE,
|
|
180
241
|
});
|
|
181
242
|
}
|
|
@@ -193,6 +254,24 @@ export class DocsPanel extends BasePanel {
|
|
|
193
254
|
this.markDirty();
|
|
194
255
|
}
|
|
195
256
|
|
|
257
|
+
/**
|
|
258
|
+
* The filter input line — pinned rendering contract shared with
|
|
259
|
+
* ScrollableListPanel.buildFilterLine: 'Filter: ' unfocused / '[Filter] '
|
|
260
|
+
* focused, literal trailing '_' cursor while active (active:false is
|
|
261
|
+
* passed to buildSearchInputLine to suppress its block-glyph cursor
|
|
262
|
+
* substitution).
|
|
263
|
+
*/
|
|
264
|
+
private _buildFilterLine(width: number): Line {
|
|
265
|
+
const label = this.searching ? '[Filter] ' : 'Filter: ';
|
|
266
|
+
const value = this.searching ? `${this.searchQuery}_` : this.searchQuery;
|
|
267
|
+
return buildSearchInputLine(width, label, value, DEFAULT_PANEL_PALETTE, {
|
|
268
|
+
active: false,
|
|
269
|
+
bg: this.searching ? DEFAULT_PANEL_PALETTE.inputBg : DEFAULT_PANEL_PALETTE.sectionBg,
|
|
270
|
+
emptyLabel: '(/ to filter)',
|
|
271
|
+
valueColor: this.searching ? DEFAULT_PANEL_PALETTE.info : (this.searchQuery ? DEFAULT_PANEL_PALETTE.value : DEFAULT_PANEL_PALETTE.dim),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
196
275
|
private _move(delta: number): void {
|
|
197
276
|
if (this.rows.length === 0) return;
|
|
198
277
|
this.cursorIndex = Math.max(0, Math.min(this.rows.length - 1, this.cursorIndex + delta));
|
|
@@ -207,11 +286,11 @@ export class DocsPanel extends BasePanel {
|
|
|
207
286
|
const tools = this.toolRegistry?.list() ?? [];
|
|
208
287
|
const filtered = q ? tools.filter(t => t.definition.name.toLowerCase().includes(q) || (t.definition.description ?? '').toLowerCase().includes(q)) : tools;
|
|
209
288
|
if (filtered.length === 0) {
|
|
210
|
-
rows.push({ kind: 'empty', text:
|
|
289
|
+
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
290
|
} else {
|
|
212
291
|
rows.push({ kind: 'header', text: ` Tools (${filtered.length})`, fg: C.sectionFg, bg: C.sectionBg, bold: true });
|
|
213
292
|
for (const tool of filtered) {
|
|
214
|
-
rows.push({ kind: 'item', text: ` ${tool.definition.name}`, fg: C.toolFg, bg: '', bold: true });
|
|
293
|
+
rows.push({ kind: 'item', text: ` ${tool.definition.name}`, fg: C.toolFg, bg: '', bold: true, toolName: tool.definition.name });
|
|
215
294
|
if (tool.definition.description) {
|
|
216
295
|
rows.push({ kind: 'detail', text: ` ${tool.definition.description}`, fg: C.label, bg: '' });
|
|
217
296
|
}
|
|
@@ -237,7 +316,7 @@ export class DocsPanel extends BasePanel {
|
|
|
237
316
|
const models = this.providerRegistry?.listModels() ?? [];
|
|
238
317
|
const filtered = q ? models.filter(m => m.id.toLowerCase().includes(q) || m.displayName.toLowerCase().includes(q) || m.provider.toLowerCase().includes(q)) : models;
|
|
239
318
|
if (filtered.length === 0) {
|
|
240
|
-
rows.push({ kind: 'empty', text:
|
|
319
|
+
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
320
|
} else {
|
|
242
321
|
// Group by provider
|
|
243
322
|
const byProvider = new Map<string, typeof filtered>();
|
|
@@ -246,23 +325,46 @@ export class DocsPanel extends BasePanel {
|
|
|
246
325
|
if (!arr) { arr = []; byProvider.set(m.provider, arr); }
|
|
247
326
|
arr.push(m);
|
|
248
327
|
}
|
|
328
|
+
// Live active-model marker (WO-136): Enter on a selectable row calls
|
|
329
|
+
// providerRegistry.setCurrentModel, so re-derive this on every build
|
|
330
|
+
// instead of caching it — it can change without this panel's input.
|
|
331
|
+
const activeKey = this.providerRegistry?.getCurrentModel?.()?.registryKey;
|
|
249
332
|
for (const [provider, pModels] of byProvider) {
|
|
250
333
|
rows.push({ kind: 'header', text: ` ${provider} (${pModels.length})`, fg: C.sectionFg, bg: C.sectionBg, bold: true });
|
|
251
334
|
for (const m of pModels) {
|
|
252
335
|
const ctxK = m.contextWindow > 0 ? `${(m.contextWindow / 1000).toFixed(0)}k` : '?';
|
|
253
336
|
const caps = [m.contextWindow > 0 ? `ctx:${ctxK}` : ''].filter(Boolean).join(' ');
|
|
254
|
-
|
|
255
|
-
|
|
337
|
+
const isActive = m.registryKey === activeKey;
|
|
338
|
+
const activeTag = isActive ? ' ACTIVE' : '';
|
|
339
|
+
rows.push({
|
|
340
|
+
kind: 'item',
|
|
341
|
+
text: ` ${m.displayName} ${caps}${activeTag}`,
|
|
342
|
+
fg: isActive ? C.good : C.toolFg,
|
|
343
|
+
bg: '',
|
|
344
|
+
modelKey: m.registryKey,
|
|
345
|
+
modelSelectable: m.selectable,
|
|
346
|
+
});
|
|
347
|
+
rows.push({ kind: 'detail', text: ` ${m.id}${m.selectable ? '' : ' (not selectable)'}`, fg: C.label, bg: '' });
|
|
256
348
|
}
|
|
257
349
|
}
|
|
258
350
|
}
|
|
259
351
|
} else {
|
|
260
|
-
// Shortcuts
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
352
|
+
// Shortcuts — live bindings from KeybindingsManager.getAll() (WO-136),
|
|
353
|
+
// including any user overrides from ~/.goodvibes/tui/keybindings.json.
|
|
354
|
+
const km = this.keybindingsManager;
|
|
355
|
+
const shortcuts = (km?.getAll() ?? []).map((entry) => ({
|
|
356
|
+
key: entry.combos.length > 0 ? entry.combos.map((combo) => km!.formatCombo(combo)).join(', ') : '(unbound)',
|
|
357
|
+
desc: entry.description,
|
|
358
|
+
}));
|
|
359
|
+
const filtered = q ? shortcuts.filter(s => s.key.toLowerCase().includes(q) || s.desc.toLowerCase().includes(q)) : shortcuts;
|
|
360
|
+
if (shortcuts.length === 0) {
|
|
361
|
+
rows.push({ kind: 'empty', text: ' Keybindings manager not wired into this session.', fg: C.dim, bg: '' });
|
|
362
|
+
} else {
|
|
363
|
+
rows.push({ kind: 'header', text: ' Keyboard Shortcuts', fg: C.sectionFg, bg: C.sectionBg, bold: true });
|
|
364
|
+
for (const s of filtered) {
|
|
365
|
+
const key = s.key.padEnd(20);
|
|
366
|
+
rows.push({ kind: 'item', text: ` ${key} ${s.desc}`, fg: C.value, bg: '' });
|
|
367
|
+
}
|
|
266
368
|
}
|
|
267
369
|
}
|
|
268
370
|
|