@pellux/goodvibes-tui 0.26.0 → 0.28.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 (148) hide show
  1. package/CHANGELOG.md +40 -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/context-usage.ts +53 -0
  8. package/src/core/conversation-rendering.ts +2 -2
  9. package/src/core/conversation.ts +53 -8
  10. package/src/core/session-recovery.ts +26 -18
  11. package/src/core/system-message-router.ts +42 -26
  12. package/src/core/turn-event-wiring.ts +13 -16
  13. package/src/daemon/handlers/calendar/caldav-client.ts +2 -1
  14. package/src/daemon/handlers/inbox/index.ts +2 -1
  15. package/src/daemon/handlers/inbox/poller.ts +2 -1
  16. package/src/daemon/handlers/inbox/providers/discord.ts +2 -1
  17. package/src/daemon/handlers/inbox/providers/email.ts +2 -1
  18. package/src/daemon/handlers/inbox/providers/route-util.ts +2 -1
  19. package/src/daemon/handlers/inbox/providers/slack.ts +2 -1
  20. package/src/daemon/handlers/triage/integration.ts +2 -1
  21. package/src/daemon/handlers/triage/pipeline.ts +2 -1
  22. package/src/daemon/handlers/triage/tagger/discord.ts +2 -1
  23. package/src/daemon/handlers/triage/tagger/imap.ts +4 -3
  24. package/src/export/gist-uploader.ts +3 -1
  25. package/src/input/command-registry.ts +1 -0
  26. package/src/input/commands/cloudflare-runtime.ts +2 -1
  27. package/src/input/commands/eval.ts +4 -3
  28. package/src/input/commands/guidance-runtime.ts +2 -4
  29. package/src/input/commands/health-runtime.ts +13 -7
  30. package/src/input/commands/knowledge.ts +2 -1
  31. package/src/input/commands/runtime-services.ts +40 -10
  32. package/src/input/handler-command-route.ts +1 -1
  33. package/src/input/handler-feed-routes.ts +61 -4
  34. package/src/input/handler-feed.ts +13 -0
  35. package/src/input/handler-interactions.ts +2 -1
  36. package/src/input/handler-modal-routes.ts +2 -1
  37. package/src/input/handler-onboarding-cloudflare.ts +2 -1
  38. package/src/input/handler-onboarding.ts +8 -8
  39. package/src/input/handler-picker-routes.ts +18 -13
  40. package/src/input/handler-ui-state.ts +5 -1
  41. package/src/input/keybindings.ts +5 -0
  42. package/src/input/model-picker.ts +5 -0
  43. package/src/input/panel-integration-actions.ts +10 -0
  44. package/src/input/tts-settings-actions.ts +2 -1
  45. package/src/main.ts +52 -60
  46. package/src/panels/agent-inspector-panel.ts +90 -15
  47. package/src/panels/agent-inspector-shared.ts +3 -3
  48. package/src/panels/agent-logs-panel.ts +149 -72
  49. package/src/panels/approval-panel.ts +146 -95
  50. package/src/panels/automation-control-panel.ts +24 -1
  51. package/src/panels/base-panel.ts +28 -3
  52. package/src/panels/builtin/agent.ts +3 -1
  53. package/src/panels/builtin/development.ts +2 -1
  54. package/src/panels/builtin/session.ts +1 -1
  55. package/src/panels/cockpit-panel.ts +24 -5
  56. package/src/panels/cockpit-read-model.ts +2 -1
  57. package/src/panels/communication-panel.ts +108 -43
  58. package/src/panels/context-visualizer-panel.ts +49 -15
  59. package/src/panels/control-plane-panel.ts +27 -1
  60. package/src/panels/cost-tracker-panel.ts +87 -65
  61. package/src/panels/debug-panel.ts +61 -37
  62. package/src/panels/diff-panel.ts +59 -30
  63. package/src/panels/docs-panel.ts +30 -24
  64. package/src/panels/eval-panel.ts +130 -81
  65. package/src/panels/expandable-list-panel.ts +189 -0
  66. package/src/panels/file-explorer-panel.ts +42 -42
  67. package/src/panels/file-preview-panel.ts +11 -3
  68. package/src/panels/forensics-panel.ts +27 -13
  69. package/src/panels/git-panel.ts +65 -84
  70. package/src/panels/hooks-panel.ts +36 -2
  71. package/src/panels/incident-review-panel.ts +31 -10
  72. package/src/panels/intelligence-panel.ts +152 -71
  73. package/src/panels/knowledge-graph-panel.ts +89 -27
  74. package/src/panels/local-auth-panel.ts +17 -11
  75. package/src/panels/marketplace-panel.ts +33 -13
  76. package/src/panels/memory-panel.ts +7 -5
  77. package/src/panels/ops-control-panel.ts +69 -7
  78. package/src/panels/ops-strategy-panel.ts +61 -43
  79. package/src/panels/orchestration-panel.ts +34 -4
  80. package/src/panels/panel-list-panel.ts +33 -8
  81. package/src/panels/panel-manager.ts +23 -4
  82. package/src/panels/plan-dashboard-panel.ts +183 -66
  83. package/src/panels/plugins-panel.ts +48 -10
  84. package/src/panels/policy-panel.ts +60 -23
  85. package/src/panels/polish-core.ts +157 -0
  86. package/src/panels/polish-tables.ts +258 -0
  87. package/src/panels/polish.ts +44 -145
  88. package/src/panels/project-planning-panel.ts +27 -4
  89. package/src/panels/provider-accounts-panel.ts +55 -18
  90. package/src/panels/provider-health-panel.ts +118 -87
  91. package/src/panels/provider-stats-panel.ts +47 -22
  92. package/src/panels/qr-panel.ts +23 -11
  93. package/src/panels/remote-panel.ts +12 -5
  94. package/src/panels/routes-panel.ts +27 -5
  95. package/src/panels/sandbox-panel.ts +62 -27
  96. package/src/panels/schedule-panel.ts +44 -24
  97. package/src/panels/scrollable-list-panel.ts +124 -10
  98. package/src/panels/security-panel.ts +72 -20
  99. package/src/panels/services-panel.ts +68 -22
  100. package/src/panels/session-browser-panel.ts +42 -26
  101. package/src/panels/session-maintenance.ts +2 -2
  102. package/src/panels/settings-sync-panel.ts +30 -15
  103. package/src/panels/skills-panel.ts +2 -1
  104. package/src/panels/subscription-panel.ts +21 -3
  105. package/src/panels/symbol-outline-panel.ts +40 -47
  106. package/src/panels/system-messages-panel.ts +60 -27
  107. package/src/panels/tasks-panel.ts +36 -14
  108. package/src/panels/thinking-panel.ts +32 -36
  109. package/src/panels/token-budget-panel.ts +80 -53
  110. package/src/panels/tool-inspector-panel.ts +37 -39
  111. package/src/panels/types.ts +25 -0
  112. package/src/panels/watchers-panel.ts +25 -5
  113. package/src/panels/work-plan-panel.ts +127 -35
  114. package/src/panels/worktree-panel.ts +65 -20
  115. package/src/panels/wrfc-panel-format.ts +133 -0
  116. package/src/panels/wrfc-panel.ts +53 -147
  117. package/src/renderer/agent-detail-modal.ts +2 -2
  118. package/src/renderer/compaction-preview.ts +2 -1
  119. package/src/renderer/compositor.ts +46 -43
  120. package/src/renderer/modal-utils.ts +0 -10
  121. package/src/renderer/model-picker-overlay.ts +9 -4
  122. package/src/renderer/model-workspace.ts +2 -3
  123. package/src/renderer/panel-composite.ts +7 -20
  124. package/src/renderer/panel-workspace-bar.ts +14 -8
  125. package/src/renderer/process-modal.ts +2 -2
  126. package/src/renderer/progress.ts +3 -2
  127. package/src/renderer/tab-strip.ts +148 -34
  128. package/src/renderer/ui-factory.ts +4 -6
  129. package/src/runtime/bootstrap-command-context.ts +3 -0
  130. package/src/runtime/bootstrap-command-parts.ts +3 -1
  131. package/src/runtime/bootstrap-core.ts +31 -31
  132. package/src/runtime/bootstrap-shell.ts +1 -0
  133. package/src/runtime/onboarding/apply.ts +4 -3
  134. package/src/runtime/onboarding/snapshot.ts +4 -3
  135. package/src/runtime/process-lifecycle.ts +195 -0
  136. package/src/runtime/services.ts +4 -1
  137. package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
  138. package/src/runtime/ui/model-picker/types.ts +4 -0
  139. package/src/runtime/wrfc-persistence.ts +20 -5
  140. package/src/shell/ui-openers.ts +3 -2
  141. package/src/utils/format-duration.ts +55 -0
  142. package/src/utils/format-number.ts +71 -0
  143. package/src/verification/live-verifier.ts +4 -3
  144. package/src/version.ts +1 -1
  145. package/src/core/context-auto-compact.ts +0 -110
  146. package/src/panels/panel-picker.ts +0 -106
  147. package/src/renderer/panel-picker-overlay.ts +0 -202
  148. package/src/renderer/panel-tab-bar.ts +0 -69
