@pellux/goodvibes-tui 0.26.0 → 0.27.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 (79) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +12 -7
  3. package/docs/foundation-artifacts/operator-contract.json +4 -1
  4. package/package.json +2 -2
  5. package/src/cli/bundle-command.ts +2 -1
  6. package/src/cli/service-posture.ts +2 -1
  7. package/src/core/conversation-rendering.ts +2 -2
  8. package/src/core/conversation.ts +49 -2
  9. package/src/core/session-recovery.ts +24 -18
  10. package/src/core/system-message-router.ts +42 -26
  11. package/src/core/turn-event-wiring.ts +12 -16
  12. package/src/daemon/handlers/calendar/caldav-client.ts +2 -1
  13. package/src/daemon/handlers/inbox/index.ts +2 -1
  14. package/src/daemon/handlers/inbox/poller.ts +2 -1
  15. package/src/daemon/handlers/inbox/providers/discord.ts +2 -1
  16. package/src/daemon/handlers/inbox/providers/email.ts +2 -1
  17. package/src/daemon/handlers/inbox/providers/route-util.ts +2 -1
  18. package/src/daemon/handlers/inbox/providers/slack.ts +2 -1
  19. package/src/daemon/handlers/triage/integration.ts +2 -1
  20. package/src/daemon/handlers/triage/pipeline.ts +2 -1
  21. package/src/daemon/handlers/triage/tagger/discord.ts +2 -1
  22. package/src/daemon/handlers/triage/tagger/imap.ts +4 -3
  23. package/src/export/gist-uploader.ts +3 -1
  24. package/src/input/command-registry.ts +1 -0
  25. package/src/input/commands/cloudflare-runtime.ts +2 -1
  26. package/src/input/commands/guidance-runtime.ts +2 -4
  27. package/src/input/commands/health-runtime.ts +13 -7
  28. package/src/input/commands/knowledge.ts +2 -1
  29. package/src/input/commands/runtime-services.ts +40 -10
  30. package/src/input/handler-command-route.ts +1 -1
  31. package/src/input/handler-interactions.ts +2 -1
  32. package/src/input/handler-modal-routes.ts +2 -1
  33. package/src/input/handler-onboarding-cloudflare.ts +2 -1
  34. package/src/input/handler-onboarding.ts +8 -8
  35. package/src/input/handler-ui-state.ts +1 -1
  36. package/src/input/tts-settings-actions.ts +2 -1
  37. package/src/main.ts +52 -59
  38. package/src/panels/agent-inspector-panel.ts +72 -2
  39. package/src/panels/agent-logs-panel.ts +127 -19
  40. package/src/panels/base-panel.ts +2 -1
  41. package/src/panels/builtin/agent.ts +3 -1
  42. package/src/panels/builtin/development.ts +1 -0
  43. package/src/panels/builtin/session.ts +1 -1
  44. package/src/panels/context-visualizer-panel.ts +24 -14
  45. package/src/panels/cost-tracker-panel.ts +7 -13
  46. package/src/panels/debug-panel.ts +11 -22
  47. package/src/panels/docs-panel.ts +14 -24
  48. package/src/panels/file-explorer-panel.ts +2 -1
  49. package/src/panels/file-preview-panel.ts +2 -1
  50. package/src/panels/git-panel.ts +10 -17
  51. package/src/panels/marketplace-panel.ts +2 -1
  52. package/src/panels/memory-panel.ts +2 -1
  53. package/src/panels/project-planning-panel.ts +5 -4
  54. package/src/panels/provider-health-panel.ts +56 -67
  55. package/src/panels/schedule-panel.ts +4 -7
  56. package/src/panels/session-browser-panel.ts +13 -21
  57. package/src/panels/skills-panel.ts +2 -1
  58. package/src/panels/tasks-panel.ts +2 -7
  59. package/src/panels/thinking-panel.ts +6 -11
  60. package/src/panels/token-budget-panel.ts +31 -15
  61. package/src/panels/tool-inspector-panel.ts +10 -18
  62. package/src/panels/work-plan-panel.ts +2 -1
  63. package/src/panels/wrfc-panel.ts +37 -35
  64. package/src/renderer/agent-detail-modal.ts +2 -2
  65. package/src/renderer/modal-utils.ts +0 -10
  66. package/src/renderer/process-modal.ts +2 -2
  67. package/src/runtime/bootstrap-command-context.ts +3 -0
  68. package/src/runtime/bootstrap-command-parts.ts +3 -1
  69. package/src/runtime/bootstrap-core.ts +31 -31
  70. package/src/runtime/bootstrap-shell.ts +1 -0
  71. package/src/runtime/onboarding/apply.ts +4 -3
  72. package/src/runtime/onboarding/snapshot.ts +4 -3
  73. package/src/runtime/process-lifecycle.ts +195 -0
  74. package/src/runtime/services.ts +4 -1
  75. package/src/runtime/wrfc-persistence.ts +20 -5
  76. package/src/shell/ui-openers.ts +3 -2
  77. package/src/verification/live-verifier.ts +4 -3
  78. package/src/version.ts +1 -1
  79. package/src/core/context-auto-compact.ts +0 -110
