@pellux/goodvibes-tui 1.0.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CHANGELOG.md +130 -127
  2. package/README.md +32 -14
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/cli/ensure-goodvibes-gitignore.ts +32 -0
  6. package/src/cli/entrypoint.ts +2 -0
  7. package/src/core/alert-gating.ts +67 -0
  8. package/src/core/approval-alert.ts +72 -0
  9. package/src/core/budget-breach-notifier.ts +106 -0
  10. package/src/core/conversation-rendering.ts +19 -0
  11. package/src/core/conversation.ts +58 -0
  12. package/src/core/focus-tracker.ts +41 -0
  13. package/src/core/long-task-notifier.ts +33 -6
  14. package/src/core/stream-event-wiring.ts +104 -2
  15. package/src/core/stream-stall-watchdog.ts +41 -17
  16. package/src/core/system-message-router.ts +37 -51
  17. package/src/core/turn-cancellation.ts +20 -0
  18. package/src/core/turn-event-wiring.ts +64 -3
  19. package/src/export/cost-utils.ts +126 -8
  20. package/src/input/command-registry.ts +54 -1
  21. package/src/input/commands/checkpoint-runtime.ts +280 -0
  22. package/src/input/commands/codebase-runtime.ts +192 -0
  23. package/src/input/commands/diff-runtime.ts +61 -30
  24. package/src/input/commands/eval.ts +1 -1
  25. package/src/input/commands/health-runtime.ts +1 -1
  26. package/src/input/commands/image-runtime.ts +112 -0
  27. package/src/input/commands/intelligence-runtime.ts +11 -1
  28. package/src/input/commands/local-auth-runtime.ts +4 -1
  29. package/src/input/commands/local-runtime.ts +9 -3
  30. package/src/input/commands/marketplace-runtime.ts +2 -2
  31. package/src/input/commands/memory.ts +6 -1
  32. package/src/input/commands/operator-panel-runtime.ts +40 -24
  33. package/src/input/commands/planning-runtime.ts +26 -3
  34. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  35. package/src/input/commands/plugin-runtime.ts +2 -2
  36. package/src/input/commands/policy-dispatch.ts +21 -0
  37. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  38. package/src/input/commands/qrcode-runtime.ts +3 -3
  39. package/src/input/commands/recall-review.ts +35 -0
  40. package/src/input/commands/remote-runtime.ts +3 -3
  41. package/src/input/commands/runtime-services.ts +54 -13
  42. package/src/input/commands/services-runtime.ts +1 -1
  43. package/src/input/commands/session-content.ts +12 -0
  44. package/src/input/commands/session-workflow.ts +19 -1
  45. package/src/input/commands/settings-sync-runtime.ts +1 -1
  46. package/src/input/commands/share-runtime.ts +9 -2
  47. package/src/input/commands/shell-core.ts +15 -7
  48. package/src/input/commands/skills-runtime.ts +2 -8
  49. package/src/input/commands/subscription-runtime.ts +2 -2
  50. package/src/input/commands/test-runtime.ts +277 -0
  51. package/src/input/commands/websearch-runtime.ts +101 -0
  52. package/src/input/commands/work-plan-runtime.ts +36 -10
  53. package/src/input/commands/workstream-runtime.ts +338 -0
  54. package/src/input/commands.ts +12 -0
  55. package/src/input/config-modal-types.ts +161 -0
  56. package/src/input/config-modal.ts +377 -0
  57. package/src/input/feed-context-factory.ts +7 -8
  58. package/src/input/handler-command-route.ts +27 -17
  59. package/src/input/handler-content-actions.ts +22 -8
  60. package/src/input/handler-feed-routes.ts +107 -7
  61. package/src/input/handler-feed.ts +49 -13
  62. package/src/input/handler-interactions.ts +3 -3
  63. package/src/input/handler-modal-routes.ts +65 -0
  64. package/src/input/handler-modal-stack.ts +3 -5
  65. package/src/input/handler-modal-token-routes.ts +16 -49
  66. package/src/input/handler-picker-routes.ts +11 -94
  67. package/src/input/handler-shortcuts.ts +24 -14
  68. package/src/input/handler-types.ts +3 -6
  69. package/src/input/handler-ui-state.ts +10 -22
  70. package/src/input/handler.ts +10 -28
  71. package/src/input/keybindings.ts +22 -8
  72. package/src/input/model-picker-types.ts +24 -6
  73. package/src/input/model-picker.ts +58 -0
  74. package/src/input/panel-integration-actions.ts +9 -170
  75. package/src/input/settings-modal-data.ts +95 -0
  76. package/src/input/settings-modal-mutations.ts +6 -4
  77. package/src/main.ts +41 -44
  78. package/src/panels/agent-inspector-shared.ts +35 -11
  79. package/src/panels/base-panel.ts +22 -1
  80. package/src/panels/builtin/agent.ts +21 -107
  81. package/src/panels/builtin/development.ts +16 -62
  82. package/src/panels/builtin/knowledge.ts +8 -32
  83. package/src/panels/builtin/operations.ts +84 -428
  84. package/src/panels/builtin/session.ts +21 -112
  85. package/src/panels/builtin/shared.ts +9 -43
  86. package/src/panels/builtin-modals.ts +218 -0
  87. package/src/panels/builtin-panels.ts +5 -0
  88. package/src/panels/cost-tracker-panel.ts +63 -14
  89. package/src/panels/diff-panel.ts +123 -60
  90. package/src/panels/eval-registry.ts +60 -0
  91. package/src/panels/fleet-panel.ts +800 -0
  92. package/src/panels/fleet-read-model.ts +477 -0
  93. package/src/panels/fleet-steer.ts +92 -0
  94. package/src/panels/fleet-stop.ts +125 -0
  95. package/src/panels/fleet-tabs.ts +230 -0
  96. package/src/panels/fleet-transcript.ts +356 -0
  97. package/src/panels/git-panel.ts +9 -9
  98. package/src/panels/index.ts +7 -31
  99. package/src/panels/local-auth-panel.ts +10 -0
  100. package/src/panels/modals/hooks-modal.ts +187 -0
  101. package/src/panels/modals/keybindings-modal.ts +151 -0
  102. package/src/panels/modals/knowledge-modal.ts +158 -0
  103. package/src/panels/modals/local-auth-modal.ts +132 -0
  104. package/src/panels/modals/marketplace-modal.ts +218 -0
  105. package/src/panels/modals/memory-modal.ts +180 -0
  106. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  107. package/src/panels/modals/modal-theme.ts +36 -0
  108. package/src/panels/modals/pairing-modal.ts +144 -0
  109. package/src/panels/modals/planning-modal.ts +282 -0
  110. package/src/panels/modals/plugins-modal.ts +174 -0
  111. package/src/panels/modals/policy-modal.ts +256 -0
  112. package/src/panels/modals/provider-health-modal.ts +136 -0
  113. package/src/panels/modals/remote-modal.ts +115 -0
  114. package/src/panels/modals/sandbox-modal.ts +150 -0
  115. package/src/panels/modals/security-modal.ts +217 -0
  116. package/src/panels/modals/services-modal.ts +179 -0
  117. package/src/panels/modals/settings-sync-modal.ts +142 -0
  118. package/src/panels/modals/skills-modal.ts +189 -0
  119. package/src/panels/modals/subscription-modal.ts +172 -0
  120. package/src/panels/modals/work-plan-modal.ts +149 -0
  121. package/src/panels/panel-confirm-overlay.ts +72 -0
  122. package/src/panels/panel-manager.ts +108 -5
  123. package/src/panels/project-planning-answer-actions.ts +4 -2
  124. package/src/panels/scrollable-list-panel.ts +9 -0
  125. package/src/panels/skills-panel.ts +7 -51
  126. package/src/panels/token-budget-panel.ts +5 -3
  127. package/src/panels/types.ts +26 -0
  128. package/src/permissions/hunk-selection.ts +179 -0
  129. package/src/permissions/prompt.ts +60 -4
  130. package/src/renderer/compaction-history-modal.ts +19 -3
  131. package/src/renderer/compaction-preview.ts +13 -2
  132. package/src/renderer/compaction-quality.ts +100 -0
  133. package/src/renderer/config-modal.ts +81 -0
  134. package/src/renderer/conversation-overlays.ts +10 -17
  135. package/src/renderer/fleet-tab-strip.ts +56 -0
  136. package/src/renderer/footer-tips.ts +10 -2
  137. package/src/renderer/git-status.ts +40 -0
  138. package/src/renderer/help-overlay.ts +2 -2
  139. package/src/renderer/model-workspace.ts +44 -1
  140. package/src/renderer/panel-workspace-bar.ts +8 -2
  141. package/src/renderer/shell-surface.ts +8 -1
  142. package/src/renderer/turn-injection.ts +114 -0
  143. package/src/renderer/ui-factory.ts +91 -7
  144. package/src/runtime/bootstrap-command-context.ts +24 -1
  145. package/src/runtime/bootstrap-command-parts.ts +36 -7
  146. package/src/runtime/bootstrap-core.ts +19 -4
  147. package/src/runtime/bootstrap-hook-bridge.ts +5 -0
  148. package/src/runtime/bootstrap-shell.ts +41 -17
  149. package/src/runtime/bootstrap.ts +11 -17
  150. package/src/runtime/code-index-services.ts +112 -0
  151. package/src/runtime/orchestrator-core-services.ts +49 -0
  152. package/src/runtime/process-lifecycle.ts +3 -1
  153. package/src/runtime/services.ts +91 -43
  154. package/src/runtime/ui-services.ts +8 -0
  155. package/src/runtime/workstream-services.ts +195 -0
  156. package/src/shell/blocking-input.ts +22 -1
  157. package/src/shell/ui-openers.ts +129 -17
  158. package/src/utils/format-duration.ts +8 -18
  159. package/src/utils/splash-lines.ts +1 -1
  160. package/src/version.ts +1 -1
  161. package/src/panels/agent-inspector-panel.ts +0 -786
  162. package/src/panels/approval-panel.ts +0 -252
  163. package/src/panels/automation-control-panel.ts +0 -479
  164. package/src/panels/cockpit-panel.ts +0 -481
  165. package/src/panels/cockpit-read-model.ts +0 -233
  166. package/src/panels/communication-panel.ts +0 -256
  167. package/src/panels/control-plane-panel.ts +0 -470
  168. package/src/panels/debug-panel.ts +0 -609
  169. package/src/panels/docs-panel.ts +0 -375
  170. package/src/panels/eval-panel.ts +0 -627
  171. package/src/panels/file-explorer-panel.ts +0 -664
  172. package/src/panels/file-preview-panel.ts +0 -517
  173. package/src/panels/hooks-panel.ts +0 -401
  174. package/src/panels/incident-review-panel.ts +0 -406
  175. package/src/panels/intelligence-panel.ts +0 -383
  176. package/src/panels/knowledge-graph-panel.ts +0 -506
  177. package/src/panels/marketplace-panel.ts +0 -399
  178. package/src/panels/memory-panel.ts +0 -558
  179. package/src/panels/ops-control-panel.ts +0 -329
  180. package/src/panels/ops-strategy-panel.ts +0 -321
  181. package/src/panels/orchestration-panel.ts +0 -465
  182. package/src/panels/panel-list-panel.ts +0 -557
  183. package/src/panels/plan-dashboard-panel.ts +0 -707
  184. package/src/panels/policy-panel.ts +0 -517
  185. package/src/panels/project-planning-panel.ts +0 -721
  186. package/src/panels/provider-health-panel.ts +0 -678
  187. package/src/panels/provider-health-tracker.ts +0 -306
  188. package/src/panels/provider-health-views.ts +0 -560
  189. package/src/panels/qr-panel.ts +0 -280
  190. package/src/panels/remote-panel.ts +0 -534
  191. package/src/panels/routes-panel.ts +0 -241
  192. package/src/panels/sandbox-panel.ts +0 -456
  193. package/src/panels/security-panel.ts +0 -447
  194. package/src/panels/services-panel.ts +0 -329
  195. package/src/panels/session-browser-panel.ts +0 -487
  196. package/src/panels/settings-sync-panel.ts +0 -398
  197. package/src/panels/subscription-panel.ts +0 -342
  198. package/src/panels/symbol-outline-panel.ts +0 -619
  199. package/src/panels/system-messages-panel.ts +0 -364
  200. package/src/panels/tasks-panel.ts +0 -608
  201. package/src/panels/thinking-panel.ts +0 -333
  202. package/src/panels/tool-inspector-panel.ts +0 -537
  203. package/src/panels/work-plan-panel.ts +0 -530
  204. package/src/panels/worktree-panel.ts +0 -360
  205. package/src/panels/wrfc-panel.ts +0 -790
  206. package/src/renderer/agent-detail-modal.ts +0 -465
  207. package/src/renderer/live-tail-modal.ts +0 -156
  208. package/src/renderer/process-modal.ts +0 -656
  209. package/src/renderer/qr-renderer.ts +0 -120
