@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.
Files changed (143) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/src/core/context-usage.ts +53 -0
  5. package/src/core/conversation.ts +4 -6
  6. package/src/core/session-recovery.ts +2 -0
  7. package/src/core/turn-event-wiring.ts +1 -0
  8. package/src/input/commands/cost-runtime.ts +49 -0
  9. package/src/input/commands/eval.ts +4 -3
  10. package/src/input/commands/operator-runtime.ts +5 -1
  11. package/src/input/commands.ts +2 -0
  12. package/src/input/handler-feed-routes.ts +60 -26
  13. package/src/input/handler-feed.ts +17 -8
  14. package/src/input/handler-picker-routes.ts +18 -13
  15. package/src/input/handler-shortcuts.ts +51 -0
  16. package/src/input/handler-ui-state.ts +4 -0
  17. package/src/input/handler.ts +43 -6
  18. package/src/input/keybindings.ts +53 -8
  19. package/src/input/model-picker.ts +5 -0
  20. package/src/input/panel-integration-actions.ts +117 -1
  21. package/src/main.ts +5 -2
  22. package/src/panels/agent-inspector-panel.ts +139 -45
  23. package/src/panels/agent-inspector-shared.ts +147 -3
  24. package/src/panels/approval-panel.ts +204 -102
  25. package/src/panels/automation-control-panel.ts +370 -103
  26. package/src/panels/base-panel.ts +26 -2
  27. package/src/panels/builtin/agent.ts +28 -37
  28. package/src/panels/builtin/development.ts +40 -32
  29. package/src/panels/builtin/knowledge.ts +15 -6
  30. package/src/panels/builtin/operations.ts +178 -117
  31. package/src/panels/builtin/session.ts +35 -9
  32. package/src/panels/builtin/shared.ts +98 -6
  33. package/src/panels/cockpit-panel.ts +253 -75
  34. package/src/panels/cockpit-read-model.ts +2 -1
  35. package/src/panels/communication-panel.ts +159 -56
  36. package/src/panels/confirm-state.ts +8 -1
  37. package/src/panels/control-plane-panel.ts +354 -95
  38. package/src/panels/cost-tracker-panel.ts +243 -57
  39. package/src/panels/debug-panel.ts +347 -159
  40. package/src/panels/diff-panel.ts +257 -82
  41. package/src/panels/docs-panel.ts +166 -64
  42. package/src/panels/eval-panel.ts +336 -109
  43. package/src/panels/expandable-list-panel.ts +189 -0
  44. package/src/panels/file-explorer-panel.ts +238 -159
  45. package/src/panels/file-preview-panel.ts +141 -59
  46. package/src/panels/git-panel.ts +360 -191
  47. package/src/panels/hooks-panel.ts +181 -19
  48. package/src/panels/incident-review-panel.ts +252 -43
  49. package/src/panels/index.ts +0 -4
  50. package/src/panels/intelligence-panel.ts +310 -103
  51. package/src/panels/knowledge-graph-panel.ts +480 -58
  52. package/src/panels/local-auth-panel.ts +251 -33
  53. package/src/panels/marketplace-panel.ts +219 -33
  54. package/src/panels/memory-panel.ts +83 -81
  55. package/src/panels/ops-control-panel.ts +211 -32
  56. package/src/panels/ops-strategy-panel.ts +131 -45
  57. package/src/panels/orchestration-panel.ts +259 -67
  58. package/src/panels/panel-list-panel.ts +184 -136
  59. package/src/panels/panel-manager.ts +120 -13
  60. package/src/panels/plan-dashboard-panel.ts +507 -74
  61. package/src/panels/plugins-panel.ts +227 -34
  62. package/src/panels/policy-panel.ts +264 -55
  63. package/src/panels/polish-core.ts +162 -0
  64. package/src/panels/polish-tables.ts +258 -0
  65. package/src/panels/polish.ts +67 -149
  66. package/src/panels/project-planning-answer-actions.ts +134 -0
  67. package/src/panels/project-planning-panel.ts +84 -113
  68. package/src/panels/provider-health-panel.ts +438 -480
  69. package/src/panels/provider-health-routes.ts +203 -0
  70. package/src/panels/provider-health-tracker.ts +194 -6
  71. package/src/panels/provider-health-views.ts +560 -0
  72. package/src/panels/qr-panel.ts +136 -38
  73. package/src/panels/remote-panel.ts +123 -38
  74. package/src/panels/routes-panel.ts +87 -24
  75. package/src/panels/sandbox-panel.ts +232 -65
  76. package/src/panels/scrollable-list-panel.ts +143 -145
  77. package/src/panels/security-panel.ts +204 -52
  78. package/src/panels/services-panel.ts +174 -76
  79. package/src/panels/session-browser-panel.ts +101 -6
  80. package/src/panels/session-maintenance.ts +4 -122
  81. package/src/panels/settings-sync-panel.ts +346 -68
  82. package/src/panels/skills-panel.ts +157 -89
  83. package/src/panels/subscription-panel.ts +105 -34
  84. package/src/panels/symbol-outline-panel.ts +287 -154
  85. package/src/panels/system-messages-panel.ts +172 -38
  86. package/src/panels/tasks-panel.ts +348 -134
  87. package/src/panels/thinking-panel.ts +66 -32
  88. package/src/panels/token-budget-panel.ts +233 -46
  89. package/src/panels/tool-inspector-panel.ts +170 -54
  90. package/src/panels/types.ts +65 -4
  91. package/src/panels/work-plan-panel.ts +393 -39
  92. package/src/panels/worktree-panel.ts +239 -61
  93. package/src/panels/wrfc-panel-format.ts +133 -0
  94. package/src/panels/wrfc-panel.ts +146 -150
  95. package/src/renderer/compaction-preview.ts +2 -1
  96. package/src/renderer/compositor.ts +46 -43
  97. package/src/renderer/conversation-overlays.ts +25 -11
  98. package/src/renderer/footer-tips.ts +41 -0
  99. package/src/renderer/fullscreen-primitives.ts +22 -16
  100. package/src/renderer/help-overlay.ts +91 -14
  101. package/src/renderer/hint-grammar.ts +52 -0
  102. package/src/renderer/layout.ts +7 -7
  103. package/src/renderer/modal-factory.ts +23 -15
  104. package/src/renderer/model-picker-overlay.ts +30 -11
  105. package/src/renderer/model-workspace.ts +2 -3
  106. package/src/renderer/overlay-box.ts +16 -10
  107. package/src/renderer/panel-composite.ts +7 -20
  108. package/src/renderer/panel-workspace-bar.ts +14 -8
  109. package/src/renderer/process-indicator.ts +3 -1
  110. package/src/renderer/progress.ts +8 -6
  111. package/src/renderer/search-overlay.ts +27 -6
  112. package/src/renderer/session-picker-modal.ts +6 -4
  113. package/src/renderer/settings-modal.ts +70 -10
  114. package/src/renderer/shell-surface.ts +41 -15
  115. package/src/renderer/status-glyphs.ts +11 -9
  116. package/src/renderer/tab-strip.ts +148 -34
  117. package/src/renderer/theme.ts +60 -3
  118. package/src/renderer/ui-factory.ts +45 -36
  119. package/src/renderer/ui-primitives.ts +23 -2
  120. package/src/runtime/bootstrap-shell.ts +35 -18
  121. package/src/runtime/diagnostics/panels/index.ts +0 -3
  122. package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
  123. package/src/runtime/ui/model-picker/types.ts +4 -0
  124. package/src/shell/ui-openers.ts +14 -22
  125. package/src/utils/format-duration.ts +55 -0
  126. package/src/utils/format-number.ts +71 -0
  127. package/src/utils/splash-lines.ts +44 -3
  128. package/src/version.ts +1 -1
  129. package/src/work-plans/work-plan-store.ts +13 -0
  130. package/src/panels/agent-logs-panel.ts +0 -666
  131. package/src/panels/agent-logs-shared.ts +0 -129
  132. package/src/panels/context-visualizer-panel.ts +0 -214
  133. package/src/panels/forensics-panel.ts +0 -364
  134. package/src/panels/panel-picker.ts +0 -106
  135. package/src/panels/provider-account-snapshot.ts +0 -259
  136. package/src/panels/provider-accounts-panel.ts +0 -218
  137. package/src/panels/provider-stats-panel.ts +0 -366
  138. package/src/panels/schedule-panel.ts +0 -342
  139. package/src/panels/watchers-panel.ts +0 -193
  140. package/src/renderer/panel-picker-overlay.ts +0 -202
  141. package/src/renderer/panel-tab-bar.ts +0 -69
  142. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  143. package/src/runtime/diagnostics/panels/policy.ts +0 -177