@@ -18,14 +18,12 @@ import {
18
18
  buildStyledPanelLine,
19
19
  buildPanelWorkspace,
20
20
  DEFAULT_PANEL_PALETTE,
21
+ extendPalette,
21
22
  } from './polish.ts';
22
23
 
23
- const C = {
24
+ const C = extendPalette(DEFAULT_PANEL_PALETTE, {
24
25
  convFg: '#cc99ff',
25
- overFg: '#ff6666',
26
- barEmpty: '#333344',
27
- labelFg: '#8888bb',
28
- } as const;
26
+ });
29
27
 
30
28
 
31
29
 
@@ -49,8 +47,9 @@ export class ContextVisualizerPanel extends BasePanel {
49
47
  sessionMemoryStore: SessionMemoryQuery,
50
48
  private readonly configManager: Pick<ConfigManager, 'get'>,
51
49
  private getUsage?: () => { input: number; output: number; cacheRead: number; cacheWrite: number; model?: string },
52
- private contextLimit?: number,
50
+ private contextLimit?: number | (() => number),
53
51
  private sessionReadModel?: UiReadModel<UiSessionSnapshot>,
52
+ private getLastInputTokens?: () => number,
54
53
  ) {
55
54
  super('context', 'Context', 'C', 'ai');
56
55
  this.sessionMemoryStore = sessionMemoryStore;
@@ -80,7 +79,7 @@ export class ContextVisualizerPanel extends BasePanel {
80
79
  const pct = limit > 0 ? Math.min(100, Math.round((input / limit) * 100)) : 0;
81
80
  const barWidth = Math.max(1, width - 2);
82
81
  const overLimit = limit > 0 && input > limit;
83
- const fg = overLimit ? C.overFg : C.convFg;
82
+ const fg = overLimit ? C.bad : C.convFg;
84
83
  if (limit <= 0) {
85
84
  return buildPanelWorkspace(width, height, {
86
85
  title: ' Context Usage',
@@ -138,10 +137,10 @@ export class ContextVisualizerPanel extends BasePanel {
138
137
  private _renderBar(width: number, barWidth: number, input: number, limit: number): Line {
139
138
  const filled = limit > 0 ? Math.min(barWidth, Math.round((input / limit) * barWidth)) : 0;
140
139
  const overLimit = limit > 0 && input > limit;
141
- const barFg = overLimit ? C.overFg : C.convFg;
140
+ const barFg = overLimit ? C.bad : C.convFg;
142
141
  return buildMeterLine(width, filled, barWidth, {
143
142
  filled: barFg,
144
- empty: C.barEmpty,
143
+ empty: C.empty,
145
144
  label: DEFAULT_PANEL_PALETTE.dim,
146
145
  });
147
146
  }
@@ -151,7 +150,7 @@ export class ContextVisualizerPanel extends BasePanel {
151
150
  const valStr = formatK(val).padStart(7);
152
151
  const pctStr = `${pct}%`.padStart(5);
153
152
  return buildStyledPanelLine(width, [
154
- { text: labelPadded, fg: C.labelFg },
153
+ { text: labelPadded, fg: C.label },
155
154
  { text: valStr, fg },
156
155
  { text: ' ', fg: DEFAULT_PANEL_PALETTE.dim },
157
156
  { text: pctStr, fg },
@@ -159,10 +158,21 @@ export class ContextVisualizerPanel extends BasePanel {
159
158
  }
160
159
 
161
160
  private _refresh(): void {
162
- const usage = this.getUsage?.();
163
- if (usage) {
164
- this.snapshot.input = usage.input;
165
- this.snapshot.limit = this.contextLimit ?? 0;
161
+ // Resolve the context window live so the panel tracks /model switches when a
162
+ // getter is supplied; a plain number stays fixed at its provided value.
163
+ this.snapshot.limit =
164
+ typeof this.contextLimit === 'function' ? this.contextLimit() : (this.contextLimit ?? 0);
165
+ // Prefer the live per-call input occupancy (cache-inclusive, matching the
166
+ // Tokens panel and the auto-compaction threshold) when a getter is wired;
167
+ // otherwise fall back to the cumulative usage input.
168
+ const lastInput = this.getLastInputTokens?.();
169
+ if (lastInput !== undefined) {
170
+ this.snapshot.input = lastInput;
171
+ } else {
172
+ const usage = this.getUsage?.();
173
+ if (usage) {
174
+ this.snapshot.input = usage.input;
175
+ }
166
176
  }
167
177
  this.markDirty();
168
178
  }
@@ -14,6 +14,7 @@ import {
14
14
  buildStyledPanelLine,
15
15
  buildPanelWorkspace,
16
16
  resolveScrollablePanelSection,
17
+ extendPalette,
17
18
  DEFAULT_PANEL_PALETTE,
18
19
  type PanelWorkspaceSection,
19
20
  } from './polish.ts';
@@ -75,20 +76,13 @@ function buildSparkline(history: number[]): string {
75
76
  // Colour palette
76
77
  // ---------------------------------------------------------------------------
77
78
 
78
- const C = {
79
- header: '#ffffff',
80
- label: '#aaaaaa',
81
- value: '#00ff88',
79
+ const C = extendPalette(DEFAULT_PANEL_PALETTE, {
82
80
  cost: '#ffdd44',
83
- alert: '#ff4444',
84
- dim: '#555555',
85
81
  model: '#88aaff',
86
82
  running: '#88aaff',
87
- done: '#00ff88',
88
- failed: '#ff4444',
89
83
  separator: '#333333',
90
84
  bg: '',
91
- } as const;
85
+ });
92
86
 
93
87
  // ---------------------------------------------------------------------------
94
88
  // CostTrackerPanel
@@ -273,7 +267,7 @@ export class CostTrackerPanel extends BasePanel {
273
267
  const overBudget = this.budgetThreshold > 0 && sessionCost > this.budgetThreshold;
274
268
  const sparkline = buildSparkline(this.costHistory);
275
269
  const costStr = formatCost(sessionCost);
276
- const costFg = overBudget ? C.alert : C.cost;
270
+ const costFg = overBudget ? C.bad : C.cost;
277
271
  const budgetStr = this.budgetThreshold > 0
278
272
  ? ` / ${formatCost(this.budgetThreshold)}`
279
273
  : '';
@@ -281,7 +275,7 @@ export class CostTrackerPanel extends BasePanel {
281
275
  const sessionLines: Line[] = [
282
276
  this.renderKeyValue(width, ' Total', `${costStr}${budgetStr}${alertStr}`, costFg),
283
277
  ];
284
- if (sparkline.length > 0) sessionLines.push(this.renderLabeledLine(width, ' Trend', sparkline, C.value));
278
+ if (sparkline.length > 0) sessionLines.push(this.renderLabeledLine(width, ' Trend', sparkline, C.good));
285
279
  sessionLines.push(this.renderKeyValue(width, ' Input', formatTokens(this.sessionUsage.input), C.label));
286
280
  sessionLines.push(this.renderKeyValue(width, ' Output', formatTokens(this.sessionUsage.output), C.label));
287
281
  if (this.sessionUsage.cacheRead > 0 || this.sessionUsage.cacheWrite > 0) {
@@ -307,8 +301,8 @@ export class CostTrackerPanel extends BasePanel {
307
301
  ];
308
302
  for (const agent of agentList) {
309
303
  const statusFg = agent.status === 'running' ? C.running
310
- : agent.status === 'failed' ? C.failed
311
- : C.done;
304
+ : agent.status === 'failed' ? C.bad
305
+ : C.good;
312
306
  const statusIcon = agent.status === 'running' ? '…'
313
307
  : agent.status === 'failed' ? '✕'
314
308
  : '✓';
@@ -11,6 +11,7 @@ import {
11
11
  resolveScrollablePanelSection,
12
12
  resolveStackedScrollableSections,
13
13
  DEFAULT_PANEL_PALETTE,
14
+ extendPalette,
14
15
  type PanelWorkspaceSection,
15
16
  } from './polish.ts';
16
17
 
@@ -52,24 +53,12 @@ const MAX_ERROR_LOG = 20;
52
53
  // Colors
53
54
  // ---------------------------------------------------------------------------
54
55
 
55
- const C = {
56
- title: '#00ffff',
57
- ok: '#5fd700',
58
- error: '#ff5f5f',
59
- warn: '#ffaf00',
60
- label: '244',
61
- value: '252',
62
- dim: '240',
56
+ const C = extendPalette(DEFAULT_PANEL_PALETTE, {
63
57
  provName: '#e2e8f0',
64
- separator: '#374151',
65
58
  input: '#00ffff',
66
59
  output: '#d000ff',
67
- latGood: '#5fd700',
68
- latWarn: '#ffaf00',
69
- latBad: '#ff5f5f',
70
- sectionHdr: '238',
71
60
  colHdr: '242',
72
- } as const;
61
+ });
73
62
 
74
63
  const LATENCY_WARN_MS = 2_000;
75
64
  const LATENCY_BAD_MS = 5_000;
@@ -99,9 +88,9 @@ function fmtAgo(ts: number): string {
99
88
  }
100
89
 
101
90
  function latColor(ms: number): string {
102
- if (ms >= LATENCY_BAD_MS) return C.latBad;
103
- if (ms >= LATENCY_WARN_MS) return C.latWarn;
104
- return C.latGood;
91
+ if (ms >= LATENCY_BAD_MS) return C.bad;
92
+ if (ms >= LATENCY_WARN_MS) return C.warn;
93
+ return C.good;
105
94
  }
106
95
 
107
96
  function statusCodeFromError(msg: string): number {
@@ -346,9 +335,9 @@ export class DebugPanel extends BasePanel {
346
335
  { text: ' Calls: ', fg: C.label },
347
336
  { text: String(this._totalCalls), fg: C.value },
348
337
  { text: ' OK: ', fg: C.label },
349
- { text: String(okCount), fg: C.ok },
338
+ { text: String(okCount), fg: C.good },
350
339
  { text: ' Errors: ', fg: C.label },
351
- { text: String(errCount), fg: errCount > 0 ? C.error : C.dim },
340
+ { text: String(errCount), fg: errCount > 0 ? C.bad : C.dim },
352
341
  ]),
353
342
  ];
354
343
  }
@@ -372,7 +361,7 @@ export class DebugPanel extends BasePanel {
372
361
  private _callLogRow(e: ApiCallEntry, width: number): Line {
373
362
  const timeStr = fmtAgo(e.ts).padEnd(8);
374
363
  const statusChar = e.status === 'ok' ? '✓' : '✕';
375
- const statusFg = e.status === 'ok' ? C.ok : C.error;
364
+ const statusFg = e.status === 'ok' ? C.good : C.bad;
376
365
  const provStr = e.provider.slice(0, 11).padEnd(12);
377
366
  const modelStr = e.model.slice(0, 19).padEnd(20);
378
367
  const inStr = fmtTok(e.inputTokens).padStart(8);
@@ -391,7 +380,7 @@ export class DebugPanel extends BasePanel {
391
380
 
392
381
  // Append status code for errors
393
382
  if (e.status === 'error' && e.statusCode > 0) {
394
- segments.push({ text: ` [${e.statusCode}]`, fg: C.error });
383
+ segments.push({ text: ` [${e.statusCode}]`, fg: C.bad });
395
384
  }
396
385
 
397
386
  return buildStyledPanelLine(
@@ -414,7 +403,7 @@ export class DebugPanel extends BasePanel {
414
403
  const codeStr = e.statusCode > 0 ? `[${e.statusCode}] ` : '';
415
404
  const msgStr = (e.errorMessage ?? 'unknown error').slice(0, width - 12 - codeStr.length);
416
405
  const full = ` ${timeStr} ${codeStr}${msgStr}`;
417
- return this._textLine(full.slice(0, width), C.error, width);
406
+ return this._textLine(full.slice(0, width), C.bad, width);
418
407
  }
419
408
 
420
409
  // -------------------------------------------------------------------------
@@ -4,7 +4,7 @@
4
4
 
5
5
  import type { Line } from '../types/grid.ts';
6
6
  import { BasePanel } from './base-panel.ts';
7
- import { buildPanelLine, buildPanelWorkspace, buildSearchInputLine, resolveScrollablePanelSection, DEFAULT_PANEL_PALETTE } from './polish.ts';
7
+ import { buildPanelLine, buildPanelWorkspace, buildSearchInputLine, resolveScrollablePanelSection, extendPalette, DEFAULT_PANEL_PALETTE } from './polish.ts';
8
8
  import type { ProviderModelCatalogQuery, ToolCatalogQuery } from '../runtime/ui-service-queries.ts';
9
9
  import {
10
10
  getPanelSearchFocusTransition,
@@ -14,22 +14,12 @@ import {
14
14
  isPanelSearchPrintable,
15
15
  } from './search-focus.ts';
16
16
 
17
- const C = {
18
- headerBg: '#1a1a2e',
19
- headerFg: '#ffffff',
20
- statusBar: '#222233',
21
- statusFg: '#aaaaaa',
22
- sectionFg: '#00ffff',
23
- sectionBg: '#0d1a2a',
24
- toolFg: '#88ccff',
25
- descFg: '#aaaaaa',
26
- keyFg: '#ffcc44',
27
- valueFg: '#ccccdd',
28
- dimFg: '#555566',
29
- selected: '#00ffff',
30
- selectedBg: '#1a2a3a',
31
- searchFg: '#ffffff',
32
- } as const;
17
+ const C = extendPalette(DEFAULT_PANEL_PALETTE, {
18
+ // Panel-specific domain colors with no clean shared equivalent.
19
+ sectionFg: '#00ffff',
20
+ toolFg: '#88ccff',
21
+ selected: '#00ffff',
22
+ });
33
23
 
34
24
  // ---------------------------------------------------------------------------
35
25
  // Hardcoded keyboard shortcut reference
@@ -68,7 +58,7 @@ interface FlatRow {
68
58
  }
69
59
 
70
60
  function renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
71
- const bg = isCursor ? C.selectedBg : row.bg;
61
+ const bg = isCursor ? C.selectBg : row.bg;
72
62
  return buildPanelLine(width, [
73
63
  [isCursor ? '▸' : ' ', C.selected, bg],
74
64
  [row.text, isCursor ? C.selected : row.fg, bg],
@@ -217,13 +207,13 @@ export class DocsPanel extends BasePanel {
217
207
  const tools = this.toolRegistry?.list() ?? [];
218
208
  const filtered = q ? tools.filter(t => t.definition.name.toLowerCase().includes(q) || (t.definition.description ?? '').toLowerCase().includes(q)) : tools;
219
209
  if (filtered.length === 0) {
220
- rows.push({ kind: 'empty', text: ' No tools match.', fg: C.dimFg, bg: '' });
210
+ rows.push({ kind: 'empty', text: ' No tools match.', fg: C.dim, bg: '' });
221
211
  } else {
222
212
  rows.push({ kind: 'header', text: ` Tools (${filtered.length})`, fg: C.sectionFg, bg: C.sectionBg, bold: true });
223
213
  for (const tool of filtered) {
224
214
  rows.push({ kind: 'item', text: ` ${tool.definition.name}`, fg: C.toolFg, bg: '', bold: true });
225
215
  if (tool.definition.description) {
226
- rows.push({ kind: 'detail', text: ` ${tool.definition.description}`, fg: C.descFg, bg: '' });
216
+ rows.push({ kind: 'detail', text: ` ${tool.definition.description}`, fg: C.label, bg: '' });
227
217
  }
228
218
  const metadata: string[] = [];
229
219
  if (tool.definition.sideEffects && tool.definition.sideEffects.length > 0) {
@@ -239,7 +229,7 @@ export class DocsPanel extends BasePanel {
239
229
  metadata.push('streaming');
240
230
  }
241
231
  if (metadata.length > 0) {
242
- rows.push({ kind: 'detail', text: ` ${metadata.join(' | ')}`, fg: C.dimFg, bg: '' });
232
+ rows.push({ kind: 'detail', text: ` ${metadata.join(' | ')}`, fg: C.dim, bg: '' });
243
233
  }
244
234
  }
245
235
  }
@@ -247,7 +237,7 @@ export class DocsPanel extends BasePanel {
247
237
  const models = this.providerRegistry?.listModels() ?? [];
248
238
  const filtered = q ? models.filter(m => m.id.toLowerCase().includes(q) || m.displayName.toLowerCase().includes(q) || m.provider.toLowerCase().includes(q)) : models;
249
239
  if (filtered.length === 0) {
250
- rows.push({ kind: 'empty', text: ' No models match.', fg: C.dimFg, bg: '' });
240
+ rows.push({ kind: 'empty', text: ' No models match.', fg: C.dim, bg: '' });
251
241
  } else {
252
242
  // Group by provider
253
243
  const byProvider = new Map<string, typeof filtered>();
@@ -262,7 +252,7 @@ export class DocsPanel extends BasePanel {
262
252
  const ctxK = m.contextWindow > 0 ? `${(m.contextWindow / 1000).toFixed(0)}k` : '?';
263
253
  const caps = [m.contextWindow > 0 ? `ctx:${ctxK}` : ''].filter(Boolean).join(' ');
264
254
  rows.push({ kind: 'item', text: ` ${m.displayName} ${caps}`, fg: C.toolFg, bg: '' });
265
- rows.push({ kind: 'detail', text: ` ${m.id}`, fg: C.descFg, bg: '' });
255
+ rows.push({ kind: 'detail', text: ` ${m.id}`, fg: C.label, bg: '' });
266
256
  }
267
257
  }
268
258
  }
@@ -272,7 +262,7 @@ export class DocsPanel extends BasePanel {
272
262
  rows.push({ kind: 'header', text: ' Keyboard Shortcuts', fg: C.sectionFg, bg: C.sectionBg, bold: true });
273
263
  for (const s of filtered) {
274
264
  const key = s.key.padEnd(16);
275
- rows.push({ kind: 'item', text: ` ${key} ${s.desc}`, fg: C.valueFg, bg: '' });
265
+ rows.push({ kind: 'item', text: ` ${key} ${s.desc}`, fg: C.value, bg: '' });
276
266
  }
277
267
  }
278
268
 
@@ -24,6 +24,7 @@ import {
24
24
  isPanelSearchCommit,
25
25
  isPanelSearchPrintable,
26
26
  } from './search-focus.ts';
27
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
27
28
 
28
29
  // ---------------------------------------------------------------------------
29
30
  // Constants
@@ -342,7 +343,7 @@ export class FileExplorerPanel extends BasePanel {
342
343
  this.cacheValid = true;
343
344
  });
344
345
  } catch (err) {
345
- this.setError(err instanceof Error ? err.message : String(err));
346
+ this.setError(summarizeError(err));
346
347
  }
347
348
  this.markDirty();
348
349
  })();
@@ -13,6 +13,7 @@ import {
13
13
  resolveScrollablePanelSection,
14
14
  DEFAULT_PANEL_PALETTE,
15
15
  } from './polish.ts';
16
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
16
17
 
17
18
  // ─── Constants ────────────────────────────────────────────────────────────────
18
19
 
@@ -144,7 +145,7 @@ export class FilePreviewPanel extends BasePanel {
144
145
  this.clampScroll(0);
145
146
  });
146
147
  } catch (err) {
147
- this.setError(err instanceof Error ? err.message : String(err));
148
+ this.setError(summarizeError(err));
148
149
  }
149
150
  this.markDirty();
150
151
  }
@@ -10,6 +10,7 @@ import {
10
10
  resolveScrollablePanelSection,
11
11
  buildStyledPanelLine,
12
12
  DEFAULT_PANEL_PALETTE,
13
+ extendPalette,
13
14
  } from './polish.ts';
14
15
 
15
16
  // ---------------------------------------------------------------------------
@@ -56,25 +57,17 @@ const MIN_VISIBLE_DIFF_LINES = 5;
56
57
  // Colors
57
58
  // ---------------------------------------------------------------------------
58
59
 
59
- const C = {
60
+ const C = extendPalette(DEFAULT_PANEL_PALETTE, {
60
61
  branch: '#00d7ff',
61
- clean: '#5fd700',
62
- dirty: '#ffaf00',
63
- ahead: '#5fd700',
64
- behind: '#ff5f5f',
65
62
  sectionHeader: '244',
66
- staged: '#5fd700',
67
- unstaged: '#ff5f5f',
68
63
  commit: '250',
69
64
  commitHash: '238',
70
65
  commitAuthor: '244',
71
66
  selected: '#1c1c1c',
72
67
  selectedFg: '#ffffff',
73
- diffAdd: '#5fd700',
74
- diffRemove: '#ff5f5f',
75
68
  diffMeta: '#5f87ff',
76
69
  diffNeutral: '250',
77
- } as const;
70
+ });
78
71
 
79
72
  // ---------------------------------------------------------------------------
80
73
  // GitPanel
@@ -352,7 +345,7 @@ export class GitPanel extends BasePanel {
352
345
  return this.renderMessage(width, height, 'Loading git status...', C.branch);
353
346
  }
354
347
  if (this.error) {
355
- return this.renderMessage(width, height, `Git error: ${this.error}`, C.unstaged);
348
+ return this.renderMessage(width, height, `Git error: ${this.error}`, C.bad);
356
349
  }
357
350
  // I3: spinner during openDiff() async fetch
358
351
  if (this.loadingState === 'loading') {
@@ -398,15 +391,15 @@ export class GitPanel extends BasePanel {
398
391
  x = this.paintText(line, this.data.branch, x, width, C.branch, { bold: true });
399
392
 
400
393
  if (this.data.ahead > 0) {
401
- x = this.paintText(line, ` +${this.data.ahead}`, x, width, C.ahead);
394
+ x = this.paintText(line, ` +${this.data.ahead}`, x, width, C.good);
402
395
  }
403
396
  if (this.data.behind > 0) {
404
- x = this.paintText(line, ` -${this.data.behind}`, x, width, C.behind);
397
+ x = this.paintText(line, ` -${this.data.behind}`, x, width, C.bad);
405
398
  }
406
399
 
407
400
  const isDirty = this.data.stagedFiles.length > 0 || this.data.unstagedFiles.length > 0;
408
401
  const statusText = isDirty ? ' * dirty' : ' y clean';
409
- const statusFg = isDirty ? C.dirty : C.clean;
402
+ const statusFg = isDirty ? C.warn : C.good;
410
403
  this.paintText(line, statusText, x, width, statusFg);
411
404
 
412
405
  return line;
@@ -418,7 +411,7 @@ export class GitPanel extends BasePanel {
418
411
 
419
412
  private renderFileRow(entry: GitFileEntry, selected: boolean, width: number): Line {
420
413
  const line = createEmptyLine(width);
421
- const fg = entry.staged ? C.staged : C.unstaged;
414
+ const fg = entry.staged ? C.good : C.bad;
422
415
  const prefix = ' ';
423
416
  const label = `${prefix}${entry.path}`;
424
417
 
@@ -602,9 +595,9 @@ export class GitPanel extends BasePanel {
602
595
  const dLine = createEmptyLine(width);
603
596
  let fg: string;
604
597
  if (rawLine.startsWith('+') && !rawLine.startsWith('+++')) {
605
- fg = C.diffAdd;
598
+ fg = C.good;
606
599
  } else if (rawLine.startsWith('-') && !rawLine.startsWith('---')) {
607
- fg = C.diffRemove;
600
+ fg = C.bad;
608
601
  } else if (rawLine.startsWith('@@') || rawLine.startsWith('diff') || rawLine.startsWith('index')) {
609
602
  fg = C.diffMeta;
610
603
  } else {
@@ -18,6 +18,7 @@ import {
18
18
  type EcosystemEntryKind,
19
19
  } from '@/runtime/index.ts';
20
20
  import type { UiMarketplaceSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
21
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
21
22
 
22
23
  const C = {
23
24
  ...DEFAULT_PANEL_PALETTE,
@@ -114,7 +115,7 @@ export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
114
115
  this.clearError();
115
116
  } catch (e) {
116
117
  // I2: surface catalog load failure
117
- this.setError(`Catalog load failed: ${e instanceof Error ? e.message : String(e)}`);
118
+ this.setError(`Catalog load failed: ${summarizeError(e)}`);
118
119
  }
119
120
  }
120
121
 
@@ -28,6 +28,7 @@ import {
28
28
  getPanelSearchFocusTransition,
29
29
  isPanelSearchCancel,
30
30
  } from './search-focus.ts';
31
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
31
32
 
32
33
  // ---------------------------------------------------------------------------
33
34
  // Colour palette
@@ -267,7 +268,7 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
267
268
  });
268
269
  }
269
270
  } catch (e) {
270
- this.setError(`Review update failed: ${e instanceof Error ? e.message : String(e)}`);
271
+ this.setError(`Review update failed: ${summarizeError(e)}`);
271
272
  }
272
273
  }
273
274
  this.refreshReviewRecords();
@@ -23,6 +23,7 @@ import {
23
23
  resolveScrollablePanelSection,
24
24
  type PanelWorkspaceSection,
25
25
  } from './polish.ts';
26
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
26
27
 
27
28
  const C = extendPalette(DEFAULT_PANEL_PALETTE, {
28
29
  planning: '#38bdf8',
@@ -512,7 +513,7 @@ export class ProjectPlanningPanel extends BasePanel {
512
513
  };
513
514
  this.clearError();
514
515
  } catch (err) {
515
- this.setError(err instanceof Error ? err.message : String(err));
516
+ this.setError(summarizeError(err));
516
517
  } finally {
517
518
  this.loading = false;
518
519
  this.markDirty();
@@ -647,7 +648,7 @@ export class ProjectPlanningPanel extends BasePanel {
647
648
  this.refresh(true);
648
649
  this.registerTimer(setTimeout(() => this.refresh(true), 250));
649
650
  } catch (err) {
650
- this.setError(err instanceof Error ? err.message : String(err));
651
+ this.setError(summarizeError(err));
651
652
  this.requestRender();
652
653
  }
653
654
  })();
@@ -698,7 +699,7 @@ export class ProjectPlanningPanel extends BasePanel {
698
699
  this.dismissPlanning?.();
699
700
  this.refresh(true);
700
701
  } catch (err) {
701
- this.setError(err instanceof Error ? err.message : String(err));
702
+ this.setError(summarizeError(err));
702
703
  this.requestRender();
703
704
  }
704
705
  })();
@@ -741,7 +742,7 @@ export class ProjectPlanningPanel extends BasePanel {
741
742
  });
742
743
  this.refresh(true);
743
744
  } catch (err) {
744
- this.setError(err instanceof Error ? err.message : String(err));
745
+ this.setError(summarizeError(err));
745
746
  this.requestRender();
746
747
  }
747
748
  })();