@pellux/goodvibes-tui 0.28.0 → 1.0.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 +133 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/conversation-line-cache.ts +432 -0
- package/src/core/conversation-rendering.ts +11 -3
- package/src/core/conversation.ts +50 -4
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-content-actions.ts +8 -3
- package/src/input/handler-feed-routes.ts +12 -35
- package/src/input/handler-feed.ts +4 -8
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler.ts +43 -6
- package/src/input/input-history.ts +17 -4
- package/src/input/keybindings.ts +48 -8
- package/src/input/panel-integration-actions.ts +107 -1
- package/src/main.ts +19 -11
- package/src/panels/agent-inspector-panel.ts +125 -36
- package/src/panels/agent-inspector-shared.ts +144 -0
- package/src/panels/approval-panel.ts +67 -16
- package/src/panels/automation-control-panel.ts +368 -124
- package/src/panels/base-panel.ts +1 -1
- 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 +232 -73
- package/src/panels/communication-panel.ts +58 -20
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +351 -118
- package/src/panels/cost-tracker-panel.ts +165 -7
- package/src/panels/debug-panel.ts +312 -159
- package/src/panels/diff-panel.ts +209 -57
- package/src/panels/docs-panel.ts +152 -66
- package/src/panels/eval-panel.ts +220 -42
- package/src/panels/file-explorer-panel.ts +202 -122
- package/src/panels/file-preview-panel.ts +132 -57
- package/src/panels/git-panel.ts +310 -129
- package/src/panels/hooks-panel.ts +150 -22
- package/src/panels/incident-review-panel.ts +223 -35
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +212 -86
- package/src/panels/knowledge-graph-panel.ts +461 -101
- package/src/panels/local-auth-panel.ts +240 -28
- package/src/panels/marketplace-panel.ts +193 -26
- package/src/panels/memory-panel.ts +78 -77
- package/src/panels/ops-control-panel.ts +146 -29
- package/src/panels/ops-strategy-panel.ts +72 -4
- package/src/panels/orchestration-panel.ts +231 -69
- package/src/panels/panel-list-panel.ts +154 -131
- package/src/panels/panel-manager.ts +97 -9
- package/src/panels/plan-dashboard-panel.ts +333 -17
- package/src/panels/plugins-panel.ts +184 -29
- package/src/panels/policy-panel.ts +210 -38
- package/src/panels/polish-core.ts +7 -2
- package/src/panels/polish.ts +23 -4
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +62 -113
- package/src/panels/provider-health-panel.ts +434 -518
- 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 +116 -30
- package/src/panels/remote-panel.ts +114 -36
- package/src/panels/routes-panel.ts +63 -22
- package/src/panels/sandbox-panel.ts +174 -42
- package/src/panels/scrollable-list-panel.ts +19 -135
- package/src/panels/security-panel.ts +133 -33
- package/src/panels/services-panel.ts +116 -64
- package/src/panels/session-browser-panel.ts +73 -2
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +335 -72
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +86 -33
- package/src/panels/symbol-outline-panel.ts +248 -108
- package/src/panels/system-messages-panel.ts +114 -13
- package/src/panels/tasks-panel.ts +326 -139
- package/src/panels/thinking-panel.ts +43 -10
- package/src/panels/token-budget-panel.ts +194 -18
- package/src/panels/tool-inspector-panel.ts +144 -34
- package/src/panels/types.ts +40 -4
- package/src/panels/work-plan-panel.ts +280 -17
- package/src/panels/worktree-panel.ts +175 -42
- package/src/panels/wrfc-panel.ts +116 -24
- package/src/renderer/agent-detail-modal.ts +37 -8
- package/src/renderer/code-block.ts +58 -28
- package/src/renderer/context-inspector.ts +3 -6
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/conversation-surface.ts +1 -21
- package/src/renderer/diff-view.ts +6 -4
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +23 -17
- package/src/renderer/fullscreen-workspace.ts +2 -7
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/history-search-overlay.ts +10 -16
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/markdown.ts +9 -1
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +8 -485
- package/src/renderer/model-workspace.ts +9 -24
- package/src/renderer/overlay-box.ts +23 -19
- package/src/renderer/process-indicator.ts +15 -25
- package/src/renderer/profile-picker-modal.ts +13 -14
- package/src/renderer/prompt-content-width.ts +16 -0
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/selection-modal-overlay.ts +3 -1
- package/src/renderer/semantic-diff.ts +1 -1
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal-helpers.ts +10 -34
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +54 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/surface-layout.ts +0 -12
- package/src/renderer/term-caps.ts +1 -1
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/tool-call.ts +6 -20
- package/src/renderer/ui-factory.ts +48 -37
- package/src/renderer/ui-primitives.ts +41 -3
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/render-scheduler.ts +80 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +2 -2
- package/src/utils/splash-lines.ts +54 -5
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -635
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -238
- package/src/panels/forensics-panel.ts +0 -378
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -255
- package/src/panels/provider-stats-panel.ts +0 -391
- package/src/panels/schedule-panel.ts +0 -365
- package/src/panels/watchers-panel.ts +0 -213
- package/src/renderer/file-tree.ts +0 -153
- package/src/renderer/progress.ts +0 -99
- package/src/renderer/status-token.ts +0 -67
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -3,7 +3,6 @@ import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
|
3
3
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
4
|
import {
|
|
5
5
|
buildDetailBlock,
|
|
6
|
-
buildGuidanceLine,
|
|
7
6
|
buildKeyboardHints,
|
|
8
7
|
buildKeyValueLine,
|
|
9
8
|
buildMeterLine,
|
|
@@ -16,6 +15,12 @@ import {
|
|
|
16
15
|
} from './polish.ts';
|
|
17
16
|
import type { WorkPlanItem, WorkPlanItemStatus, WorkPlanStore } from '../work-plans/work-plan-store.ts';
|
|
18
17
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
18
|
+
import { isTextBackspace } from '../input/delete-key-policy.ts';
|
|
19
|
+
import { AgentInspectorPanel } from './agent-inspector-panel.ts';
|
|
20
|
+
import { WrfcPanel } from './wrfc-panel.ts';
|
|
21
|
+
import type { PanelIntegrationContext } from './types.ts';
|
|
22
|
+
|
|
23
|
+
type WorkPlanDraftField = 'title' | 'owner' | 'notes';
|
|
19
24
|
|
|
20
25
|
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
21
26
|
pending: '#94a3b8',
|
|
@@ -59,9 +64,21 @@ function statusName(status: WorkPlanItemStatus): string {
|
|
|
59
64
|
export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
60
65
|
private items: readonly WorkPlanItem[] = [];
|
|
61
66
|
private lastPlanUpdatedAt = 0;
|
|
67
|
+
private lastStatus: string | null = null;
|
|
68
|
+
|
|
69
|
+
// Draft-input state for 'a' (add) / 'e' (edit): a small modal-like field
|
|
70
|
+
// editor inline in the header, following the same draft-buffer pattern as
|
|
71
|
+
// ProjectPlanningPanel's typed-answer draft (Tab cycles fields, Enter
|
|
72
|
+
// saves, Esc cancels, Backspace edits the active field).
|
|
73
|
+
private draftMode: 'add' | 'edit' | null = null;
|
|
74
|
+
private draftField: WorkPlanDraftField = 'title';
|
|
75
|
+
private draftTitle = '';
|
|
76
|
+
private draftOwner = '';
|
|
77
|
+
private draftNotes = '';
|
|
78
|
+
private draftEditingItemId: string | null = null;
|
|
62
79
|
|
|
63
80
|
constructor(private readonly store: WorkPlanStore) {
|
|
64
|
-
super('work-plan', 'Work Plan', '
|
|
81
|
+
super('work-plan', 'Work Plan', '◧', 'agent');
|
|
65
82
|
this.showSelectionGutter = true;
|
|
66
83
|
}
|
|
67
84
|
|
|
@@ -85,7 +102,12 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
85
102
|
|
|
86
103
|
handleInput(key: string): boolean {
|
|
87
104
|
if (this.lastError !== null) this.clearError();
|
|
88
|
-
|
|
105
|
+
|
|
106
|
+
if (this.draftMode) {
|
|
107
|
+
return this.handleDraftInput(key);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const item = this.getSelectedItem();
|
|
89
111
|
try {
|
|
90
112
|
switch (key) {
|
|
91
113
|
case ' ':
|
|
@@ -120,6 +142,24 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
120
142
|
case 'r':
|
|
121
143
|
this.refresh(true);
|
|
122
144
|
return true;
|
|
145
|
+
case 'a':
|
|
146
|
+
this.beginAddDraft();
|
|
147
|
+
return true;
|
|
148
|
+
case 'e':
|
|
149
|
+
if (!item) return false;
|
|
150
|
+
this.beginEditDraft(item);
|
|
151
|
+
return true;
|
|
152
|
+
case 'x':
|
|
153
|
+
this.exportMarkdown();
|
|
154
|
+
return true;
|
|
155
|
+
// 'i'/'w' jump to the selected item's linked agent/WRFC chain. The
|
|
156
|
+
// actual navigation happens in handlePanelIntegrationAction (it
|
|
157
|
+
// needs the PanelManager); consuming the key here just requires the
|
|
158
|
+
// linked target to exist so the router below fires next.
|
|
159
|
+
case 'i':
|
|
160
|
+
return item?.linked?.agentId !== undefined;
|
|
161
|
+
case 'w':
|
|
162
|
+
return item?.linked?.wrfcId !== undefined;
|
|
123
163
|
default:
|
|
124
164
|
return super.handleInput(key);
|
|
125
165
|
}
|
|
@@ -129,6 +169,165 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
129
169
|
}
|
|
130
170
|
}
|
|
131
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Cross-panel jumps: 'i' opens the Inspector focused on the selected
|
|
174
|
+
* item's linked agent; 'w' opens the WRFC panel focused on its linked
|
|
175
|
+
* chain.
|
|
176
|
+
*/
|
|
177
|
+
handlePanelIntegrationAction(key: string, ctx: PanelIntegrationContext): boolean {
|
|
178
|
+
const item = this.getSelectedItem();
|
|
179
|
+
if (!item) return false;
|
|
180
|
+
if (key === 'i') {
|
|
181
|
+
const agentId = item.linked?.agentId;
|
|
182
|
+
if (!agentId) return false;
|
|
183
|
+
const inspector = ctx.panelManager.open('inspector');
|
|
184
|
+
if (inspector instanceof AgentInspectorPanel) {
|
|
185
|
+
inspector.inspectAgent(agentId);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (key === 'w') {
|
|
191
|
+
const wrfcId = item.linked?.wrfcId;
|
|
192
|
+
if (!wrfcId) return false;
|
|
193
|
+
const wrfc = ctx.panelManager.open('wrfc');
|
|
194
|
+
if (wrfc instanceof WrfcPanel) {
|
|
195
|
+
wrfc.selectChain(wrfcId);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// -------------------------------------------------------------------------
|
|
204
|
+
// Draft-input (add/edit)
|
|
205
|
+
// -------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
private handleDraftInput(key: string): boolean {
|
|
208
|
+
if (key === 'escape') {
|
|
209
|
+
this.cancelDraft();
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
if (key === 'tab') {
|
|
213
|
+
this.cycleDraftField();
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
if (key === 'enter' || key === 'return') {
|
|
217
|
+
this.commitDraft();
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
if (isTextBackspace(key)) {
|
|
221
|
+
this.setDraftFieldValue(this.getDraftFieldValue().slice(0, -1));
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (key === 'space') {
|
|
225
|
+
this.setDraftFieldValue(`${this.getDraftFieldValue()} `);
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
if (this.isPrintableKey(key)) {
|
|
229
|
+
this.setDraftFieldValue(`${this.getDraftFieldValue()}${key}`);
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
// Absorb everything else (up/down/pageup/etc.) while drafting.
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private beginAddDraft(): void {
|
|
237
|
+
this.draftMode = 'add';
|
|
238
|
+
this.draftField = 'title';
|
|
239
|
+
this.draftTitle = '';
|
|
240
|
+
this.draftOwner = '';
|
|
241
|
+
this.draftNotes = '';
|
|
242
|
+
this.draftEditingItemId = null;
|
|
243
|
+
this.lastStatus = null;
|
|
244
|
+
this.needsRender = true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private beginEditDraft(item: WorkPlanItem): void {
|
|
248
|
+
this.draftMode = 'edit';
|
|
249
|
+
this.draftField = 'title';
|
|
250
|
+
this.draftTitle = item.title;
|
|
251
|
+
this.draftOwner = item.owner ?? '';
|
|
252
|
+
this.draftNotes = item.notes ?? '';
|
|
253
|
+
this.draftEditingItemId = item.id;
|
|
254
|
+
this.lastStatus = null;
|
|
255
|
+
this.needsRender = true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private cancelDraft(): void {
|
|
259
|
+
this.draftMode = null;
|
|
260
|
+
this.draftEditingItemId = null;
|
|
261
|
+
this.needsRender = true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private cycleDraftField(): void {
|
|
265
|
+
const order: WorkPlanDraftField[] = ['title', 'owner', 'notes'];
|
|
266
|
+
const next = order[(order.indexOf(this.draftField) + 1) % order.length];
|
|
267
|
+
if (next) this.draftField = next;
|
|
268
|
+
this.needsRender = true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private getDraftFieldValue(): string {
|
|
272
|
+
switch (this.draftField) {
|
|
273
|
+
case 'title': return this.draftTitle;
|
|
274
|
+
case 'owner': return this.draftOwner;
|
|
275
|
+
case 'notes': return this.draftNotes;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private setDraftFieldValue(value: string): void {
|
|
280
|
+
switch (this.draftField) {
|
|
281
|
+
case 'title': this.draftTitle = value; break;
|
|
282
|
+
case 'owner': this.draftOwner = value; break;
|
|
283
|
+
case 'notes': this.draftNotes = value; break;
|
|
284
|
+
}
|
|
285
|
+
this.needsRender = true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private commitDraft(): void {
|
|
289
|
+
const title = this.draftTitle.trim();
|
|
290
|
+
if (!title) {
|
|
291
|
+
this.setError('Work plan item title is required.');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
if (this.draftMode === 'add') {
|
|
296
|
+
this.store.addItem(title, {
|
|
297
|
+
owner: this.draftOwner.trim() || undefined,
|
|
298
|
+
notes: this.draftNotes.trim() || undefined,
|
|
299
|
+
source: 'tui-panel',
|
|
300
|
+
});
|
|
301
|
+
this.lastStatus = 'Item added.';
|
|
302
|
+
} else if (this.draftMode === 'edit' && this.draftEditingItemId) {
|
|
303
|
+
this.store.updateItem(this.draftEditingItemId, {
|
|
304
|
+
title,
|
|
305
|
+
owner: this.draftOwner.trim() || null,
|
|
306
|
+
notes: this.draftNotes.trim() || null,
|
|
307
|
+
});
|
|
308
|
+
this.lastStatus = 'Item updated.';
|
|
309
|
+
}
|
|
310
|
+
this.draftMode = null;
|
|
311
|
+
this.draftEditingItemId = null;
|
|
312
|
+
this.refresh(true);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
this.setError(summarizeError(error));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private isPrintableKey(key: string): boolean {
|
|
319
|
+
return key.length === 1 && key >= ' ';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private exportMarkdown(): void {
|
|
323
|
+
try {
|
|
324
|
+
const { path } = this.store.exportMarkdown();
|
|
325
|
+
this.lastStatus = `Exported to ${path}`;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
this.setError(summarizeError(error));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
132
331
|
protected getItems(): readonly WorkPlanItem[] {
|
|
133
332
|
return this.items;
|
|
134
333
|
}
|
|
@@ -138,8 +337,10 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
138
337
|
}
|
|
139
338
|
|
|
140
339
|
protected getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
340
|
+
// WO-160: '/work-plan add <title>' dropped — 'a' already opens an
|
|
341
|
+
// in-panel add-item draft even from this empty state (see
|
|
342
|
+
// beginAddDraft), so the printed command was a pure action substitute.
|
|
141
343
|
return [
|
|
142
|
-
{ command: '/work-plan add <title>', summary: 'add a persistent item that survives across sessions' },
|
|
143
344
|
{ command: '/work-plan list', summary: 'print the current plan to the shell' },
|
|
144
345
|
];
|
|
145
346
|
}
|
|
@@ -159,7 +360,7 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
159
360
|
}
|
|
160
361
|
|
|
161
362
|
private setSelectedStatus(status: WorkPlanItemStatus): boolean {
|
|
162
|
-
const item = this.
|
|
363
|
+
const item = this.getSelectedItem();
|
|
163
364
|
if (!item) return false;
|
|
164
365
|
this.store.setItemStatus(item.id, status);
|
|
165
366
|
this.refresh();
|
|
@@ -168,7 +369,10 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
168
369
|
|
|
169
370
|
private refresh(force = false): void {
|
|
170
371
|
const plan = this.store.getActivePlan();
|
|
171
|
-
|
|
372
|
+
// updatedAt is bumped by every store mutation (addItem/updateItem/
|
|
373
|
+
// removeItem/clearCompleted), so it alone is a sufficient staleness
|
|
374
|
+
// check — no need to also compare item counts.
|
|
375
|
+
if (!force && plan.updatedAt === this.lastPlanUpdatedAt) return;
|
|
172
376
|
this.items = plan.items;
|
|
173
377
|
this.lastPlanUpdatedAt = plan.updatedAt;
|
|
174
378
|
this.clampSelection();
|
|
@@ -176,6 +380,7 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
176
380
|
}
|
|
177
381
|
|
|
178
382
|
private renderHeader(width: number): Line[] {
|
|
383
|
+
if (this.draftMode) return this.renderDraftForm(width);
|
|
179
384
|
const plan = this.store.getActivePlan();
|
|
180
385
|
const counts = new Map<WorkPlanItemStatus, number>();
|
|
181
386
|
for (const status of STATUS_ORDER) counts.set(status, 0);
|
|
@@ -202,12 +407,15 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
202
407
|
{ label: 'blocked', value: String(counts.get('blocked') ?? 0), valueColor: (counts.get('blocked') ?? 0) > 0 ? C.blocked : C.dim },
|
|
203
408
|
{ label: 'done', value: String(counts.get('done') ?? 0), valueColor: (counts.get('done') ?? 0) > 0 ? C.done : C.dim },
|
|
204
409
|
], C),
|
|
205
|
-
|
|
410
|
+
// WO-160: dropped the printed '/work-plan add <title>' guidance line —
|
|
411
|
+
// 'a' already opens an in-panel add-item draft (see beginAddDraft /
|
|
412
|
+
// handleDraftInput) and is advertised in the footer's 'a: add' hint,
|
|
413
|
+
// so the printed command was a pure action substitute.
|
|
206
414
|
];
|
|
207
415
|
|
|
208
416
|
const header: Line[] = buildSummaryBlock(width, 'Persistent Work Plan', postureLines, C);
|
|
209
417
|
|
|
210
|
-
const active = this.
|
|
418
|
+
const active = this.getSelectedItem();
|
|
211
419
|
if (active) {
|
|
212
420
|
const detailRows: Line[] = [
|
|
213
421
|
buildPanelLine(width, [
|
|
@@ -231,29 +439,80 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
231
439
|
[truncateDisplay(active.notes, Math.max(8, width - 8)), C.value],
|
|
232
440
|
]));
|
|
233
441
|
}
|
|
442
|
+
const linkedSegments = this.buildLinkedSegments(active);
|
|
443
|
+
if (linkedSegments.length > 0) {
|
|
444
|
+
detailRows.push(buildPanelLine(width, [[' linked ', C.label], ...linkedSegments]));
|
|
445
|
+
}
|
|
234
446
|
header.push(...buildDetailBlock(width, 'Selected item', detailRows, C));
|
|
235
447
|
}
|
|
236
448
|
return header;
|
|
237
449
|
}
|
|
238
450
|
|
|
451
|
+
/** Renders item.linked (agentId/wrfcId/taskId/sessionId) with their jump keys. */
|
|
452
|
+
private buildLinkedSegments(item: WorkPlanItem): Array<[string, string]> {
|
|
453
|
+
const linked = item.linked;
|
|
454
|
+
if (!linked) return [];
|
|
455
|
+
const segments: Array<[string, string]> = [];
|
|
456
|
+
if (linked.agentId) segments.push([`agent:${linked.agentId} (i) `, C.info]);
|
|
457
|
+
if (linked.wrfcId) segments.push([`wrfc:${linked.wrfcId} (w) `, C.info]);
|
|
458
|
+
if (linked.taskId) segments.push([`task:${linked.taskId} `, C.dim]);
|
|
459
|
+
if (linked.sessionId) segments.push([`session:${linked.sessionId} `, C.dim]);
|
|
460
|
+
return segments;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private renderDraftForm(width: number): Line[] {
|
|
464
|
+
const title = this.draftMode === 'add' ? 'Add Work Plan Item' : 'Edit Work Plan Item';
|
|
465
|
+
const field = (label: string, value: string, key: WorkPlanDraftField): Line => {
|
|
466
|
+
const active = key === this.draftField;
|
|
467
|
+
const text = active ? `${value}▏` : (value || '(empty)');
|
|
468
|
+
return buildPanelLine(width, [
|
|
469
|
+
[` ${label.padEnd(6)} `, C.label],
|
|
470
|
+
[truncateDisplay(text, Math.max(8, width - 10)), active ? C.value : C.dim],
|
|
471
|
+
]);
|
|
472
|
+
};
|
|
473
|
+
return buildSummaryBlock(width, title, [
|
|
474
|
+
buildPanelLine(width, [[' Tab next field Enter save Esc cancel', C.dim]]),
|
|
475
|
+
field('Title', this.draftTitle, 'title'),
|
|
476
|
+
field('Owner', this.draftOwner, 'owner'),
|
|
477
|
+
field('Notes', this.draftNotes, 'notes'),
|
|
478
|
+
], C);
|
|
479
|
+
}
|
|
480
|
+
|
|
239
481
|
private renderFooter(width: number): Line[] {
|
|
482
|
+
if (this.draftMode) {
|
|
483
|
+
return [
|
|
484
|
+
buildKeyboardHints(width, [
|
|
485
|
+
{ keys: 'Tab', label: 'next field' },
|
|
486
|
+
{ keys: 'Enter', label: 'save' },
|
|
487
|
+
{ keys: 'Esc', label: 'cancel' },
|
|
488
|
+
], C),
|
|
489
|
+
];
|
|
490
|
+
}
|
|
240
491
|
const hasItem = this.items.length > 0;
|
|
241
492
|
if (!hasItem) {
|
|
242
493
|
return [
|
|
243
494
|
buildKeyboardHints(width, [
|
|
244
495
|
{ keys: '↑/↓', label: 'navigate' },
|
|
496
|
+
{ keys: 'a', label: 'add' },
|
|
245
497
|
], C),
|
|
246
498
|
];
|
|
247
499
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
500
|
+
const active = this.getSelectedItem();
|
|
501
|
+
const hints: Array<{ keys: string; label: string }> = [
|
|
502
|
+
{ keys: this.items.length > 0 ? `${this.selectedIndex + 1}/${this.items.length}` : '0/0', label: 'item' },
|
|
503
|
+
{ keys: 'Enter', label: 'cycle status' },
|
|
504
|
+
{ keys: '1-6', label: 'set status' },
|
|
505
|
+
{ keys: 'a', label: 'add' },
|
|
506
|
+
{ keys: 'e', label: 'edit' },
|
|
507
|
+
{ keys: 'd', label: 'delete' },
|
|
508
|
+
{ keys: 'c', label: 'clear done' },
|
|
509
|
+
{ keys: 'x', label: 'export' },
|
|
510
|
+
{ keys: 'r', label: 'refresh' },
|
|
511
|
+
];
|
|
512
|
+
if (active?.linked?.agentId) hints.push({ keys: 'i', label: 'jump agent' });
|
|
513
|
+
if (active?.linked?.wrfcId) hints.push({ keys: 'w', label: 'jump wrfc' });
|
|
514
|
+
const lines: Line[] = [
|
|
515
|
+
buildKeyboardHints(width, hints, C),
|
|
257
516
|
buildPanelLine(width, [
|
|
258
517
|
[' 1', C.info], [' pending ', C.dim],
|
|
259
518
|
['2', C.info], [' active ', C.dim],
|
|
@@ -263,5 +522,9 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
263
522
|
['6', C.info], [' cancelled', C.dim],
|
|
264
523
|
]),
|
|
265
524
|
];
|
|
525
|
+
if (this.lastStatus) {
|
|
526
|
+
lines.push(buildPanelLine(width, [[` ${this.lastStatus}`, C.good]]));
|
|
527
|
+
}
|
|
528
|
+
return lines;
|
|
266
529
|
}
|
|
267
530
|
}
|