@pellux/goodvibes-tui 0.18.20 → 0.19.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 (83) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +7 -3
  5. package/src/core/conversation-rendering.ts +22 -6
  6. package/src/core/orchestrator.ts +1 -1
  7. package/src/input/commands/diff-runtime.ts +6 -5
  8. package/src/input/commands/guidance-runtime.ts +1 -1
  9. package/src/input/commands/health-runtime.ts +2 -2
  10. package/src/input/commands/local-setup-review.ts +1 -1
  11. package/src/input/commands/session-content.ts +1 -1
  12. package/src/input/commands/session.ts +0 -1
  13. package/src/input/commands/shell-core.ts +3 -2
  14. package/src/input/commands/skills-runtime.ts +2 -2
  15. package/src/input/commands/subscription-runtime.ts +4 -4
  16. package/src/input/feed-context-factory.ts +236 -0
  17. package/src/input/handler-feed.ts +44 -6
  18. package/src/input/handler-shortcuts.ts +138 -125
  19. package/src/input/handler.ts +119 -119
  20. package/src/input/keybindings.ts +30 -0
  21. package/src/input/panel-integration-actions.ts +2 -1
  22. package/src/input/settings-modal-types.ts +60 -0
  23. package/src/input/settings-modal.ts +83 -65
  24. package/src/panels/agent-inspector-panel.ts +10 -9
  25. package/src/panels/agent-logs-panel.ts +26 -6
  26. package/src/panels/approval-panel.ts +55 -82
  27. package/src/panels/automation-control-panel.ts +120 -161
  28. package/src/panels/base-panel.ts +108 -3
  29. package/src/panels/communication-panel.ts +69 -107
  30. package/src/panels/context-visualizer-panel.ts +2 -0
  31. package/src/panels/control-plane-panel.ts +117 -172
  32. package/src/panels/diff-panel.ts +2 -0
  33. package/src/panels/file-explorer-panel.ts +51 -31
  34. package/src/panels/file-preview-panel.ts +57 -35
  35. package/src/panels/git-panel.ts +12 -13
  36. package/src/panels/hooks-panel.ts +103 -138
  37. package/src/panels/incident-review-panel.ts +59 -109
  38. package/src/panels/knowledge-panel.ts +75 -107
  39. package/src/panels/local-auth-panel.ts +77 -93
  40. package/src/panels/marketplace-panel.ts +51 -69
  41. package/src/panels/mcp-panel.ts +110 -155
  42. package/src/panels/memory-panel.ts +90 -158
  43. package/src/panels/ops-control-panel.ts +51 -85
  44. package/src/panels/orchestration-panel.ts +70 -51
  45. package/src/panels/panel-list-panel.ts +5 -4
  46. package/src/panels/panel-manager.ts +25 -2
  47. package/src/panels/plan-dashboard-panel.ts +2 -0
  48. package/src/panels/plugins-panel.ts +37 -60
  49. package/src/panels/polish.ts +51 -2
  50. package/src/panels/provider-accounts-panel.ts +1 -0
  51. package/src/panels/provider-health-panel.ts +6 -8
  52. package/src/panels/routes-panel.ts +91 -141
  53. package/src/panels/schedule-panel.ts +7 -6
  54. package/src/panels/scrollable-list-panel.ts +64 -16
  55. package/src/panels/security-panel.ts +118 -152
  56. package/src/panels/services-panel.ts +63 -105
  57. package/src/panels/session-browser-panel.ts +19 -18
  58. package/src/panels/settings-sync-panel.ts +79 -123
  59. package/src/panels/skills-panel.ts +114 -230
  60. package/src/panels/subscription-panel.ts +64 -86
  61. package/src/panels/system-messages-panel.ts +147 -141
  62. package/src/panels/tasks-panel.ts +130 -179
  63. package/src/panels/token-budget-panel.ts +2 -0
  64. package/src/panels/watchers-panel.ts +89 -137
  65. package/src/panels/worktree-panel.ts +1 -0
  66. package/src/panels/wrfc-panel.ts +2 -0
  67. package/src/renderer/agent-detail-modal.ts +2 -2
  68. package/src/renderer/ansi-sanitize.ts +76 -0
  69. package/src/renderer/buffer.ts +23 -1
  70. package/src/renderer/diff.ts +8 -0
  71. package/src/renderer/help-overlay.ts +48 -28
  72. package/src/renderer/markdown.ts +3 -145
  73. package/src/renderer/settings-modal-helpers.ts +27 -0
  74. package/src/renderer/settings-modal.ts +18 -1
  75. package/src/renderer/status-glyphs.ts +21 -0
  76. package/src/renderer/status-token.ts +4 -8
  77. package/src/renderer/tool-call.ts +4 -3
  78. package/src/runtime/bootstrap-core.ts +1 -1
  79. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  80. package/src/runtime/bootstrap.ts +7 -8
  81. package/src/runtime/diagnostics/panels/policy.ts +2 -1
  82. package/src/shell/ui-openers.ts +1 -1
  83. package/src/version.ts +1 -1