@@ -1,106 +0,0 @@
1
- import type { PanelRegistration, PanelCategory } from './types.ts';
2
-
3
- /** Display order for panel categories. */
4
- const CATEGORY_ORDER: PanelCategory[] = ['development', 'agent', 'monitoring', 'session', 'ai'];
5
-
6
- /** Human-readable labels for each category. */
7
- const CATEGORY_LABELS: Record<PanelCategory, string> = {
8
- development: 'Development',
9
- agent: 'Agent',
10
- monitoring: 'Monitoring',
11
- session: 'Session',
12
- ai: 'AI',
13
- };
14
-
15
- /**
16
- * Modal state for browsing and selecting panel registrations.
17
- * The filtered list is a flat list of PanelRegistration items matching the
18
- * current search query. Category headers are rendered by the overlay, not
19
- * stored here — keeping this class pure data/logic.
20
- */
21
- export class PanelPicker {
22
- public active: boolean = false;
23
- public selectedIndex: number = 0;
24
- public searchQuery: string = '';
25
- private items: PanelRegistration[] = [];
26
- private filtered: PanelRegistration[] = [];
27
-
28
- /** Open the picker with the given set of registered panels. */
29
- open(registrations: PanelRegistration[]): void {
30
- this.items = registrations;
31
- this.searchQuery = '';
32
- this.filtered = this._applyFilter('');
33
- this.selectedIndex = 0;
34
- this.active = true;
35
- }
36
-
37
- /** Close and reset the picker. */
38
- close(): void {
39
- this.active = false;
40
- this.searchQuery = '';
41
- this.selectedIndex = 0;
42
- }
43
-
44
- /**
45
- * Filter the list by name or description (case-insensitive).
46
- * Resets selectedIndex to 0.
47
- */
48
- search(query: string): void {
49
- this.searchQuery = query;
50
- this.filtered = this._applyFilter(query);
51
- this.selectedIndex = 0;
52
- }
53
-
54
- /** Move selection up by one, wrapping at top. */
55
- moveUp(): void {
56
- if (this.filtered.length === 0) return;
57
- this.selectedIndex = this.selectedIndex <= 0
58
- ? this.filtered.length - 1
59
- : this.selectedIndex - 1;
60
- }
61
-
62
- /** Move selection down by one, wrapping at bottom. */
63
- moveDown(): void {
64
- if (this.filtered.length === 0) return;
65
- this.selectedIndex = this.selectedIndex >= this.filtered.length - 1
66
- ? 0
67
- : this.selectedIndex + 1;
68
- }
69
-
70
- /**
71
- * Returns the filtered list, sorted by category order then name.
72
- * The overlay is responsible for inserting category headers between groups.
73
- */
74
- getVisible(): PanelRegistration[] {
75
- return this.filtered;
76
- }
77
-
78
- /** Returns the currently selected registration, or null if the list is empty. */
79
- getSelected(): PanelRegistration | null {
80
- if (this.filtered.length === 0) return null;
81
- return this.filtered[this.selectedIndex] ?? null;
82
- }
83
-
84
- // ── Private ──────────────────────────────────────────────────────────────
85
-
86
- private _applyFilter(query: string): PanelRegistration[] {
87
- const q = query.trim().toLowerCase();
88
- const source = q
89
- ? this.items.filter(
90
- r =>
91
- r.name.toLowerCase().includes(q) ||
92
- r.description.toLowerCase().includes(q),
93
- )
94
- : [...this.items];
95
-
96
- // Sort by category order, then alphabetically within each category
97
- return source.sort((a, b) => {
98
- const ai = CATEGORY_ORDER.indexOf(a.category);
99
- const bi = CATEGORY_ORDER.indexOf(b.category);
100
- if (ai !== bi) return ai - bi;
101
- return a.name.localeCompare(b.name);
102
- });
103
- }
104
- }
105
-
106
- export { CATEGORY_LABELS, CATEGORY_ORDER };
@@ -1,202 +0,0 @@
1
- import { type Line } from '../types/grid.ts';
2
- import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
3
- import type { PanelPicker } from '../panels/panel-picker.ts';
4
- import { CATEGORY_LABELS, CATEGORY_ORDER } from '../panels/panel-picker.ts';
5
- import type { PanelCategory, PanelRegistration } from '../panels/types.ts';
6
- import {
7
- createOverlayBorderLine,
8
- createOverlayBoxLayout,
9
- createOverlayContentLine,
10
- DEFAULT_OVERLAY_PALETTE,
11
- putOverlayText,
12
- } from './overlay-box.ts';
13
- import { getOverlaySurfaceMetrics } from './overlay-viewport.ts';
14
-
15
- const TITLE_FG = '#cbd5e1';
16
- const CATEGORY_FG = '#94a3b8';
17
- const SELECTED_FG = '#e2e8f0';
18
- const SELECTED_BG = '#1e293b';
19
- const BODY_FG = '252';
20
- const BORDER_FG = DEFAULT_OVERLAY_PALETTE.borderFg;
21
-
22
- /**
23
- * Render the panel picker modal as Line[] for overlay in the viewport.
24
- * Panels are grouped by category. Category headers are inserted between groups.
25
- * When a search query is active a search-bar row is shown beneath the title.
26
- */
27
- export function renderPanelPickerOverlay(
28
- picker: PanelPicker,
29
- width: number,
30
- viewportHeight = 24,
31
- ): Line[] {
32
- if (!picker.active) return [];
33
-
34
- const lines: Line[] = [];
35
- const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
36
- margin: 4,
37
- maxWidth: 72,
38
- chromeRows: picker.searchQuery.length > 0 ? 7 : 6,
39
- minContentRows: 6,
40
- maxContentRows: 10,
41
- });
42
- const layout = createOverlayBoxLayout(width, metrics.margin, metrics.boxWidth);
43
- const contentW = layout.innerWidth;
44
- const titleFg = TITLE_FG;
45
- const borderFg = BORDER_FG;
46
-
47
- // ── Title bar ──────────────────────────────────────────────────────────────
48
- const titleLine = createOverlayBorderLine(width, layout, '┌', '─', '┐', borderFg);
49
- putOverlayText(titleLine, layout.margin + 2, layout.width - 4, 'Open Panel Workspace', { fg: titleFg, bold: true });
50
- lines.push(titleLine);
51
-
52
- // ── Search bar (shown when query is non-empty) ──────────────────────────────
53
- if (picker.searchQuery.length > 0) {
54
- const searchLine = createOverlayContentLine(width, layout, borderFg);
55
- const searchLabel = '\u2315 ';
56
- const queryAvail = contentW - getDisplayWidth(searchLabel);
57
- const queryText = fitDisplay(picker.searchQuery, queryAvail);
58
- putOverlayText(searchLine, layout.margin + 2, getDisplayWidth(searchLabel), searchLabel, { fg: titleFg });
59
- putOverlayText(searchLine, layout.margin + 2 + getDisplayWidth(searchLabel), queryAvail, queryText, { fg: titleFg });
60
- lines.push(searchLine);
61
-
62
- lines.push(createOverlayBorderLine(width, layout, '├', '─', '┤', borderFg));
63
- } else {
64
- const placeholder = ' Browse by category or start typing to filter by panel name, purpose, or category.';
65
- const row = createOverlayContentLine(width, layout, borderFg);
66
- putOverlayText(row, layout.margin + 2, contentW, fitDisplay(placeholder, contentW), { fg: '244', dim: true });
67
- lines.push(row);
68
- }
69
-
70
- const visible = picker.getVisible();
71
-
72
- if (visible.length === 0) {
73
- const noResults = 'No panels match your search';
74
- const noRow = createOverlayContentLine(width, layout, borderFg);
75
- putOverlayText(noRow, layout.margin + 2, contentW, fitDisplay(noResults, contentW), { fg: '244', dim: true });
76
- lines.push(noRow);
77
- } else {
78
- // Build a flat render list of { type: 'header' | 'item', ... } entries
79
- // so we can compute the correct scroll window over all rows.
80
- type HeaderEntry = { type: 'header'; category: PanelCategory };
81
- type ItemEntry = { type: 'item'; reg: PanelRegistration; flatIndex: number };
82
- type RenderEntry = HeaderEntry | ItemEntry;
83
-
84
- const renderEntries: RenderEntry[] = [];
85
-
86
- // When no search query, group by category; when searching, show flat list
87
- // with a single "Results" header.
88
- if (picker.searchQuery.length === 0) {
89
- const byCategory = new Map<PanelCategory, PanelRegistration[]>();
90
- for (const reg of visible) {
91
- const group = byCategory.get(reg.category) ?? [];
92
- group.push(reg);
93
- byCategory.set(reg.category, group);
94
- }
95
- let flatIndex = 0;
96
- for (const cat of CATEGORY_ORDER) {
97
- const group = byCategory.get(cat);
98
- if (!group || group.length === 0) continue;
99
- renderEntries.push({ type: 'header', category: cat });
100
- for (const reg of group) {
101
- renderEntries.push({ type: 'item', reg, flatIndex });
102
- flatIndex++;
103
- }
104
- }
105
- } else {
106
- for (let i = 0; i < visible.length; i++) {
107
- renderEntries.push({ type: 'item', reg: visible[i], flatIndex: i });
108
- }
109
- }
110
-
111
- // Compute scroll window: ensure the selected item row stays visible.
112
- // Find the render-entry index of the selected item.
113
- const selectedEntryIdx = renderEntries.findIndex(
114
- e => e.type === 'item' && e.flatIndex === picker.selectedIndex,
115
- );
116
- const total = renderEntries.length;
117
- const maxVisible = metrics.contentRows;
118
- let startEntry = 0;
119
- if (total > maxVisible && selectedEntryIdx >= 0) {
120
- startEntry = Math.max(
121
- 0,
122
- Math.min(
123
- selectedEntryIdx - Math.floor(maxVisible / 2),
124
- total - maxVisible,
125
- ),
126
- );
127
- }
128
- const endEntry = Math.min(startEntry + maxVisible, total);
129
-
130
- for (let i = startEntry; i < endEntry; i++) {
131
- const entry = renderEntries[i];
132
-
133
- if (entry.type === 'header') {
134
- // Category header row
135
- const label = CATEGORY_LABELS[entry.category].toUpperCase();
136
- const headerRow = createOverlayContentLine(width, layout, borderFg);
137
- putOverlayText(headerRow, layout.margin + 2, contentW, fitDisplay(` ${label}`, contentW), { fg: CATEGORY_FG, dim: true });
138
- lines.push(headerRow);
139
- } else {
140
- const { reg, flatIndex } = entry;
141
- const isSelected = flatIndex === picker.selectedIndex;
142
- const indicator = isSelected ? '\u25b6 ' : ' ';
143
-
144
- // icon (1 char) + 2 spaces + name
145
- const iconStr = reg.icon + ' ';
146
- const iconW = getDisplayWidth(iconStr);
147
-
148
- // separator between name and description: ' \u2014 '
149
- const sep = ' \u2014 ';
150
- const sepW = getDisplayWidth(sep);
151
-
152
- // Allocate space: indicator(2) + icon(iconW) + name + sep + desc fills contentW
153
- const nameMaxW = Math.max(8, Math.floor((contentW - 2 - iconW - sepW) * 0.35));
154
- const descMaxW = contentW - 2 - iconW - nameMaxW - sepW;
155
-
156
- const nameRaw = reg.name;
157
- const nameStr = fitDisplay(nameRaw, nameMaxW);
158
-
159
- const descRaw = reg.description;
160
- const descStr = fitDisplay(descRaw, descMaxW);
161
-
162
- const row = createOverlayContentLine(width, layout, borderFg, isSelected ? SELECTED_BG : '');
163
- const rowText = indicator + iconStr + nameStr + sep + descStr;
164
- putOverlayText(row, layout.margin + 2, contentW, fitDisplay(truncateDisplay(rowText, contentW), contentW), {
165
- fg: isSelected ? SELECTED_FG : BODY_FG,
166
- bg: isSelected ? SELECTED_BG : '',
167
- bold: isSelected,
168
- });
169
- lines.push(row);
170
- }
171
- }
172
-
173
- const selected = picker.getSelected();
174
- if (selected) {
175
- lines.push(createOverlayBorderLine(width, layout, '├', '─', '┤', borderFg));
176
- const categoryLabel = CATEGORY_LABELS[selected.category].toUpperCase();
177
- const selectedLine = createOverlayContentLine(width, layout, borderFg);
178
- putOverlayText(selectedLine, layout.margin + 2, contentW, fitDisplay(`${selected.icon} ${selected.name} [${categoryLabel}]`, contentW), { fg: SELECTED_FG });
179
- lines.push(selectedLine);
180
- const desc = fitDisplay(truncateDisplay(selected.description, contentW), contentW);
181
- const descRow = createOverlayContentLine(width, layout, borderFg);
182
- putOverlayText(descRow, layout.margin + 2, contentW, desc, { fg: '244', dim: true });
183
- lines.push(descRow);
184
- }
185
-
186
- // ── Scroll indicator ───────────────────────────────────────────────────
187
- if (total > maxVisible) {
188
- const scrollInfo = `${picker.selectedIndex + 1}/${visible.length}`;
189
- const scrollRow = createOverlayContentLine(width, layout, borderFg);
190
- putOverlayText(scrollRow, layout.margin + 2 + Math.max(0, contentW - getDisplayWidth(scrollInfo)), getDisplayWidth(scrollInfo), scrollInfo, { fg: '240', dim: true });
191
- lines.push(scrollRow);
192
- }
193
- }
194
-
195
- // ── Bottom border with hints ───────────────────────────────────────────────
196
- const hints = '[Up/Down] Navigate [Enter] Open [/] Filter [Esc] Cancel';
197
- const bottomLine = createOverlayBorderLine(width, layout, '└', '─', '┘', borderFg);
198
- putOverlayText(bottomLine, layout.margin + 2, layout.width - 4, truncateDisplay(hints, layout.width - 4), { fg: '240', dim: true });
199
- lines.push(bottomLine);
200
-
201
- return lines;
202
- }
@@ -1,69 +0,0 @@
1
- import { type Line } from '../types/grid.ts';
2
- import type { Panel } from '../panels/types.ts';
3
- import { renderTabStrip } from './tab-strip.ts';
4
-
5
- const ACTIVE_FG = '#e2e8f0';
6
- const ACTIVE_FG_UNFOCUSED = '#94a3b8';
7
- const ACTIVE_BG = '#1e293b';
8
- const INACTIVE_FG = '244';
9
- const SEPARATOR_FG = '238';
10
- const CLOSE_FG = '238';
11
- const LABEL_FG = '#cbd5e1';
12
- const LABEL_BG = '#0f172a';
13
-
14
- /**
15
- * Render the panel tab bar.
16
- *
17
- * Shows open panel tabs with the active one highlighted.
18
- * Format: │ icon Name │ icon Name │ ...
19
- *
20
- * Active tab: slate-white, bold, bg #1e293b
21
- * Inactive tab: grey (244), no background
22
- * Separators: │ in dim grey (238)
23
- * Overflow: > right scroll indicator (stateless - shows when tabs extend beyond width)
24
- * Close button: x at far right for active tab
25
- */
26
- export function renderPanelTabBar(
27
- panels: Panel[],
28
- activeIndex: number,
29
- width: number,
30
- focused: boolean = true,
31
- paneLabel?: string,
32
- ): Line {
33
- if (panels.length === 0) return renderTabStrip({ width, tabs: [], style: {
34
- activeFg: ACTIVE_FG,
35
- activeBg: ACTIVE_BG,
36
- activeBold: focused,
37
- inactiveFg: INACTIVE_FG,
38
- separatorFg: SEPARATOR_FG,
39
- labelFg: LABEL_FG,
40
- labelBg: focused ? LABEL_BG : '',
41
- labelBold: focused,
42
- overflowFg: SEPARATOR_FG,
43
- trailingFg: focused ? ACTIVE_FG_UNFOCUSED : INACTIVE_FG,
44
- } });
45
-
46
- const tabs = panels.map((panel, index) => ({
47
- label: `${panel.icon} ${panel.name}`,
48
- active: index === activeIndex,
49
- }));
50
-
51
- return renderTabStrip({
52
- width,
53
- tabs,
54
- prefixLabel: paneLabel ? ` ${paneLabel.toUpperCase()} ` : undefined,
55
- suffixLabel: ` ${panels.length} x `,
56
- style: {
57
- activeFg: focused ? ACTIVE_FG : ACTIVE_FG_UNFOCUSED,
58
- activeBg: ACTIVE_BG,
59
- activeBold: focused,
60
- inactiveFg: INACTIVE_FG,
61
- separatorFg: SEPARATOR_FG,
62
- labelFg: LABEL_FG,
63
- labelBg: focused ? LABEL_BG : '',
64
- labelBold: focused,
65
- overflowFg: SEPARATOR_FG,
66
- trailingFg: CLOSE_FG,
67
- },
68
- });
69
- }