@@ -1,530 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { truncateDisplay } from '../utils/terminal-width.ts';
3
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
- import {
5
- buildDetailBlock,
6
- buildKeyboardHints,
7
- buildKeyValueLine,
8
- buildMeterLine,
9
- buildPanelLine,
10
- buildPanelListRow,
11
- buildSummaryBlock,
12
- DEFAULT_PANEL_PALETTE,
13
- extendPalette,
14
- type PanelPalette,
15
- } from './polish.ts';
16
- import type { WorkPlanItem, WorkPlanItemStatus, WorkPlanStore } from '../work-plans/work-plan-store.ts';
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';
24
-
25
- const C = extendPalette(DEFAULT_PANEL_PALETTE, {
26
- pending: '#94a3b8',
27
- inProgress: '#38bdf8',
28
- blocked: '#f59e0b',
29
- done: '#22c55e',
30
- failed: '#ef4444',
31
- cancelled: '#64748b',
32
- accent: '#a5b4fc',
33
- selectBg: '#1e293b',
34
- });
35
-
36
- const STATUS_LABEL: Record<WorkPlanItemStatus, string> = {
37
- pending: '[ ]',
38
- in_progress: '[>]',
39
- blocked: '[!]',
40
- done: '[x]',
41
- failed: '[x]',
42
- cancelled: '[-]',
43
- };
44
-
45
- const STATUS_COLOR: Record<WorkPlanItemStatus, string> = {
46
- pending: C.pending,
47
- in_progress: C.inProgress,
48
- blocked: C.blocked,
49
- done: C.done,
50
- failed: C.failed,
51
- cancelled: C.cancelled,
52
- };
53
-
54
- const STATUS_ORDER: WorkPlanItemStatus[] = ['pending', 'in_progress', 'blocked', 'done', 'failed', 'cancelled'];
55
-
56
- function compactDate(value: number): string {
57
- return new Date(value).toISOString().replace('T', ' ').slice(0, 16);
58
- }
59
-
60
- function statusName(status: WorkPlanItemStatus): string {
61
- return status.replace(/_/g, ' ');
62
- }
63
-
64
- export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
65
- private items: readonly WorkPlanItem[] = [];
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;
79
-
80
- constructor(private readonly store: WorkPlanStore) {
81
- super('work-plan', 'Work Plan', '◧', 'agent');
82
- this.showSelectionGutter = true;
83
- }
84
-
85
- onActivate(): void {
86
- super.onActivate();
87
- this.refresh();
88
- }
89
-
90
- protected override getPalette(): PanelPalette {
91
- return C;
92
- }
93
-
94
- render(width: number, height: number): Line[] {
95
- this.refresh();
96
- return this.renderList(width, height, {
97
- title: 'Work Plan',
98
- header: this.renderHeader(width),
99
- footer: this.renderFooter(width),
100
- });
101
- }
102
-
103
- handleInput(key: string): boolean {
104
- if (this.lastError !== null) this.clearError();
105
-
106
- if (this.draftMode) {
107
- return this.handleDraftInput(key);
108
- }
109
-
110
- const item = this.getSelectedItem();
111
- try {
112
- switch (key) {
113
- case ' ':
114
- case 'return':
115
- case 'enter':
116
- if (!item) return false;
117
- this.store.cycleItemStatus(item.id);
118
- this.refresh();
119
- return true;
120
- case '1':
121
- return this.setSelectedStatus('pending');
122
- case '2':
123
- return this.setSelectedStatus('in_progress');
124
- case '3':
125
- return this.setSelectedStatus('blocked');
126
- case '4':
127
- return this.setSelectedStatus('done');
128
- case '5':
129
- return this.setSelectedStatus('failed');
130
- case '6':
131
- return this.setSelectedStatus('cancelled');
132
- case 'd':
133
- case 'delete':
134
- if (!item) return false;
135
- this.store.removeItem(item.id);
136
- this.refresh();
137
- return true;
138
- case 'c':
139
- this.store.clearCompleted();
140
- this.refresh();
141
- return true;
142
- case 'r':
143
- this.refresh(true);
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;
163
- default:
164
- return super.handleInput(key);
165
- }
166
- } catch (error) {
167
- this.setError(summarizeError(error));
168
- return true;
169
- }
170
- }
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
-
331
- protected getItems(): readonly WorkPlanItem[] {
332
- return this.items;
333
- }
334
-
335
- protected override getEmptyStateMessage(): string {
336
- return ' No work plan items yet';
337
- }
338
-
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.
343
- return [
344
- { command: '/work-plan list', summary: 'print the current plan to the shell' },
345
- ];
346
- }
347
-
348
- protected renderItem(item: WorkPlanItem, _index: number, selected: boolean, width: number): Line {
349
- const status = STATUS_LABEL[item.status];
350
- const owner = item.owner ? ` @${item.owner}` : '';
351
- const source = item.source ? ` (${item.source})` : '';
352
- const reserve = status.length + owner.length + source.length + 4;
353
- const title = truncateDisplay(item.title, Math.max(6, width - reserve));
354
- return buildPanelListRow(width, [
355
- { text: `${status} `, fg: STATUS_COLOR[item.status], bold: item.status === 'in_progress' },
356
- { text: title, fg: selected ? C.value : C.label, bold: selected || item.status === 'in_progress' },
357
- { text: owner, fg: C.accent },
358
- { text: source, fg: C.dim },
359
- ], C, { selected, selectedBg: C.selectBg });
360
- }
361
-
362
- private setSelectedStatus(status: WorkPlanItemStatus): boolean {
363
- const item = this.getSelectedItem();
364
- if (!item) return false;
365
- this.store.setItemStatus(item.id, status);
366
- this.refresh();
367
- return true;
368
- }
369
-
370
- private refresh(force = false): void {
371
- const plan = this.store.getActivePlan();
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;
376
- this.items = plan.items;
377
- this.lastPlanUpdatedAt = plan.updatedAt;
378
- this.clampSelection();
379
- this.needsRender = true;
380
- }
381
-
382
- private renderHeader(width: number): Line[] {
383
- if (this.draftMode) return this.renderDraftForm(width);
384
- const plan = this.store.getActivePlan();
385
- const counts = new Map<WorkPlanItemStatus, number>();
386
- for (const status of STATUS_ORDER) counts.set(status, 0);
387
- for (const item of plan.items) counts.set(item.status, (counts.get(item.status) ?? 0) + 1);
388
-
389
- const total = plan.items.length;
390
- const done = (counts.get('done') ?? 0) + (counts.get('cancelled') ?? 0);
391
- const pct = total > 0 ? Math.round((done / total) * 100) : 0;
392
- const meterWidth = Math.max(10, Math.min(26, width - 34));
393
-
394
- const postureLines: Line[] = [
395
- buildPanelLine(width, [
396
- [' Project ', C.label],
397
- [truncateDisplay(plan.projectRoot, Math.max(8, width - 11)), C.value],
398
- ]),
399
- buildMeterLine(width, Math.round((pct / 100) * meterWidth), meterWidth, {
400
- filled: pct === 100 ? C.done : C.inProgress,
401
- empty: C.empty,
402
- label: C.label,
403
- }, { prefix: ' Progress ', suffix: ` ${done}/${total} (${pct}%) ` }),
404
- buildKeyValueLine(width, [
405
- { label: 'pending', value: String(counts.get('pending') ?? 0), valueColor: (counts.get('pending') ?? 0) > 0 ? C.pending : C.dim },
406
- { label: 'active', value: String(counts.get('in_progress') ?? 0), valueColor: (counts.get('in_progress') ?? 0) > 0 ? C.inProgress : C.dim },
407
- { label: 'blocked', value: String(counts.get('blocked') ?? 0), valueColor: (counts.get('blocked') ?? 0) > 0 ? C.blocked : C.dim },
408
- { label: 'done', value: String(counts.get('done') ?? 0), valueColor: (counts.get('done') ?? 0) > 0 ? C.done : C.dim },
409
- ], C),
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.
414
- ];
415
-
416
- const header: Line[] = buildSummaryBlock(width, 'Persistent Work Plan', postureLines, C);
417
-
418
- const active = this.getSelectedItem();
419
- if (active) {
420
- const detailRows: Line[] = [
421
- buildPanelLine(width, [
422
- [' ', C.label],
423
- [STATUS_LABEL[active.status], STATUS_COLOR[active.status]],
424
- [' ', C.label],
425
- [truncateDisplay(active.title, Math.max(8, width - 6)), C.value],
426
- ]),
427
- buildKeyValueLine(width, [
428
- { label: 'status', value: statusName(active.status), valueColor: STATUS_COLOR[active.status] },
429
- ...(active.owner ? [{ label: 'owner', value: active.owner, valueColor: C.accent }] : []),
430
- { label: 'updated', value: compactDate(active.updatedAt), valueColor: C.dim },
431
- ], C),
432
- ];
433
- if (active.source) {
434
- detailRows.push(buildPanelLine(width, [[' source ', C.label], [active.source, C.info]]));
435
- }
436
- if (active.notes) {
437
- detailRows.push(buildPanelLine(width, [
438
- [' notes ', C.label],
439
- [truncateDisplay(active.notes, Math.max(8, width - 8)), C.value],
440
- ]));
441
- }
442
- const linkedSegments = this.buildLinkedSegments(active);
443
- if (linkedSegments.length > 0) {
444
- detailRows.push(buildPanelLine(width, [[' linked ', C.label], ...linkedSegments]));
445
- }
446
- header.push(...buildDetailBlock(width, 'Selected item', detailRows, C));
447
- }
448
- return header;
449
- }
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
-
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
- }
491
- const hasItem = this.items.length > 0;
492
- if (!hasItem) {
493
- return [
494
- buildKeyboardHints(width, [
495
- { keys: '↑/↓', label: 'navigate' },
496
- { keys: 'a', label: 'add' },
497
- ], C),
498
- ];
499
- }
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),
516
- buildPanelLine(width, [
517
- [' 1', C.info], [' pending ', C.dim],
518
- ['2', C.info], [' active ', C.dim],
519
- ['3', C.info], [' blocked ', C.dim],
520
- ['4', C.info], [' done ', C.dim],
521
- ['5', C.info], [' failed ', C.dim],
522
- ['6', C.info], [' cancelled', C.dim],
523
- ]),
524
- ];
525
- if (this.lastStatus) {
526
- lines.push(buildPanelLine(width, [[` ${this.lastStatus}`, C.good]]));
527
- }
528
- return lines;
529
- }
530
- }