@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
@@ -5,7 +5,6 @@ export { PanelManager } from './panel-manager.ts';
5
5
  export { TokenBudgetPanel } from './token-budget-panel.ts';
6
6
  export { CostTrackerPanel } from './cost-tracker-panel.ts';
7
7
  export { AgentInspectorPanel } from './agent-inspector-panel.ts';
8
- export { AgentLogsPanel } from './agent-logs-panel.ts';
9
8
  export { WrfcPanel } from './wrfc-panel.ts';
10
9
  export { ProviderHealthPanel } from './provider-health-panel.ts';
11
10
  export { ProviderHealthTracker } from './provider-health-tracker.ts';
@@ -13,17 +12,14 @@ export type { ProviderHealth, ProviderStatus } from './provider-health-tracker.t
13
12
  export { GitPanel } from './git-panel.ts';
14
13
  export { SymbolOutlinePanel } from './symbol-outline-panel.ts';
15
14
  export type { SymbolEntry, SymbolKind } from './symbol-outline-panel.ts';
16
- export { ProviderStatsPanel } from './provider-stats-panel.ts';
17
15
  export { SessionBrowserPanel } from './session-browser-panel.ts';
18
16
  export { DocsPanel } from './docs-panel.ts';
19
17
  export { ThinkingPanel } from './thinking-panel.ts';
20
18
  export { ToolInspectorPanel } from './tool-inspector-panel.ts';
21
- export { ContextVisualizerPanel } from './context-visualizer-panel.ts';
22
19
  export { DebugPanel } from './debug-panel.ts';
23
20
  export type { ApiCallEntry, ApiCallStatus } from './debug-panel.ts';
24
21
  export { registerBuiltinPanels } from './builtin-panels.ts';
25
22
  export type { BuiltinPanelDeps } from './builtin-panels.ts';
26
- export { ForensicsPanel } from './forensics-panel.ts';
27
23
  export { IncidentReviewPanel } from './incident-review-panel.ts';
28
24
  export { PolicyPanel } from './policy-panel.ts';
29
25
  export { PluginsPanel } from './plugins-panel.ts';
@@ -1,42 +1,70 @@
1
1
  import type { Line } from '../types/grid.ts';
2
2
  import { createEmptyLine } from '../types/grid.ts';
3
+ import { truncateDisplay } from '../utils/terminal-width.ts';
3
4
  import { BasePanel } from './base-panel.ts';