@@ -1,6 +1,6 @@
1
1
  import type { Line } from '../types/grid.ts';
2
2
  import { createEmptyLine } from '../types/grid.ts';
3
- import { BasePanel } from './base-panel.ts';
3
+ import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
4
  import type { RuntimeTask, TaskLifecycleState } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/tasks';
5
5
  import type { ManagedWorktreeMeta } from '@pellux/goodvibes-sdk/platform/runtime/worktree/registry';
6
6
  import type { UiReadModel, UiTasksSnapshot, UiWorktreeSnapshot } from '../runtime/ui-read-models.ts';
@@ -13,8 +13,6 @@ import {
13
13
  buildSummaryBlock,
14
14
  buildPanelWorkspace,
15
15
  DEFAULT_PANEL_PALETTE,
16
- resolvePrimaryScrollableSection,
17
- type PanelWorkspaceSection,
18
16
  } from './polish.ts';
19
17
 
20
18
  const C = {
@@ -148,18 +146,17 @@ function reviewTaskWorktreeAttachments(
148
146
  });
149
147
  }
150
148
 
151
- export class TasksPanel extends BasePanel {
149
+ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
152
150
  private readonly readModel?: UiReadModel<UiTasksSnapshot>;
153
151
  private readonly worktrees?: UiReadModel<UiWorktreeSnapshot>;
154
152
  private readonly unsubscribers: readonly (() => void)[];
155
- private selectedIndex = 0;
156
- private scrollOffset = 0;
157
153
 
158
154
  public constructor(
159
155
  readModel: UiReadModel<UiTasksSnapshot> | undefined,
160
156
  worktrees?: UiReadModel<UiWorktreeSnapshot>,
161
157
  ) {
162
158
  super('tasks', 'Tasks', 'J', 'monitoring');
159
+ this.showSelectionGutter = true; // I5: non-color selection affordance
163
160
  this.readModel = readModel;
164
161
  this.worktrees = worktrees;
165
162
  this.unsubscribers = [
@@ -177,39 +174,46 @@ export class TasksPanel extends BasePanel {
177
174
  for (const unsubscribe of this.unsubscribers) unsubscribe();
178
175
  }
179
176
 
177
+ protected override getPalette() { return C; }
178
+ protected override getEmptyStateMessage() { return ' No tasks recorded yet.'; }
179
+ protected override getEmptyStateActions() {
180
+ return [
181
+ { command: '/tasks create', summary: 'create a tracked task from the shell' },
182
+ { command: '/orchestration', summary: 'review graph-native task execution and WRFC flows' },
183
+ ];
184
+ }
185
+
186
+ protected getItems(): readonly RuntimeTask[] {
187
+ if (!this.readModel) return [];
188
+ return sortTasks([...this.readModel.getSnapshot().tasks]);
189
+ }
190
+
191
+ protected renderItem(task: RuntimeTask, index: number, selected: boolean, width: number): Line {
192
+ return buildPanelListRow(width, [
193
+ { text: task.status.padEnd(10), fg: statusColor(task.status) },
194
+ { text: ` ${kindLabel(task.kind).padEnd(12)}`, fg: C.value },
195
+ { text: ` ${task.id.slice(0, 8)} `, fg: C.dim },
196
+ { text: task.title.slice(0, Math.max(0, width - 37)), fg: C.value },
197
+ ], C, { selected });
198
+ }
199
+
180
200
  public handleInput(key: string): boolean {
181
- const tasks = this._tasks();
182
- if (tasks.length === 0) return false;
183
- if (key === 'up' || key === 'k') {
184
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
185
- this.markDirty();
186
- return true;
187
- }
188
- if (key === 'down' || key === 'j') {
189
- this.selectedIndex = Math.min(tasks.length - 1, this.selectedIndex + 1);
190
- this.markDirty();
191
- return true;
192
- }
193
201
  if (key === 'home') {
194
202
  this.selectedIndex = 0;
195
203
  this.markDirty();
196
204
  return true;
197
205
  }
198
206
  if (key === 'end') {
199
- this.selectedIndex = tasks.length - 1;
207
+ const tasks = this.getItems();
208
+ this.selectedIndex = Math.max(0, tasks.length - 1);
200
209
  this.markDirty();
201
210
  return true;
202
211
  }
203
- return false;
204
- }
205
-
206
- private _tasks(): RuntimeTask[] {
207
- if (!this.readModel) return [];
208
- return sortTasks([...this.readModel.getSnapshot().tasks]);
212
+ return super.handleInput(key);
209
213
  }
210
214
 
211
215
  public render(width: number, height: number): Line[] {
212
- this.needsRender = false;
216
+ this.clampSelection();
213
217
  const intro = 'Live task lifecycle, ownership, retries, and result/error details across runtime execution domains.';
214
218
  const footerLines = [buildPanelLine(width, [[' Up/Down move Home/End jump', C.dim]])];
215
219
 
@@ -232,34 +236,7 @@ export class TasksPanel extends BasePanel {
232
236
  return workspace;
233
237
  }
234
238
 
235
- const tasks = this._tasks();
236
- if (tasks.length === 0) {
237
- const workspace = buildPanelWorkspace(width, height, {
238
- title: 'Task Control Room',
239
- intro,
240
- sections: [{
241
- title: 'Overview',
242
- lines: [
243
- buildPanelLine(width, [[' queued:0 running:0 blocked:0 failed:0 completed:0', C.dim]]),
244
- ...buildEmptyState(
245
- width,
246
- ' No tasks recorded yet.',
247
- 'Tasks will appear here as exec, agent, ACP, scheduler, daemon, MCP, plugin, and integration work starts.',
248
- [
249
- { command: '/tasks create', summary: 'create a tracked task from the shell' },
250
- { command: '/orchestration', summary: 'review graph-native task execution and WRFC flows' },
251
- ],
252
- C,
253
- ),
254
- ],
255
- }],
256
- palette: C,
257
- });
258
- while (workspace.length < height) workspace.push(createEmptyLine(width));
259
- return workspace;
260
- }
261
-
262
- this.selectedIndex = Math.min(this.selectedIndex, tasks.length - 1);
239
+ const tasks = this.getItems();
263
240
  const counts = STATUS_ORDER.map((status) => ({
264
241
  status,
265
242
  count: tasks.filter((task) => task.status === status).length,
@@ -270,7 +247,6 @@ export class TasksPanel extends BasePanel {
270
247
  const queuedCount = counts.find((entry) => entry.status === 'queued')?.count ?? 0;
271
248
  const completedCount = counts.find((entry) => entry.status === 'completed')?.count ?? 0;
272
249
 
273
- const selected = tasks[this.selectedIndex]!;
274
250
  const postureLines: Line[] = [
275
251
  buildPanelLine(width, [
276
252
  [' queued ', C.label],
@@ -284,7 +260,11 @@ export class TasksPanel extends BasePanel {
284
260
  [' completed ', C.label],
285
261
  [String(completedCount), completedCount > 0 ? C.completed : C.dim],
286
262
  ]),
287
- buildPanelLine(width, [
263
+ ];
264
+
265
+ const selected = tasks[this.selectedIndex];
266
+ if (selected) {
267
+ postureLines.push(buildPanelLine(width, [
288
268
  [' selected ', C.label],
289
269
  [selected.id, C.info],
290
270
  [' status ', C.label],
@@ -293,156 +273,127 @@ export class TasksPanel extends BasePanel {
293
273
  [selected.kind, C.value],
294
274
  [' owner ', C.label],
295
275
  [selected.owner.slice(0, Math.max(0, width - 46)), C.dim],
296
- ]),
276
+ ]));
277
+ }
278
+ postureLines.push(
297
279
  buildGuidanceLine(width, '/teamwork review', 'inspect task-family posture, archetype metadata, and recovery options for active work', C),
298
280
  buildGuidanceLine(width, '/worktree task <task-id>', 'review worktree ownership, restore, and merge posture for the selected task', C),
299
- ];
300
- const descriptor = selected.description ? parseTaskDescriptor(selected.description) : null;
301
- const detailRows: Line[] = [
302
- buildPanelLine(width, [
281
+ );
282
+
283
+ const detailRows: Line[] = [];
284
+ if (selected) {
285
+ const descriptor = selected.description ? parseTaskDescriptor(selected.description) : null;
286
+ detailRows.push(buildPanelLine(width, [
303
287
  [' Title: ', C.label],
304
288
  [selected.title, C.value],
305
289
  [' Status: ', C.label],
306
290
  [selected.status, statusColor(selected.status)],
307
291
  [' Kind: ', C.label],
308
292
  [selected.kind, C.value],
309
- ]),
310
- buildPanelLine(width, [
293
+ ]));
294
+ detailRows.push(buildPanelLine(width, [
311
295
  [' Owner: ', C.label],
312
296
  [selected.owner, C.value],
313
297
  [' Cancellable: ', C.label],
314
298
  [selected.cancellable ? 'yes' : 'no', selected.cancellable ? C.running : C.failed],
315
299
  [' Queue: ', C.label],
316
300
  [formatWhen(selected.queuedAt), C.dim],
317
- ]),
318
- buildPanelLine(width, [
301
+ ]));
302
+ detailRows.push(buildPanelLine(width, [
319
303
  [' Started: ', C.label],
320
304
  [formatWhen(selected.startedAt), C.dim],
321
305
  [' Ended: ', C.label],
322
306
  [formatWhen(selected.endedAt), C.dim],
323
307
  [' Duration: ', C.label],
324
308
  [formatDuration(selected.startedAt, selected.endedAt), C.dim],
325
- ]),
326
- ];
327
- if (descriptor?.mode || descriptor?.family || descriptor?.source) {
328
- detailRows.push(buildPanelLine(width, [
329
- [' Mode: ', C.label],
330
- [descriptor?.mode ?? 'n/a', C.value],
331
- [' Family: ', C.label],
332
- [descriptor?.family ?? 'n/a', C.info],
333
- [' Source: ', C.label],
334
- [descriptor?.source ?? 'builtin/runtime', C.dim],
335
- ]));
336
- }
337
- if (descriptor?.reviewMode || descriptor?.executionProtocol || descriptor?.template) {
338
- detailRows.push(buildPanelLine(width, [
339
- [' Review: ', C.label],
340
- [descriptor?.reviewMode ?? 'n/a', C.value],
341
- [' Protocol: ', C.label],
342
- [descriptor?.executionProtocol ?? 'n/a', C.value],
343
- [' Template: ', C.label],
344
- [descriptor?.template ?? 'n/a', C.dim],
345
- ]));
346
- }
347
- if (selected.correlationId || selected.turnId) {
348
- detailRows.push(buildPanelLine(width, [
349
- [' Correlation: ', C.label],
350
- [selected.correlationId ?? 'n/a', C.dim],
351
- [' Turn: ', C.label],
352
- [selected.turnId ?? 'n/a', C.dim],
353
- ]));
354
- }
355
- if (selected.parentTaskId || selected.childTaskIds.length > 0) {
356
- detailRows.push(buildPanelLine(width, [
357
- [' Parent: ', C.label],
358
- [selected.parentTaskId ?? 'none', C.dim],
359
- [' Children: ', C.label],
360
- [selected.childTaskIds.length > 0 ? selected.childTaskIds.join(', ') : 'none', C.dim],
361
- ]));
362
- }
363
- const attachedWorktrees = reviewTaskWorktreeAttachments(selected.id, this.worktrees);
364
- if (attachedWorktrees.total > 0) {
365
- detailRows.push(buildPanelLine(width, [
366
- [' Worktrees: ', C.label],
367
- [`${attachedWorktrees.total} tracked`, C.info],
368
- [' Active: ', C.label],
369
- [String(attachedWorktrees.active), attachedWorktrees.active > 0 ? C.running : C.dim],
370
- [' Paused: ', C.label],
371
- [String(attachedWorktrees.paused), attachedWorktrees.paused > 0 ? C.blocked : C.dim],
372
309
  ]));
373
- detailRows.push(buildPanelLine(width, [[
374
- ` Next: /worktree task ${selected.id} /worktree recover task ${selected.id}`,
375
- C.dim,
376
- ]]));
377
- for (const record of attachedWorktrees.records.slice(0, 2)) {
310
+ if (descriptor?.mode || descriptor?.family || descriptor?.source) {
311
+ detailRows.push(buildPanelLine(width, [
312
+ [' Mode: ', C.label],
313
+ [descriptor?.mode ?? 'n/a', C.value],
314
+ [' Family: ', C.label],
315
+ [descriptor?.family ?? 'n/a', C.info],
316
+ [' Source: ', C.label],
317
+ [descriptor?.source ?? 'builtin/runtime', C.dim],
318
+ ]));
319
+ }
320
+ if (descriptor?.reviewMode || descriptor?.executionProtocol || descriptor?.template) {
321
+ detailRows.push(buildPanelLine(width, [
322
+ [' Review: ', C.label],
323
+ [descriptor?.reviewMode ?? 'n/a', C.value],
324
+ [' Protocol: ', C.label],
325
+ [descriptor?.executionProtocol ?? 'n/a', C.value],
326
+ [' Template: ', C.label],
327
+ [descriptor?.template ?? 'n/a', C.dim],
328
+ ]));
329
+ }
330
+ if (selected.correlationId || selected.turnId) {
331
+ detailRows.push(buildPanelLine(width, [
332
+ [' Correlation: ', C.label],
333
+ [selected.correlationId ?? 'n/a', C.dim],
334
+ [' Turn: ', C.label],
335
+ [selected.turnId ?? 'n/a', C.dim],
336
+ ]));
337
+ }
338
+ if (selected.parentTaskId || selected.childTaskIds.length > 0) {
339
+ detailRows.push(buildPanelLine(width, [
340
+ [' Parent: ', C.label],
341
+ [selected.parentTaskId ?? 'none', C.dim],
342
+ [' Children: ', C.label],
343
+ [selected.childTaskIds.length > 0 ? selected.childTaskIds.join(', ') : 'none', C.dim],
344
+ ]));
345
+ }
346
+ const attachedWorktrees = reviewTaskWorktreeAttachments(selected.id, this.worktrees);
347
+ if (attachedWorktrees.total > 0) {
348
+ detailRows.push(buildPanelLine(width, [
349
+ [' Worktrees: ', C.label],
350
+ [`${attachedWorktrees.total} tracked`, C.info],
351
+ [' Active: ', C.label],
352
+ [String(attachedWorktrees.active), attachedWorktrees.active > 0 ? C.running : C.dim],
353
+ [' Paused: ', C.label],
354
+ [String(attachedWorktrees.paused), attachedWorktrees.paused > 0 ? C.blocked : C.dim],
355
+ ]));
378
356
  detailRows.push(buildPanelLine(width, [[
379
- ` ${record.state.padEnd(15)} ${record.path}`.slice(0, Math.max(0, width - 2)),
380
- record.state === 'active' ? C.running : record.state === 'paused' ? C.blocked : C.dim,
357
+ ` Next: /worktree task ${selected.id} /worktree recover task ${selected.id}`,
358
+ C.dim,
381
359
  ]]));
360
+ for (const record of attachedWorktrees.records.slice(0, 2)) {
361
+ detailRows.push(buildPanelLine(width, [[
362
+ ` ${record.state.padEnd(15)} ${record.path}`.slice(0, Math.max(0, width - 2)),
363
+ record.state === 'active' ? C.running : record.state === 'paused' ? C.blocked : C.dim,
364
+ ]]));
365
+ }
366
+ }
367
+ if (selected.retryPolicy) {
368
+ detailRows.push(buildPanelLine(width, [
369
+ [' Retry: ', C.label],
370
+ [`${selected.retryPolicy.currentAttempt}/${selected.retryPolicy.maxAttempts} ${selected.retryPolicy.backoff}`, C.value],
371
+ ]));
372
+ }
373
+ if (selected.error) {
374
+ detailRows.push(buildPanelLine(width, [
375
+ [' Error: ', C.label],
376
+ [selected.error.slice(0, Math.max(0, width - 10)), C.failed],
377
+ ]));
378
+ }
379
+ if (selected.result !== undefined) {
380
+ const resultText = safeJson(selected.result);
381
+ detailRows.push(buildPanelLine(width, [
382
+ [' Result: ', C.label],
383
+ [resultText.slice(0, Math.max(0, width - 11)), C.dim],
384
+ ]));
382
385
  }
383
386
  }
384
- if (selected.retryPolicy) {
385
- detailRows.push(buildPanelLine(width, [
386
- [' Retry: ', C.label],
387
- [`${selected.retryPolicy.currentAttempt}/${selected.retryPolicy.maxAttempts} ${selected.retryPolicy.backoff}`, C.value],
388
- ]));
389
- }
390
- if (selected.error) {
391
- detailRows.push(buildPanelLine(width, [
392
- [' Error: ', C.label],
393
- [selected.error.slice(0, Math.max(0, width - 10)), C.failed],
394
- ]));
395
- }
396
- if (selected.result !== undefined) {
397
- const resultText = safeJson(selected.result);
398
- detailRows.push(buildPanelLine(width, [
399
- [' Result: ', C.label],
400
- [resultText.slice(0, Math.max(0, width - 11)), C.dim],
401
- ]));
402
- }
403
- const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Task posture', postureLines, C) };
404
- const selectedSection: PanelWorkspaceSection = { lines: buildDetailBlock(width, 'Selected task', detailRows, C) };
405
- const rawTaskLines: Line[] = [];
406
- for (let absolute = 0; absolute < tasks.length; absolute++) {
407
- const task = tasks[absolute]!;
408
- rawTaskLines.push(buildPanelListRow(width, [
409
- { text: task.status.padEnd(10), fg: statusColor(task.status) },
410
- { text: ` ${kindLabel(task.kind).padEnd(12)}`, fg: C.value },
411
- { text: ` ${task.id.slice(0, 8)} `, fg: C.dim },
412
- { text: task.title.slice(0, Math.max(0, width - 37)), fg: C.value },
413
- ], C, { selected: absolute === this.selectedIndex }));
414
- }
415
- const resolvedTasksSection = resolvePrimaryScrollableSection(width, height, {
416
- intro,
417
- footerLines,
418
- palette: C,
419
- beforeSections: [postureSection],
420
- section: {
421
- title: 'Tasks',
422
- scrollableLines: rawTaskLines,
423
- selectedIndex: this.selectedIndex,
424
- scrollOffset: this.scrollOffset,
425
- guardRows: 1,
426
- minRows: 4,
427
- appendWindowSummary: { dimColor: C.dim },
428
- },
429
- afterSections: [selectedSection],
430
- });
431
- this.scrollOffset = resolvedTasksSection.scrollOffset;
432
387
 
433
- const sections: PanelWorkspaceSection[] = [
434
- postureSection,
435
- resolvedTasksSection.section,
436
- selectedSection,
437
- ];
438
- const lines = buildPanelWorkspace(width, height, {
388
+ const headerLines: Line[] = buildSummaryBlock(width, 'Task posture', postureLines, C);
389
+
390
+ return this.renderList(width, height, {
439
391
  title: 'Task Control Room',
440
- intro,
441
- sections,
442
- footerLines,
443
- palette: C,
392
+ header: headerLines,
393
+ footer: [
394
+ ...buildDetailBlock(width, 'Selected task', detailRows, C),
395
+ ...footerLines,
396
+ ],
444
397
  });
445
- while (lines.length < height) lines.push(createEmptyLine(width));
446
- return lines.slice(0, height);
447
398
  }
448
399
  }
@@ -199,6 +199,7 @@ export class TokenBudgetPanel extends BasePanel {
199
199
  // ---------------------------------------------------------------------------
200
200
 
201
201
  override render(width: number, height: number): Line[] {
202
+ return this.trackedRender(() => {
202
203
  const sections: PanelWorkspaceSection[] = [];
203
204
 
204
205
  if (this.contextWindow > 0) {
@@ -258,6 +259,7 @@ export class TokenBudgetPanel extends BasePanel {
258
259
  sections,
259
260
  palette: DEFAULT_PANEL_PALETTE,
260
261
  });
262
+ });
261
263
  }
262
264
 
263
265
  private renderMaintenance(width: number): Line[] {