@@ -13,30 +13,26 @@
13
13
  import type { Line } from '../types/grid.ts';
14
14
  import type { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
15
15
  import type { MemoryClass, MemoryRecord, MemoryReviewState } from '@pellux/goodvibes-sdk/platform/state';
16
- import { ScrollableListPanel, SearchableListPanel } from './scrollable-list-panel.ts';
16
+ import { ScrollableListPanel } from './scrollable-list-panel.ts';
17
+ import { truncateDisplay } from '../utils/terminal-width.ts';
17
18
  import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
18
19
  import {
19
20
  buildBodyText,
20
- buildGuidanceLine,
21
21
  buildKeyValueLine,
22
22
  buildPanelLine,
23
23
  buildPanelWorkspace,
24
24
  extendPalette,
25
25
  DEFAULT_PANEL_PALETTE,
26
26
  } from './polish.ts';
27
- import {
28
- getPanelSearchFocusTransition,
29
- isPanelSearchCancel,
30
- } from './search-focus.ts';
31
27
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
32
28
 
33
29
  // ---------------------------------------------------------------------------
34
30
  // Colour palette
35
31
  // ---------------------------------------------------------------------------
36
32
 
33
+ // Domain accents only — one hue per memory class (base chrome: header/
34
+ // headerBg/selectBg come from DEFAULT_PANEL_PALETTE).
37
35
  const C = extendPalette(DEFAULT_PANEL_PALETTE, {
38
- header: '#94a3b8',
39
- headerBg: '#1e293b',
40
36
  decision: '#38bdf8',
41
37
  constraint: '#f97316',
42
38
  incident: '#ef4444',
@@ -46,9 +42,6 @@ const C = extendPalette(DEFAULT_PANEL_PALETTE, {
46
42
  runbook: '#eab308',
47
43
  architecture: '#60a5fa',
48
44
  ownership: '#14b8a6',
49
- selected: '#1e3a5f',
50
- searchBg: '#0f172a',
51
- searchFg: '#e2e8f0',
52
45
  });
53
46
 
54
47
  // ---------------------------------------------------------------------------
@@ -103,14 +96,13 @@ function formatConfidence(confidence: number): string {
103
96
  // MemoryPanel
104
97
  // ---------------------------------------------------------------------------
105
98
 
106
- export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
99
+ export class MemoryPanel extends ScrollableListPanel<MemoryRecord> {
107
100
  private readonly registry: MemoryRegistry;
108
101
  private filterMode: FilterMode = 'all';
109
- private filterFocused = false;
110
102
  private unsubscribe?: () => void;
111
103
 
112
- // Review-mode confirm state (for destructive stale/contradict actions)
113
- private confirm: ConfirmState<{ id: string; action: 'stale' | 'contradicted' }> | null = null;
104
+ // Confirm state (for destructive stale/contradict/delete actions)
105
+ private confirm: ConfirmState<{ id: string; action: 'stale' | 'contradicted' | 'delete' }> | null = null;
114
106
 
115
107
  // Cached records for review-mode (reviewQueue-first, same as former KnowledgePanel)
116
108
  private reviewRecords: MemoryRecord[] = [];
@@ -118,6 +110,10 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
118
110
  constructor(registry: MemoryRegistry) {
119
111
  super('memory', 'Memory', 'M', 'agent');
120
112
  this.registry = registry;
113
+ // WO-153: converged modal '/' filter — only active in 'all' mode; review
114
+ // mode uses r/s/c/f/d single-letter actions instead of text filtering.
115
+ this.filterEnabled = true;
116
+ this.filterLabel = 'Search';
121
117
  }
122
118
 
123
119
  // ---------------------------------------------------------------------------
@@ -126,13 +122,11 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
126
122
 
127
123
  override onActivate(): void {
128
124
  super.onActivate();
129
- this.searchQuery = '';
130
- this.invalidateFilter();
131
- this.filterFocused = false;
125
+ this.filterQuery = '';
126
+ this.filterActive = false;
132
127
  this.confirm = null;
133
128
  this.refreshReviewRecords();
134
129
  this.unsubscribe = this.registry.subscribe(() => {
135
- this.invalidateFilter();
136
130
  this.refreshReviewRecords();
137
131
  this.markDirty();
138
132
  });
@@ -148,16 +142,11 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
148
142
  }
149
143
 
150
144
  // ---------------------------------------------------------------------------
151
- // SearchableListPanel implementation (used in 'all' filter mode)
145
+ // ScrollableListPanel implementation (filter is only enabled in 'all' mode)
152
146
  // ---------------------------------------------------------------------------
153
147
 
154
- protected getAllItems(): readonly MemoryRecord[] {
155
- return this.registry.search({ limit: 100 });
156
- }
157
-
158
- protected matchesSearch(record: MemoryRecord, query: string): boolean {
159
- const q = query.trim().toLowerCase();
160
- if (!q) return true;
148
+ /** `q` arrives already trimmed + lower-cased from ScrollableListPanel.getVisibleItems(). */
149
+ protected override filterMatches(record: MemoryRecord, q: string): boolean {
161
150
  const haystack = [
162
151
  record.summary,
163
152
  record.detail ?? '',
@@ -176,11 +165,11 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
176
165
  [' ', C.label, bg],
177
166
  [record.reviewState.padEnd(13), reviewStateColor(record.reviewState), bg],
178
167
  [` ${formatConfidence(record.confidence)} `, C.value, bg],
179
- [record.summary.slice(0, Math.max(0, width - 26)), C.value, bg],
168
+ [truncateDisplay(record.summary, Math.max(0, width - 26)), C.value, bg],
180
169
  ]);
181
170
  }
182
171
  // All-mode row: scope/class + id + time + summary (matches former MemoryPanel row)
183
- const bg = selected ? C.selected : undefined;
172
+ const bg = selected ? C.selectBg : undefined;
184
173
  return buildPanelLine(width, [
185
174
  [' ', C.label, bg],
186
175
  [`[${record.scope.slice(0, 1).toUpperCase()}/${record.cls.slice(0, 3).toUpperCase()}] `, classColor(record.cls), bg],
@@ -188,7 +177,7 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
188
177
  [' ', C.label, bg],
189
178
  [fmtTime(record.createdAt), C.dim, bg],
190
179
  [' ', C.label, bg],
191
- [record.summary.slice(0, Math.max(0, width - 33)), C.value, bg],
180
+ [truncateDisplay(record.summary, Math.max(0, width - 33)), C.value, bg],
192
181
  ]);
193
182
  }
194
183
 
@@ -196,9 +185,9 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
196
185
 
197
186
  protected override getEmptyStateMessage() {
198
187
  if (this.filterMode === 'review') return 'No records in the review queue.';
199
- return this.searchQuery
200
- ? ` No records matching "${this.searchQuery}"`
201
- : ' No memory records. Use /recall add <class> <summary> to create one.';
188
+ // The filter-narrowed-to-empty case is handled by ScrollableListPanel's
189
+ // own 'No matches for "..."' message (renderList) before this is reached.
190
+ return ' No memory records. Use /recall add <class> <summary> to create one.';
202
191
  }
203
192
 
204
193
  protected override getEmptyStateActions() {
@@ -214,14 +203,15 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
214
203
  // ---------------------------------------------------------------------------
215
204
 
216
205
  /**
217
- * Override getItems() so that ScrollableListPanel infrastructure (renderList,
218
- * clampSelection, navigation bounds) all operate on reviewRecords in review
219
- * mode preventing the render/action index desync where rendered list index N
220
- * did not correspond to reviewRecords[N] when the two lists differ.
206
+ * getItems() so that ScrollableListPanel infrastructure (renderList,
207
+ * clampSelection, navigation bounds, and — in 'all' mode the modal '/'
208
+ * filter) all operate on the right data set per mode preventing the
209
+ * render/action index desync where rendered list index N did not
210
+ * correspond to reviewRecords[N] when the two lists differ.
221
211
  */
222
- protected override getItems(): readonly MemoryRecord[] {
212
+ protected getItems(): readonly MemoryRecord[] {
223
213
  if (this.filterMode === 'review') return this.reviewRecords;
224
- return super.getItems();
214
+ return this.registry.search({ limit: 100 });
225
215
  }
226
216
 
227
217
  private refreshReviewRecords(): void {
@@ -229,11 +219,19 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
229
219
  this.clampSelection();
230
220
  }
231
221
 
222
+ /** Count of records beyond the 100-record fetch cap in getItems(). */
223
+ private getHiddenCount(): number {
224
+ const total = this.registry.search({}).length;
225
+ return Math.max(0, total - 100);
226
+ }
227
+
232
228
  private cycleFilter(): void {
233
229
  const modes: FilterMode[] = ['all', 'review'];
234
230
  const next = modes[(modes.indexOf(this.filterMode) + 1) % modes.length];
235
231
  this.filterMode = next;
236
- this.invalidateFilter();
232
+ // The modal '/' filter only applies in 'all' mode — review mode uses
233
+ // r/s/c/f/d single-letter actions instead of text filtering.
234
+ this.filterEnabled = next === 'all';
237
235
  this.refreshReviewRecords();
238
236
  this.markDirty();
239
237
  }
@@ -249,6 +247,16 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
249
247
  if (result === 'confirmed') {
250
248
  const { id, action } = this.confirm.subject;
251
249
  this.confirm = null;
250
+ if (action === 'delete') {
251
+ try {
252
+ this.registry.delete(id);
253
+ } catch (e) {
254
+ this.setError(`Delete failed: ${summarizeError(e)}`);
255
+ }
256
+ this.refreshReviewRecords();
257
+ this.markDirty();
258
+ return true;
259
+ }
252
260
  const record = this.reviewRecords.find((r) => r.id === id);
253
261
  if (record) {
254
262
  try {
@@ -291,7 +299,9 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
291
299
 
292
300
  // Review-mode specific actions (r/s/c/f)
293
301
  if (this.filterMode === 'review') {
294
- const selected = this.reviewRecords[this.selectedIndex];
302
+ // In review mode getItems() returns reviewRecords and filterEnabled is
303
+ // false, so getSelectedItem() resolves the highlighted review record.
304
+ const selected = this.getSelectedItem();
295
305
 
296
306
  if (key === 'enter' || key === 'return' || key === 'r') {
297
307
  if (!selected) return false;
@@ -306,13 +316,13 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
306
316
  }
307
317
  if (key === 's') {
308
318
  if (!selected) return false;
309
- this.confirm = { subject: { id: selected.id, action: 'stale' }, label: selected.summary.slice(0, 40) };
319
+ this.confirm = { subject: { id: selected.id, action: 'stale' }, label: truncateDisplay(selected.summary, 40) };
310
320
  this.markDirty();
311
321
  return true;
312
322
  }
313
323
  if (key === 'c') {
314
324
  if (!selected) return false;
315
- this.confirm = { subject: { id: selected.id, action: 'contradicted' }, label: selected.summary.slice(0, 40) };
325
+ this.confirm = { subject: { id: selected.id, action: 'contradicted' }, label: truncateDisplay(selected.summary, 40) };
316
326
  this.markDirty();
317
327
  return true;
318
328
  }
@@ -327,46 +337,35 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
327
337
  this.markDirty();
328
338
  return true;
329
339
  }
330
- }
331
-
332
- // All-mode: search filter focus
333
- if (this.filterMode === 'all') {
334
- if (this.filterFocused) {
335
- const items = this.getItems();
336
- const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.selectedIndex, itemCount: items.length });
337
- if (transition === 'focus-list') {
338
- this.filterFocused = false;
339
- this.markDirty();
340
- return true;
341
- }
342
- if (isPanelSearchCancel(key)) {
343
- this.filterFocused = false;
344
- return super.handleInput(key);
345
- }
346
- return super.handleInput(key);
340
+ if (key === 'd') {
341
+ if (!selected) return false;
342
+ this.confirm = { subject: { id: selected.id, action: 'delete' }, label: truncateDisplay(selected.summary, 40) };
343
+ this.markDirty();
344
+ return true;
347
345
  }
346
+ }
348
347
 
349
- const items = this.getItems();
350
- const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.selectedIndex, itemCount: items.length });
351
- if (transition === 'focus-search') {
352
- this.filterFocused = true;
348
+ // All-mode: 'r' reload and 'd' delete are single-letter actions that
349
+ // coexist with the modal '/' filter they only fire outside filter
350
+ // mode, so they remain typeable into the filter query while it's active.
351
+ if (this.filterMode === 'all' && !this.filterActive) {
352
+ if (key === 'r') {
353
353
  this.markDirty();
354
354
  return true;
355
355
  }
356
356
 
357
- if (key === 'r') {
358
- this.invalidateFilter();
357
+ if (key === 'd') {
358
+ const selected = this.getSelectedItem();
359
+ if (!selected) return false;
360
+ this.confirm = { subject: { id: selected.id, action: 'delete' }, label: truncateDisplay(selected.summary, 40) };
359
361
  this.markDirty();
360
362
  return true;
361
363
  }
362
364
  }
363
365
 
364
- // In review mode, navigation keys (j/k/up/down/etc.) must bypass
365
- // SearchableListPanel's printable-character interception, which would
366
- // otherwise swallow single-char keys like 'j' and 'k' as search input.
367
- if (this.filterMode === 'review') {
368
- return ScrollableListPanel.prototype.handleInput.call(this, key);
369
- }
366
+ // Navigation + (in 'all' mode) the modal '/' filter: delegate to
367
+ // ScrollableListPanel. filterEnabled is false in review mode, so '/' is
368
+ // inert there and single-char keys like 'j'/'k' navigate normally.
370
369
  return super.handleInput(key);
371
370
  }
372
371
 
@@ -402,13 +401,16 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
402
401
  }
403
402
 
404
403
  private renderAllMode(width: number, height: number, filterToggleLine: Line): Line[] {
405
- const records = this.getItems();
404
+ // getVisibleItems() applies the modal '/' filter (getItems() is the
405
+ // unfiltered 100-cap fetch); the filter input line itself is
406
+ // auto-injected by renderList() below (filterEnabled=true in 'all' mode).
407
+ const records = this.getVisibleItems();
406
408
  const byClass = new Map<MemoryClass, number>();
407
409
  for (const record of records) {
408
410
  byClass.set(record.cls, (byClass.get(record.cls) ?? 0) + 1);
409
411
  }
410
412
 
411
- const filterInputLine = this.buildFilterInputLine(width, 'Search', this.filterFocused);
413
+ const hiddenCount = this.getHiddenCount();
412
414
 
413
415
  const summaryLines: Line[] = [
414
416
  buildKeyValueLine(width, [
@@ -419,11 +421,12 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
419
421
  { label: 'runbooks', value: String(byClass.get('runbook') ?? 0), valueColor: C.runbook },
420
422
  ], C),
421
423
  filterToggleLine,
422
- filterInputLine,
423
- buildGuidanceLine(width, '/recall review', 'review durable knowledge and queue posture from the command surface', C),
424
+ ...(hiddenCount > 0
425
+ ? [buildPanelLine(width, [[` +${hiddenCount} hidden (100-record cap)`, C.dim]])]
426
+ : []),
424
427
  ];
425
428
 
426
- const selected = records[this.selectedIndex];
429
+ const selected = this.getSelectedItem();
427
430
  const selectedLines: Line[] = [];
428
431
  if (selected) {
429
432
  selectedLines.push(buildKeyValueLine(width, [
@@ -449,7 +452,7 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
449
452
  header: summaryLines,
450
453
  footer: [
451
454
  ...selectedLines,
452
- buildPanelLine(width, [[' / search j/k or Up/Down move r reload Tab: Review Queue Esc clear search', C.dim]]),
455
+ buildPanelLine(width, [[' / search j/k or Up/Down move r reload d delete Tab: Review Queue Esc clear search', C.dim]]),
453
456
  ],
454
457
  });
455
458
  }
@@ -499,10 +502,9 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
499
502
  [' team ', C.label], [String(byScope.get('team') ?? 0), C.good],
500
503
  ]),
501
504
  filterToggleLine,
502
- buildGuidanceLine(width, '/recall review', 'work the stale and contradicted queue from the command surface', C),
503
505
  ];
504
506
 
505
- const selectedRecord = this.reviewRecords[this.selectedIndex];
507
+ const selectedRecord = this.getSelectedItem();
506
508
  const selectedLines: Line[] = [];
507
509
  if (selectedRecord) {
508
510
  selectedLines.push(buildPanelLine(width, [[' Selected', C.label]]));
@@ -549,7 +551,7 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
549
551
  header: [...classLines, ...reviewLines],
550
552
  footer: [
551
553
  ...(selectedLines.length > 0 ? selectedLines : []),
552
- buildPanelLine(width, [[' Tab: All Records Up/Down move r/Enter reviewed s stale c contradicted f fresh', C.dim]]),
554
+ buildPanelLine(width, [[' Tab: All Records Up/Down move r/Enter reviewed s stale c contradicted f fresh d delete', C.dim]]),
553
555
  ],
554
556
  });
555
557
  }