5
+ import { FilePreviewPanel } from './file-preview-panel.ts';
6
+ import { SymbolOutlinePanel } from './symbol-outline-panel.ts';
7
+ import type { PanelIntegrationContext } from './types.ts';
4
8
  import type { UiIntelligenceSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
5
9
  import {
6
10
  buildEmptyState,
7
11
  buildGuidanceLine,
12
+ buildKeyboardHints,
8
13
  buildKeyValueLine,
9
14
  buildPanelLine,
10
15
  buildPanelWorkspace,
16
+ buildStatusPill,
17
+ resolveStackedScrollableSections,
11
18
  DEFAULT_PANEL_PALETTE,
12
19
  type PanelWorkspaceSection,
20
+ type StyledPanelSegment,
13
21
  } from './polish.ts';
14
22
 
15
- const C = {
16
- ...DEFAULT_PANEL_PALETTE,
17
- good: '#22c55e',
18
- warn: '#f59e0b',
19
- bad: '#ef4444',
20
- info: '#38bdf8',
21
- headerBg: '#1e293b',
22
- } as const;
23
-
24
- function statusColor(status: string): string {
25
- switch (status) {
26
- case 'ready':
27
- return C.good;
28
- case 'loading':
29
- return C.info;
30
- case 'degraded':
31
- return C.warn;
32
- case 'unavailable':
33
- default:
34
- return C.bad;
35
- }
23
+ // Base chrome only — state colors and text tokens come straight from
24
+ // DEFAULT_PANEL_PALETTE (WO-002).
25
+ const C = DEFAULT_PANEL_PALETTE;
26
+
27
+ interface DiagnosticFile {
28
+ readonly filePath: string;
29
+ readonly errors: number;
30
+ readonly warnings: number;
31
+ readonly diagnostics: ReadonlyArray<{
32
+ readonly line: number;
33
+ readonly column: number;
34
+ readonly severity: string;
35
+ readonly message: string;
36
+ readonly source?: string | undefined;
37
+ readonly code?: string | undefined;
38
+ }>;
39
+ }
40
+
41
+ /** Map an intelligence-surface status string to a status-pill segment. */
42
+ function postureSegments(label: string, status: string): StyledPanelSegment[] {
43
+ const state = status === 'ready' ? 'good' : status === 'degraded' ? 'warn' : status === 'loading' ? 'info' : 'bad';
44
+ return buildStatusPill(state, `${label} ${status}`);
36
45
  }
37
46
 
38
47
  export class IntelligencePanel extends BasePanel {
39
48
  private readonly unsub: (() => void) | null;
49
+ private selectedIndex = 0;
50
+
51
+ /**
52
+ * The diagnostic file under the cursor. This panel owns its own selection
53
+ * state (`selectedIndex` navigates the `diagnosticFiles()` list directly), so
54
+ * every selected-row read routes through this one accessor — indexing the
55
+ * `diagnosticFiles()` list by the cursor directly is banned by the
56
+ * no-raw-selectedindex-read architecture rule.
57
+ */
58
+ private selectedDiagnosticFile(): DiagnosticFile | undefined {
59
+ return this.diagnosticFiles().at(this.selectedIndex);
60
+ }
61
+
62
+ private diagnosticsScrollOffset = 0;
63
+ private findingsScrollOffset = 0;
64
+ /** Set by handleInput('enter'); consumed by handlePanelIntegrationAction, which has the PanelManager reference needed to open the preview panel. */
65
+ private _pendingOpenFile: { filePath: string; line: number } | null = null;
66
+ /** Set by handleInput('s'); consumed by handlePanelIntegrationAction to open the file in preview AND sync the Symbols panel to it. */
67
+ private _pendingSymbolsPivot: { filePath: string; line: number } | null = null;
40
68
 
41
69
  public constructor(private readonly readModel?: UiReadModel<UiIntelligenceSnapshot>) {
42
70
  super('intelligence', 'Intelligence', 'J', 'development');
@@ -47,6 +75,124 @@ export class IntelligencePanel extends BasePanel {
47
75
  this.unsub?.();
48
76
  }
49
77
 
78
+ /** Diagnostic files sorted error-first so the most actionable file is first. */
79
+ private diagnosticFiles(): DiagnosticFile[] {
80
+ if (!this.readModel) return [];
81
+ const state = this.readModel.getSnapshot();
82
+ return [...state.diagnostics.entries()]
83
+ .map(([filePath, diagnostics]) => ({
84
+ filePath,
85
+ errors: diagnostics.filter((entry) => entry.severity === 'error').length,
86
+ warnings: diagnostics.filter((entry) => entry.severity === 'warning').length,
87
+ diagnostics: diagnostics.map((entry) => ({
88
+ line: entry.line,
89
+ column: entry.column,
90
+ severity: entry.severity,
91
+ message: entry.message,
92
+ source: entry.source,
93
+ code: entry.code,
94
+ })),
95
+ }))
96
+ .sort((a, b) => (b.errors - a.errors) || (b.warnings - a.warnings) || a.filePath.localeCompare(b.filePath));
97
+ }
98
+
99
+ /** The most actionable finding in a file — error-first, matching the file sort. */
100
+ private _firstFinding(file: DiagnosticFile): DiagnosticFile['diagnostics'][number] | undefined {
101
+ if (file.diagnostics.length === 0) return undefined;
102
+ return [...file.diagnostics].sort((a, b) => (a.severity === 'error' ? 0 : 1) - (b.severity === 'error' ? 0 : 1))[0];
103
+ }
104
+
105
+ public handleInput(key: string): boolean {
106
+ const files = this.diagnosticFiles();
107
+ if (files.length === 0) return false;
108
+ if (key === 'up' || key === 'k') {
109
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
110
+ this.markDirty();
111
+ return true;
112
+ }
113
+ if (key === 'down' || key === 'j') {
114
+ this.selectedIndex = Math.min(files.length - 1, this.selectedIndex + 1);
115
+ this.markDirty();
116
+ return true;
117
+ }
118
+ if (key === 'home' || key === 'g') {
119
+ this.selectedIndex = 0;
120
+ this.markDirty();
121
+ return true;
122
+ }
123
+ if (key === 'end' || key === 'G') {
124
+ this.selectedIndex = files.length - 1;
125
+ this.markDirty();
126
+ return true;
127
+ }
128
+ if (key === 'enter' || key === 'return') {
129
+ const selected = this.selectedDiagnosticFile();
130
+ const first = selected ? this._firstFinding(selected) : undefined;
131
+ if (!selected || !first) return false;
132
+ this._pendingOpenFile = { filePath: selected.filePath, line: first.line + 1 };
133
+ return true;
134
+ }
135
+ if (key === 's') {
136
+ const selected = this.selectedDiagnosticFile();
137
+ const first = selected ? this._firstFinding(selected) : undefined;
138
+ if (!selected || !first) return false;
139
+ this._pendingSymbolsPivot = { filePath: selected.filePath, line: first.line + 1 };
140
+ return true;
141
+ }
142
+ return false;
143
+ }
144
+
145
+ /**
146
+ * Cross-panel integration hook. Enter opens the selected file in the
147
+ * preview panel at its first (error-first) finding; 's' does the same and
148
+ * also syncs the Symbols panel to that file, pivoting straight from a
149
+ * diagnostic to its document symbols.
150
+ */
151
+ handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
152
+ if (this._pendingSymbolsPivot) {
153
+ const target = this._pendingSymbolsPivot;
154
+ this._pendingSymbolsPivot = null;
155
+ const previewPanel = this._openPreviewAt(ctx.panelManager, target.filePath, target.line);
156
+ if (!previewPanel) return false;
157
+ const symbols = ctx.panelManager.open('symbols');
158
+ if (symbols instanceof SymbolOutlinePanel) {
159
+ const source = previewPanel.getSource();
160
+ if (source !== null) symbols.loadFile(target.filePath, source);
161
+ }
162
+ return true;
163
+ }
164
+ if (this._pendingOpenFile) {
165
+ const target = this._pendingOpenFile;
166
+ this._pendingOpenFile = null;
167
+ return this._openPreviewAt(ctx.panelManager, target.filePath, target.line) !== null;
168
+ }
169
+ return false;
170
+ }
171
+
172
+ /** Open (or focus) the shared preview panel and jump it to filePath:line — same open/focus bridge FileExplorerPanel/DiffPanel use. */
173
+ private _openPreviewAt(panelManager: PanelIntegrationContext['panelManager'], filePath: string, line: number): FilePreviewPanel | null {
174
+ let previewPanel = panelManager.getPanel('preview');
175
+ if (previewPanel instanceof FilePreviewPanel) {
176
+ const pane = panelManager.getPaneOf('preview');
177
+ panelManager.activateById('preview');
178
+ if (pane) panelManager.focusPane(pane);
179
+ } else {
180
+ const targetPane: 'top' | 'bottom' = panelManager.isBottomPaneVisible()
181
+ ? (panelManager.getFocusedPane() === 'top' ? 'bottom' : 'top')
182
+ : 'bottom';
183
+ const opened = panelManager.open('preview', targetPane);
184
+ panelManager.show();
185
+ panelManager.focusPane(targetPane);
186
+ previewPanel = opened instanceof FilePreviewPanel ? opened : null;
187
+ }
188
+ if (!(previewPanel instanceof FilePreviewPanel)) return null;
189
+ if (previewPanel.getCurrentFilePath() !== filePath) {
190
+ previewPanel.openFile(filePath);
191
+ }
192
+ previewPanel.goToLine(line);
193
+ return previewPanel;
194
+ }
195
+
50
196
  public render(width: number, height: number): Line[] {
51
197
  this.needsRender = false;
52
198
  if (!this.readModel) {
@@ -76,98 +222,159 @@ export class IntelligencePanel extends BasePanel {
76
222
  state.hoverStatus,
77
223
  ].filter((status) => status !== 'ready').length;
78
224
 
79
- const sections: PanelWorkspaceSection[] = [
80
- {
81
- title: 'Intelligence posture',
82
- lines: [
83
- buildKeyValueLine(width, [
84
- { label: 'diagnostics', value: state.diagnosticsStatus, valueColor: statusColor(state.diagnosticsStatus) },
85
- { label: 'symbols', value: state.symbolSearchStatus, valueColor: statusColor(state.symbolSearchStatus) },
86
- { label: 'completions', value: state.completionsStatus, valueColor: statusColor(state.completionsStatus) },
87
- { label: 'hover', value: state.hoverStatus, valueColor: statusColor(state.hoverStatus) },
88
- ], C),
89
- buildKeyValueLine(width, [
90
- { label: 'errors', value: String(state.errorCount), valueColor: state.errorCount > 0 ? C.bad : C.dim },
91
- { label: 'warnings', value: String(state.warningCount), valueColor: state.warningCount > 0 ? C.warn : C.dim },
92
- { label: 'requests', value: String(state.totalRequests), valueColor: C.value },
93
- { label: 'avg latency', value: `${Math.round(state.avgLatencyMs)}ms`, valueColor: C.info },
94
- ], C),
95
- ],
96
- },
97
- {
98
- title: 'Next Actions',
99
- lines: [
100
- buildGuidanceLine(width, '/intelligence diagnostics', 'review readiness posture and current diagnostics activity', C),
101
- buildGuidanceLine(width, '/intelligence repair', 'surface repair-oriented guidance when symbols, hover, or completions degrade', C),
102
- ],
103
- },
104
- {
105
- title: 'Readiness',
106
- lines: [
107
- buildPanelLine(width, [[` Diagnostics are ${state.diagnosticsStatus}. Symbol search is ${state.symbolSearchStatus}.`, state.diagnosticsStatus === 'ready' && state.symbolSearchStatus === 'ready' ? C.dim : C.warn]]),
108
- buildPanelLine(width, [[` Hover is ${state.hoverStatus}. Completions are ${state.completionsStatus}.`, state.hoverStatus === 'ready' && state.completionsStatus === 'ready' ? C.dim : C.warn]]),
109
- ...(state.hover.active && state.hover.filePath
110
- ? [buildPanelLine(width, [[` Active hover: ${state.hover.filePath}`, C.info]])]
111
- : []),
112
- ],
113
- },
114
- ];
225
+ const diagnosticFiles = this.diagnosticFiles();
226
+ this.selectedIndex = Math.max(0, Math.min(this.selectedIndex, Math.max(0, diagnosticFiles.length - 1)));
227
+ const selectedFile = this.selectedDiagnosticFile();
228
+ const sortedFindings = selectedFile
229
+ ? [...selectedFile.diagnostics].sort((a, b) => (a.severity === 'error' ? 0 : 1) - (b.severity === 'error' ? 0 : 1))
230
+ : [];
115
231
 
116
- const diagnosticFiles = [...state.diagnostics.entries()]
117
- .map(([filePath, diagnostics]) => ({
118
- filePath,
119
- errors: diagnostics.filter((entry) => entry.severity === 'error').length,
120
- warnings: diagnostics.filter((entry) => entry.severity === 'warning').length,
121
- }))
122
- .sort((a, b) => (b.errors - a.errors) || (b.warnings - a.warnings) || a.filePath.localeCompare(b.filePath))
123
- .slice(0, 4);
124
- sections.push({
125
- title: 'Diagnostics',
126
- lines: diagnosticFiles.length > 0
127
- ? diagnosticFiles.map((entry) => buildPanelLine(width, [[
128
- ` ${entry.filePath} errors=${entry.errors} warnings=${entry.warnings}`,
129
- entry.errors > 0 ? C.bad : entry.warnings > 0 ? C.warn : C.dim,
130
- ]]))
131
- : [buildPanelLine(width, [[' No tracked diagnostics yet.', C.dim]])],
132
- });
133
-
134
- sections.push({
135
- title: 'Workflows',
232
+ const postureSection: PanelWorkspaceSection = {
233
+ title: 'Intelligence posture',
136
234
  lines: [
137
- buildGuidanceLine(width, '/intelligence symbols <file>', 'inspect document symbols for a file and verify symbol-surface readiness', C),
138
- buildGuidanceLine(width, '/intelligence outline <file>', 'review structural outline extraction without leaving the control room', C),
139
- buildGuidanceLine(width, '/intelligence definition <file> <line> <column>', 'check definition lookup for an exact source position', C),
140
- buildGuidanceLine(width, '/intelligence references <file> <line> <column>', 'review reference lookup for a symbol under the cursor', C),
141
- buildGuidanceLine(width, '/intelligence hover <file> <line> <column>', 'inspect hover/details posture for a specific source position', C),
235
+ buildPanelLine(width, [
236
+ { text: ' ', fg: C.dim },
237
+ ...postureSegments('diagnostics', state.diagnosticsStatus),
238
+ { text: ' ', fg: C.dim },
239
+ ...postureSegments('symbols', state.symbolSearchStatus),
240
+ ]),
241
+ buildPanelLine(width, [
242
+ { text: ' ', fg: C.dim },
243
+ ...postureSegments('completions', state.completionsStatus),
244
+ { text: ' ', fg: C.dim },
245
+ ...postureSegments('hover', state.hoverStatus),
246
+ ]),
247
+ buildKeyValueLine(width, [
248
+ { label: 'errors', value: String(state.errorCount), valueColor: state.errorCount > 0 ? C.bad : C.dim },
249
+ { label: 'warnings', value: String(state.warningCount), valueColor: state.warningCount > 0 ? C.warn : C.dim },
250
+ { label: 'requests', value: String(state.totalRequests), valueColor: C.value },
251
+ { label: 'avg latency', value: `${Math.round(state.avgLatencyMs)}ms`, valueColor: C.info },
252
+ ], C),
253
+ ],
254
+ };
255
+
256
+ // Diagnostics — the most actionable surface. Render as a selectable list so
257
+ // the operator can drill into a specific file's findings. Windowed via
258
+ // resolveStackedScrollableSections (WO-136) instead of a flat slice(0,6)
259
+ // so the cursor tracks into view instead of being able to scroll off-screen.
260
+ const diagnosticsLines: Line[] = diagnosticFiles.length > 0
261
+ ? diagnosticFiles.map((entry, idx) => {
262
+ const selected = idx === this.selectedIndex;
263
+ const tone = entry.errors > 0 ? C.bad : entry.warnings > 0 ? C.warn : C.dim;
264
+ const counts = ` ${entry.errors} err ${entry.warnings} warn`;
265
+ const pathBudget = Math.max(8, width - counts.length - 4);
266
+ return buildPanelLine(width, [
267
+ [selected ? ' ▸ ' : ' ', selected ? C.info : C.dim, selected ? C.headerBg : undefined],
268
+ [truncateDisplay(entry.filePath, pathBudget), selected ? C.value : C.label, selected ? C.headerBg : undefined],
269
+ [counts, tone, selected ? C.headerBg : undefined],
270
+ ]);
271
+ })
272
+ : [buildPanelLine(width, [[' No tracked diagnostics. Symbol/diagnostic surfaces are clear.', C.dim]])];
273
+
274
+ // Findings for the selected file — fully scrollable (WO-136: no 3-cap).
275
+ const findingsFixedLines: Line[] = selectedFile
276
+ ? [buildKeyValueLine(width, [
277
+ { label: 'file', value: truncateDisplay(selectedFile.filePath, Math.max(8, width - 34)), valueColor: C.value },
278
+ { label: 'errors', value: String(selectedFile.errors), valueColor: selectedFile.errors > 0 ? C.bad : C.dim },
279
+ { label: 'warnings', value: String(selectedFile.warnings), valueColor: selectedFile.warnings > 0 ? C.warn : C.dim },
280
+ ], C)]
281
+ : [];
282
+ const findingsLines: Line[] = selectedFile
283
+ ? (sortedFindings.length > 0
284
+ ? sortedFindings.map((finding) => {
285
+ const loc = `${finding.line + 1}:${finding.column + 1}`;
286
+ const tag = finding.source ? `${finding.source}${finding.code ? `(${finding.code})` : ''}` : finding.severity;
287
+ const prefix = ` ${loc} ${tag}: `;
288
+ return buildPanelLine(width, [
289
+ [prefix, finding.severity === 'error' ? C.bad : C.warn],
290
+ [truncateDisplay(finding.message, Math.max(8, width - prefix.length - 1)), C.value],
291
+ ]);
292
+ })
293
+ : [buildPanelLine(width, [[' No findings recorded for this file.', C.dim]])])
294
+ : [buildPanelLine(width, [[' Select a file above to inspect its findings.', C.dim]])];
295
+
296
+ // Recovery/Workflows collapse to a single contextual guidance line
297
+ // (WO-136) instead of a static wall of signposts.
298
+ const guidanceSection: PanelWorkspaceSection = degraded > 0
299
+ ? {
300
+ title: 'Recovery',
301
+ lines: [buildGuidanceLine(
302
+ width,
303
+ '/intelligence repair',
304
+ `${degraded} of 4 surfaces degraded (diagnostics/symbols/hover/completions) — run repair`,
305
+ C,
306
+ )],
307
+ }
308
+ : {
309
+ title: 'Workflows',
310
+ lines: [buildGuidanceLine(
311
+ width,
312
+ '/intelligence symbols <file>',
313
+ 'inspect document symbols for a file, or press s on a finding to pivot straight there',
314
+ C,
315
+ )],
316
+ };
317
+
318
+ const footerLines = diagnosticFiles.length > 0
319
+ ? [buildKeyboardHints(width, [
320
+ { keys: '↑/↓', label: 'select file' },
321
+ { keys: 'Enter', label: 'open file at finding' },
322
+ { keys: 's', label: 'pivot to symbols' },
323
+ { keys: 'Home/End', label: 'jump' },
324
+ ], C)]
325
+ : [buildKeyboardHints(width, [
326
+ { keys: '/intelligence diagnostics', label: 'review' },
327
+ { keys: '/intelligence symbols <file>', label: 'inspect symbols' },
328
+ ], C)];
329
+
330
+ const [diagnosticsWindow, findingsWindow] = resolveStackedScrollableSections(width, height, {
331
+ intro: 'Workspace intelligence posture across diagnostics, symbol search, hover, and completion readiness.',
332
+ footerLines,
333
+ palette: C,
334
+ beforeSections: [postureSection],
335
+ sections: [
336
+ {
337
+ title: `Diagnostics (${diagnosticFiles.length})`,
338
+ scrollableLines: diagnosticsLines,
339
+ selectedIndex: diagnosticFiles.length > 0 ? this.selectedIndex : undefined,
340
+ scrollOffset: this.diagnosticsScrollOffset,
341
+ minRows: 3,
342
+ weight: 2,
343
+ appendWindowSummary: diagnosticFiles.length > 0 ? {
344
+ dimColor: C.dim,
345
+ formatter: (window) => buildPanelLine(width, [[` showing ${window.start + 1}-${window.end} of ${window.total} files`, C.dim]]),
346
+ } : undefined,
347
+ },
348
+ {
349
+ title: selectedFile ? `Findings — ${truncateDisplay(selectedFile.filePath, Math.max(8, width - 14))}` : 'Findings',
350
+ fixedLines: findingsFixedLines,
351
+ scrollableLines: findingsLines,
352
+ scrollOffset: this.findingsScrollOffset,
353
+ minRows: 2,
354
+ weight: 1,
355
+ appendWindowSummary: sortedFindings.length > 0 ? {
356
+ dimColor: C.dim,
357
+ formatter: (window) => buildPanelLine(width, [[` showing ${window.start + 1}-${window.end} of ${window.total} findings`, C.dim]]),
358
+ } : undefined,
359
+ },
142
360
  ],
361
+ afterSections: [guidanceSection],
143
362
  });
363
+ this.diagnosticsScrollOffset = diagnosticsWindow?.scrollOffset ?? this.diagnosticsScrollOffset;
364
+ this.findingsScrollOffset = findingsWindow?.scrollOffset ?? this.findingsScrollOffset;
144
365
 
145
- if (degraded > 0) {
146
- sections.push({
147
- title: 'Recovery',
148
- lines: [
149
- buildPanelLine(width, [[' Workspace intelligence is not fully ready. Review LSP/tree-sitter setup and workspace language configuration.', C.warn]]),
150
- buildGuidanceLine(width, '/health review', 'check setup and readiness failures that could block diagnostics and symbol search', C),
151
- buildGuidanceLine(width, '/setup review', 'review startup and environment posture for intelligence dependencies', C),
152
- buildGuidanceLine(width, '/intelligence repair', 'show repair-oriented commands for diagnostics, symbols, hover, and completions', C),
153
- buildGuidanceLine(width, '/health repair intelligence', 'show repair commands plus post-repair verification for the intelligence domain', C),
154
- ],
155
- });
156
- } else {
157
- sections.push({
158
- title: 'Recovery',
159
- lines: [
160
- buildPanelLine(width, [[' Intelligence surfaces are healthy and ready for code-aware workflows.', C.dim]]),
161
- buildGuidanceLine(width, '/health intelligence', 'verify readiness posture after setup changes or dependency recovery', C),
162
- ],
163
- });
164
- }
366
+ const sections: PanelWorkspaceSection[] = [
367
+ postureSection,
368
+ diagnosticsWindow?.section ?? { title: `Diagnostics (${diagnosticFiles.length})`, lines: diagnosticsLines },
369
+ findingsWindow?.section ?? { title: 'Findings', lines: [...findingsFixedLines, ...findingsLines] },
370
+ guidanceSection,
371
+ ];
165
372
 
166
373
  const lines = buildPanelWorkspace(width, height, {
167
374
  title: 'Intelligence Control Room',
168
375
  intro: 'Workspace intelligence posture across diagnostics, symbol search, hover, and completion readiness.',
169
376
  sections,
170
- footerLines: [buildPanelLine(width, [[' /symbols /intelligence diagnostics /intelligence symbols <file> /intelligence definition <file> <line> <column> ', C.dim]])],
377
+ footerLines,
171
378
  palette: C,
172
379
  });
173
380
  while (lines.length < height) lines.push(createEmptyLine(width));