@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
|
@@ -1,39 +1,66 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
3
|
+
import type { PanelIntegrationContext } from './types.ts';
|
|
3
4
|
import type { UiCommunicationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
4
5
|
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
5
6
|
import {
|
|
6
7
|
buildBodyText,
|
|
8
|
+
buildDetailBlock,
|
|
7
9
|
buildEmptyState,
|
|
8
10
|
buildGuidanceLine,
|
|
9
11
|
buildKeyValueLine,
|
|
12
|
+
buildKeyboardHints,
|
|
10
13
|
buildPanelLine,
|
|
11
14
|
buildPanelWorkspace,
|
|
15
|
+
buildStatusBadge,
|
|
12
16
|
DEFAULT_PANEL_PALETTE,
|
|
13
17
|
type PanelPalette,
|
|
18
|
+
type StatusBadgeKind,
|
|
14
19
|
} from './polish.ts';
|
|
15
20
|
import { createEmptyLine } from '../types/grid.ts';
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
headerBg: '#1e293b',
|
|
21
|
-
ok: '#22c55e',
|
|
22
|
-
warn: '#eab308',
|
|
23
|
-
error: '#ef4444',
|
|
24
|
-
selectBg: '#0f172a',
|
|
25
|
-
} as const;
|
|
22
|
+
// Base chrome only — title band, state colors, and text tokens all come
|
|
23
|
+
// straight from DEFAULT_PANEL_PALETTE (WO-002).
|
|
24
|
+
const C = DEFAULT_PANEL_PALETTE;
|
|
26
25
|
|
|
27
26
|
type CommunicationRecord = UiCommunicationSnapshot['records'][number];
|
|
28
27
|
|
|
28
|
+
function statusBadgeKind(status: CommunicationRecord['status']): StatusBadgeKind {
|
|
29
|
+
return status === 'blocked' ? 'blocked' : status === 'delivered' ? 'completed' : 'running';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fmtAgo(ts: number): string {
|
|
33
|
+
const sec = Math.max(0, Math.floor((Date.now() - ts) / 1000));
|
|
34
|
+
if (sec < 60) return `${sec}s`;
|
|
35
|
+
if (sec < 3600) return `${Math.floor(sec / 60)}m`;
|
|
36
|
+
return `${Math.floor(sec / 3600)}h`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Set by handleInput (enter/o) and consumed on the very next
|
|
40
|
+
// handlePanelIntegrationAction dispatch of that same key — handleInput has
|
|
41
|
+
// no access to the panelManager/openAgentDetail callback.
|
|
42
|
+
type PendingCommunicationAction =
|
|
43
|
+
| { readonly kind: 'open-orchestration' }
|
|
44
|
+
| { readonly kind: 'open-agent-detail'; readonly agentId: string };
|
|
45
|
+
|
|
29
46
|
export class CommunicationPanel extends ScrollableListPanel<CommunicationRecord> {
|
|
30
47
|
private readonly readModel?: UiReadModel<UiCommunicationSnapshot>;
|
|
31
48
|
private readonly unsub: (() => void) | null;
|
|
49
|
+
private readonly openAgentDetail?: (agentId: string) => void;
|
|
50
|
+
/** When true, the list is narrowed to blocked messages only (b toggles). */
|
|
51
|
+
private blockedOnly = false;
|
|
52
|
+
private pendingAction: PendingCommunicationAction | null = null;
|
|
32
53
|
|
|
33
|
-
public constructor(
|
|
34
|
-
|
|
54
|
+
public constructor(
|
|
55
|
+
readModel?: UiReadModel<UiCommunicationSnapshot>,
|
|
56
|
+
openAgentDetail?: (agentId: string) => void,
|
|
57
|
+
) {
|
|
58
|
+
super('communication', 'Communication', 'Y', 'runtime-ops');
|
|
35
59
|
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
60
|
+
this.filterEnabled = true;
|
|
61
|
+
this.filterLabel = 'Filter messages';
|
|
36
62
|
this.readModel = readModel;
|
|
63
|
+
this.openAgentDetail = openAgentDetail;
|
|
37
64
|
this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
|
|
38
65
|
}
|
|
39
66
|
|
|
@@ -47,18 +74,76 @@ export class CommunicationPanel extends ScrollableListPanel<CommunicationRecord>
|
|
|
47
74
|
|
|
48
75
|
protected getItems(): readonly CommunicationRecord[] {
|
|
49
76
|
if (!this.readModel) return [];
|
|
50
|
-
|
|
77
|
+
const records = this.readModel.getSnapshot().records;
|
|
78
|
+
return this.blockedOnly ? records.filter((r) => r.status === 'blocked') : records;
|
|
51
79
|
}
|
|
52
80
|
|
|
53
|
-
protected
|
|
81
|
+
protected override filterMatches(record: CommunicationRecord, q: string): boolean {
|
|
82
|
+
return (record.content ?? '').toLowerCase().includes(q)
|
|
83
|
+
|| record.kind.toLowerCase().includes(q)
|
|
84
|
+
|| String(record.fromId ?? '').toLowerCase().includes(q)
|
|
85
|
+
|| String(record.toId ?? '').toLowerCase().includes(q);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public override handleInput(key: string): boolean {
|
|
89
|
+
// `b` toggles the blocked-lanes-only view — guarded so it only acts when not
|
|
90
|
+
// typing into the filter input.
|
|
91
|
+
if (key === 'b' && !this.filterActive) {
|
|
92
|
+
this.blockedOnly = !this.blockedOnly;
|
|
93
|
+
this.selectedIndex = 0;
|
|
94
|
+
this.markDirty();
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
// Enter jumps to the orchestration workspace that actually produces this
|
|
98
|
+
// traffic (a direct panel jump, not a printed "/orchestration" signpost).
|
|
99
|
+
// Guarded so it doesn't hijack the filter input's commit-on-enter.
|
|
100
|
+
if ((key === 'enter' || key === 'return') && !this.filterActive) {
|
|
101
|
+
this.pendingAction = { kind: 'open-orchestration' };
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
// `o` on a blocked record opens the sender's agent detail (falling back
|
|
105
|
+
// to the receiver when the sender id is missing) via deps.openAgentDetail.
|
|
106
|
+
if (key === 'o' && !this.filterActive) {
|
|
107
|
+
const record = this.getSelectedItem();
|
|
108
|
+
if (!record || record.status !== 'blocked') return false;
|
|
109
|
+
const agentId = record.fromId || record.toId;
|
|
110
|
+
if (!agentId) return false;
|
|
111
|
+
this.pendingAction = { kind: 'open-agent-detail', agentId };
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return super.handleInput(key);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
118
|
+
if (!this.pendingAction) return false;
|
|
119
|
+
const action = this.pendingAction;
|
|
120
|
+
this.pendingAction = null;
|
|
121
|
+
if (action.kind === 'open-orchestration') {
|
|
122
|
+
ctx.panelManager.open('orchestration');
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
this.openAgentDetail?.(action.agentId);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
protected renderItem(record: CommunicationRecord, _index: number, selected: boolean, width: number): Line {
|
|
54
130
|
const bg = selected ? C.selectBg : undefined;
|
|
55
|
-
const
|
|
131
|
+
const badge = buildStatusBadge(statusBadgeKind(record.status), record.status)[0]!;
|
|
132
|
+
const route = truncateDisplay(`${record.fromId} → ${record.toId}`, 26);
|
|
133
|
+
const contentWidth = Math.max(0, width - 50);
|
|
134
|
+
// Blocked messages have no delivered content; surface the policy reason
|
|
135
|
+
// inline so the most important detail (why a lane is blocked) is visible
|
|
136
|
+
// without selecting the row.
|
|
137
|
+
const tail = record.status === 'blocked' && record.reason
|
|
138
|
+
? { text: record.reason, fg: C.warn }
|
|
139
|
+
: { text: record.content, fg: C.dim };
|
|
56
140
|
return buildPanelLine(width, [
|
|
57
141
|
[' ', C.label, bg],
|
|
58
|
-
[record.
|
|
59
|
-
[` ${
|
|
60
|
-
[
|
|
61
|
-
[
|
|
142
|
+
[`${fmtAgo(record.timestamp)} `.padStart(5), C.dim, bg],
|
|
143
|
+
[` ${badge.text} `.padEnd(12), badge.fg, bg],
|
|
144
|
+
[`${record.kind} `.padEnd(9), C.info, bg],
|
|
145
|
+
[`${route} `.padEnd(27), C.value, bg],
|
|
146
|
+
[truncateDisplay(tail.text, contentWidth), tail.fg, bg],
|
|
62
147
|
]);
|
|
63
148
|
}
|
|
64
149
|
|
|
@@ -68,8 +153,7 @@ export class CommunicationPanel extends ScrollableListPanel<CommunicationRecord>
|
|
|
68
153
|
|
|
69
154
|
protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
70
155
|
return [
|
|
71
|
-
{ command: '/orchestration', summary: 'review graphs and recursive agent activity' },
|
|
72
|
-
{ command: '/communication', summary: 'reopen this workspace once the runtime emits message traffic' },
|
|
156
|
+
{ command: '/orchestration', summary: 'review graphs and recursive agent activity that emit messages' },
|
|
73
157
|
];
|
|
74
158
|
}
|
|
75
159
|
|
|
@@ -85,7 +169,7 @@ export class CommunicationPanel extends ScrollableListPanel<CommunicationRecord>
|
|
|
85
169
|
width,
|
|
86
170
|
' Runtime store not wired into this panel yet.',
|
|
87
171
|
'This workspace needs the live runtime store before it can show communication history and policy outcomes.',
|
|
88
|
-
[{ command: '/
|
|
172
|
+
[{ command: '/orchestration', summary: 'inspect recursive routing and agent activity while this workspace is unwired' }],
|
|
89
173
|
C,
|
|
90
174
|
),
|
|
91
175
|
}],
|
|
@@ -98,56 +182,75 @@ export class CommunicationPanel extends ScrollableListPanel<CommunicationRecord>
|
|
|
98
182
|
const snapshot = this.readModel.getSnapshot();
|
|
99
183
|
const records = this.getItems();
|
|
100
184
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
185
|
+
// Posture: blocked lanes / message flow surfaced first per the panel's purpose.
|
|
186
|
+
const postureLines = (): Line[] => [
|
|
187
|
+
buildPanelLine(width, [
|
|
188
|
+
[' ', C.label],
|
|
189
|
+
...buildStatusBadge('blocked', 'blocked', { count: snapshot.totalBlocked }),
|
|
190
|
+
[' ', C.dim],
|
|
191
|
+
...buildStatusBadge('completed', 'delivered', { count: snapshot.totalDelivered }),
|
|
192
|
+
[' ', C.dim],
|
|
193
|
+
...buildStatusBadge('running', 'sent', { count: snapshot.totalSent }),
|
|
194
|
+
...(this.blockedOnly ? ([[' (blocked-only view)', C.warn]] as Array<[string, string]>) : []),
|
|
195
|
+
]),
|
|
196
|
+
snapshot.totalBlocked > 0
|
|
197
|
+
? buildPanelLine(width, [[` ${snapshot.totalBlocked} message${snapshot.totalBlocked !== 1 ? 's' : ''} blocked by routing policy — press b to isolate blocked lanes.`, C.warn]])
|
|
198
|
+
: buildPanelLine(width, [[' No blocked lanes — press Enter on a record to inspect routing in the orchestration console.', C.dim]]),
|
|
109
199
|
];
|
|
110
200
|
|
|
111
201
|
if (records.length === 0) {
|
|
112
202
|
return this.renderList(width, height, {
|
|
113
203
|
title: 'Communication Control Room',
|
|
114
|
-
header: postureLines,
|
|
204
|
+
header: postureLines(),
|
|
205
|
+
footer: [buildKeyboardHints(width, this.footerHints(false), C)],
|
|
115
206
|
});
|
|
116
207
|
}
|
|
117
208
|
|
|
118
209
|
this.clampSelection();
|
|
119
|
-
const selected =
|
|
120
|
-
|
|
121
|
-
// Update posture with selected info
|
|
122
|
-
const postureWithSelected: Line[] = [
|
|
123
|
-
buildPanelLine(width, [[' Communication posture', C.label]]),
|
|
124
|
-
buildKeyValueLine(width, [
|
|
125
|
-
{ label: 'sent', value: String(snapshot.totalSent), valueColor: snapshot.totalSent > 0 ? C.info : C.dim },
|
|
126
|
-
{ label: 'delivered', value: String(snapshot.totalDelivered), valueColor: snapshot.totalDelivered > 0 ? C.ok : C.dim },
|
|
127
|
-
{ label: 'blocked', value: String(snapshot.totalBlocked), valueColor: snapshot.totalBlocked > 0 ? C.error : C.dim },
|
|
128
|
-
{ label: 'selected', value: `${selected?.fromId ?? 'n/a'} -> ${selected?.toId ?? 'n/a'}`, valueColor: C.value },
|
|
129
|
-
], C),
|
|
130
|
-
buildGuidanceLine(width, '/orchestration', 'inspect recursive routing, message handoff, and blocked broadcast posture', C),
|
|
131
|
-
];
|
|
210
|
+
const selected = this.getSelectedItem();
|
|
132
211
|
|
|
133
|
-
const
|
|
212
|
+
const detailLines: Line[] = [];
|
|
134
213
|
if (selected) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
214
|
+
detailLines.push(...buildDetailBlock(width, `Message · ${selected.kind}`, [
|
|
215
|
+
buildKeyValueLine(width, [
|
|
216
|
+
{ label: 'status', value: selected.status, valueColor: selected.status === 'blocked' ? C.bad : C.good },
|
|
217
|
+
{ label: 'scope', value: selected.scope, valueColor: C.value },
|
|
218
|
+
{ label: 'when', value: `${fmtAgo(selected.timestamp)} ago`, valueColor: C.dim },
|
|
219
|
+
], C),
|
|
220
|
+
buildKeyValueLine(width, [
|
|
221
|
+
{ label: 'from', value: `${selected.fromId} (${selected.fromRole ?? 'unknown'})`, valueColor: C.value },
|
|
222
|
+
{ label: 'to', value: `${selected.toId} (${selected.toRole ?? 'unknown'})`, valueColor: C.value },
|
|
223
|
+
], C),
|
|
224
|
+
...(selected.reason
|
|
225
|
+
? buildBodyText(width, `blocked: ${selected.reason}`, C, C.warn)
|
|
226
|
+
: []),
|
|
227
|
+
...buildBodyText(width, selected.content, C, C.value),
|
|
228
|
+
], C));
|
|
144
229
|
}
|
|
145
|
-
footerLines.push(buildPanelLine(width, [[' Up/Down move through messages', C.dim]]));
|
|
146
230
|
|
|
147
231
|
return this.renderList(width, height, {
|
|
148
232
|
title: 'Communication Control Room',
|
|
149
|
-
header:
|
|
150
|
-
footer:
|
|
233
|
+
header: postureLines(),
|
|
234
|
+
footer: [...detailLines, buildKeyboardHints(width, this.footerHints(true, selected?.status === 'blocked'), C)],
|
|
151
235
|
});
|
|
152
236
|
}
|
|
237
|
+
|
|
238
|
+
// Context-aware hints: filter keys reflect filter state, `b` reflects the
|
|
239
|
+
// blocked-only toggle, `o` only appears on a blocked selection, and the
|
|
240
|
+
// inspect hint only appears when rows exist.
|
|
241
|
+
private footerHints(hasRows: boolean, onBlockedSelection = false): Array<{ keys: string; label: string }> {
|
|
242
|
+
const hints: Array<{ keys: string; label: string }> = [];
|
|
243
|
+
if (hasRows) hints.push({ keys: '↑/↓', label: 'select' });
|
|
244
|
+
hints.push({ keys: 'Enter', label: 'orchestration' });
|
|
245
|
+
if (onBlockedSelection) hints.push({ keys: 'o', label: 'sender detail' });
|
|
246
|
+
hints.push({ keys: 'b', label: this.blockedOnly ? 'show all lanes' : 'blocked only' });
|
|
247
|
+
if (this.filterActive) {
|
|
248
|
+
hints.push({ keys: 'Esc', label: 'clear filter' });
|
|
249
|
+
} else if (this.filterQuery) {
|
|
250
|
+
hints.push({ keys: '/', label: 'edit filter' }, { keys: 'Esc', label: 'clear filter' });
|
|
251
|
+
} else {
|
|
252
|
+
hints.push({ keys: '/', label: 'filter' });
|
|
253
|
+
}
|
|
254
|
+
return hints;
|
|
255
|
+
}
|
|
153
256
|
}
|
|
@@ -28,6 +28,12 @@ export interface ConfirmState<T = string> {
|
|
|
28
28
|
readonly subject: T;
|
|
29
29
|
/** Human-readable label for the item being destroyed. */
|
|
30
30
|
readonly label: string;
|
|
31
|
+
/**
|
|
32
|
+
* Action verb shown in the confirmation prompt (e.g. 'Cancel', 'Regenerate',
|
|
33
|
+
* 'Promote'). Defaults to 'Delete' so existing destructive confirms are
|
|
34
|
+
* unaffected; non-destructive confirms should set this for honest copy.
|
|
35
|
+
*/
|
|
36
|
+
readonly verb?: string;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
/**
|
|
@@ -61,9 +67,10 @@ export function handleConfirmInput<T = string>(
|
|
|
61
67
|
*/
|
|
62
68
|
export function renderConfirmLines<T = string>(width: number, state: ConfirmState<T>): Line[] {
|
|
63
69
|
const palette = DEFAULT_PANEL_PALETTE;
|
|
70
|
+
const verb = state.verb ?? 'Delete';
|
|
64
71
|
return [
|
|
65
72
|
buildPanelLine(width, [[
|
|
66
|
-
`
|
|
73
|
+
` ${verb} "${state.label}"?`,
|
|
67
74
|
palette.warn,
|
|
68
75
|
]]),
|
|
69
76
|
buildPanelLine(width, [
|