@pellux/goodvibes-tui 1.1.0 → 1.9.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 (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -1,398 +0,0 @@
1
- import { logger } from '@pellux/goodvibes-sdk/platform/utils';
2
- import type { Line } from '../types/grid.ts';
3
- import { createEmptyLine } from '../types/grid.ts';
4
- import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
5
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
6
- import {
7
- buildDetailBlock,
8
- buildKeyboardHints,
9
- buildPanelListRow,
10
- buildPanelLine,
11
- buildPanelWorkspace,
12
- buildStatusPill,
13
- buildSummaryBlock,
14
- DEFAULT_PANEL_PALETTE,
15
- resolveScrollablePanelSection,
16
- type PanelPalette,
17
- type PanelWorkspaceSection,
18
- } from './polish.ts';
19
- import { getSettingsControlPlaneSnapshot } from '@/runtime/index.ts';
20
- import type { ConfigManager } from '../config/index.ts';
21
- import type { PanelIntegrationContext } from './types.ts';
22
-
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
- type SettingsSnapshot = ReturnType<typeof getSettingsControlPlaneSnapshot>;
28
- type ResolvedEntry = SettingsSnapshot['resolvedEntries'][number];
29
-
30
- // Tab-toggled browse modes (RemotePanel pattern): 'keys' is the primary
31
- // selectable list (kept at its own scroll viewport); the rest were
32
- // previously dumped unconditionally into the header, squeezing the key
33
- // list out of view.
34
- const BROWSE_MODES = ['keys', 'events', 'locks', 'failures', 'conflicts', 'rollback'] as const;
35
- type BrowseMode = typeof BROWSE_MODES[number];
36
-
37
- interface BrowseModeContent {
38
- readonly title: string;
39
- readonly rows: Line[];
40
- readonly emptyMessage: string;
41
- }
42
-
43
- export class SettingsSyncPanel extends ScrollableListPanel<ResolvedEntry> {
44
- private browseMode: BrowseMode = 'keys';
45
- private browseIndex = 0;
46
- private browseScrollOffset = 0;
47
-
48
- // Inline local/synced picker for a conflicted entry (Enter on the keys
49
- // list). Resolved via handlePanelIntegrationAction, the only place
50
- // executeCommand is available.
51
- private _resolvePrompt: { readonly key: string } | null = null;
52
- private _pendingResolve: { readonly key: string; readonly choice: 'local' | 'synced' } | null = null;
53
- private _pendingManagedReview = false;
54
-
55
- public constructor(private readonly configManager: ConfigManager) {
56
- super('settings-sync', 'Settings Sync', '▱', 'security-policy');
57
- this.showSelectionGutter = true; // I5: non-color selection affordance
58
- this.filterEnabled = true;
59
- this.filterLabel = 'Filter settings';
60
- }
61
-
62
- protected override filterMatches(entry: ResolvedEntry, q: string): boolean {
63
- return entry.key.toLowerCase().includes(q)
64
- || String(entry.effectiveSource).toLowerCase().includes(q)
65
- || String(entry.effectiveValue).toLowerCase().includes(q);
66
- }
67
-
68
- protected override getPalette(): PanelPalette {
69
- return C;
70
- }
71
-
72
- protected getItems(): readonly ResolvedEntry[] {
73
- return getSettingsControlPlaneSnapshot(this.configManager).resolvedEntries;
74
- }
75
-
76
- protected renderItem(entry: ResolvedEntry, _index: number, selected: boolean, width: number): Line {
77
- return buildPanelListRow(width, [
78
- { text: fitDisplay(entry.key, 32).padEnd(32), fg: C.value },
79
- { text: ` ${entry.effectiveSource}`.padEnd(11), fg: entry.effectiveSource === 'managed' ? C.warn : entry.effectiveSource === 'synced' ? C.good : entry.effectiveSource === 'local' ? C.info : C.dim },
80
- { text: truncateDisplay(`${String(entry.effectiveValue)}`, Math.max(0, width - 47)), fg: entry.conflict ? C.bad : entry.locked ? C.warn : C.dim },
81
- ], C, { selected });
82
- }
83
-
84
- protected override getEmptyStateMessage(): string {
85
- return ' No resolved settings entries.';
86
- }
87
-
88
- /** Enter on a conflicted entry opens the inline local/synced picker. */
89
- protected override onSelect(entry: ResolvedEntry): void {
90
- if (!entry.conflict) return;
91
- this._resolvePrompt = { key: entry.key };
92
- this.needsRender = true;
93
- }
94
-
95
- public override handleInput(key: string): boolean {
96
- if (this.lastError !== null) this.clearError();
97
-
98
- if (this._resolvePrompt) {
99
- if (key === 'l') {
100
- this._pendingResolve = { key: this._resolvePrompt.key, choice: 'local' };
101
- this._resolvePrompt = null;
102
- this.needsRender = true;
103
- return true;
104
- }
105
- if (key === 's') {
106
- this._pendingResolve = { key: this._resolvePrompt.key, choice: 'synced' };
107
- this._resolvePrompt = null;
108
- this.needsRender = true;
109
- return true;
110
- }
111
- if (key === 'escape' || key === 'n') {
112
- this._resolvePrompt = null;
113
- this.needsRender = true;
114
- return true;
115
- }
116
- return true; // absorbed — keep the picker pending
117
- }
118
-
119
- // The inline `/`-filter capture must win over browse-mode/managed-review
120
- // keys while it is actively collecting a query (e.g. typing "m").
121
- if (this.filterActive) {
122
- return super.handleInput(key);
123
- }
124
-
125
- if (key === 'tab') {
126
- const idx = BROWSE_MODES.indexOf(this.browseMode);
127
- this.browseMode = BROWSE_MODES[(idx + 1) % BROWSE_MODES.length]!;
128
- this.browseIndex = 0;
129
- this.browseScrollOffset = 0;
130
- this.needsRender = true;
131
- return true;
132
- }
133
-
134
- if (key === 'm') {
135
- const snapshot = getSettingsControlPlaneSnapshot(this.configManager);
136
- if (snapshot.stagedManagedBundle) {
137
- this._pendingManagedReview = true;
138
- return true;
139
- }
140
- return false;
141
- }
142
-
143
- if (this.browseMode !== 'keys') {
144
- return this._handleBrowseInput(key);
145
- }
146
-
147
- return super.handleInput(key);
148
- }
149
-
150
- public handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
151
- if (this._pendingResolve) {
152
- const { key, choice } = this._pendingResolve;
153
- this._pendingResolve = null;
154
- void ctx.executeCommand?.('settings-sync', ['resolve', key, choice]).catch((err) => {
155
- logger.debug('settings-sync resolve dispatch failed', { err });
156
- });
157
- return true;
158
- }
159
- if (this._pendingManagedReview) {
160
- this._pendingManagedReview = false;
161
- void ctx.executeCommand?.('managed', ['review']).catch((err) => {
162
- logger.debug('managed review dispatch failed', { err });
163
- });
164
- return true;
165
- }
166
- return false;
167
- }
168
-
169
- private _browseItemCount(): number {
170
- const snapshot = getSettingsControlPlaneSnapshot(this.configManager);
171
- switch (this.browseMode) {
172
- case 'events': return snapshot.recentEvents.length;
173
- case 'locks': return snapshot.managedLocks.length;
174
- case 'failures': return snapshot.recentFailures.length;
175
- case 'conflicts': return snapshot.conflicts.length;
176
- case 'rollback': return snapshot.rollbackHistory.length;
177
- default: return 0;
178
- }
179
- }
180
-
181
- private _handleBrowseInput(key: string): boolean {
182
- const count = this._browseItemCount();
183
- if (count === 0) return false;
184
- switch (key) {
185
- case 'up':
186
- case 'k':
187
- this.browseIndex = Math.max(0, this.browseIndex - 1);
188
- this.needsRender = true;
189
- return true;
190
- case 'down':
191
- case 'j':
192
- this.browseIndex = Math.min(count - 1, this.browseIndex + 1);
193
- this.needsRender = true;
194
- return true;
195
- case 'pageup':
196
- this.browseIndex = Math.max(0, this.browseIndex - this.getPageSize());
197
- this.needsRender = true;
198
- return true;
199
- case 'pagedown':
200
- this.browseIndex = Math.min(count - 1, this.browseIndex + this.getPageSize());
201
- this.needsRender = true;
202
- return true;
203
- case 'home':
204
- case 'g':
205
- this.browseIndex = 0;
206
- this.needsRender = true;
207
- return true;
208
- case 'end':
209
- case 'G':
210
- this.browseIndex = count - 1;
211
- this.needsRender = true;
212
- return true;
213
- default:
214
- return false;
215
- }
216
- }
217
-
218
- /** ≤5 fixed rows total (1 summary title + 4 rows), shared by every mode. */
219
- private _buildPostureHeader(width: number, snapshot: SettingsSnapshot): Line[] {
220
- const rows: Line[] = [
221
- buildPanelLine(width, [
222
- [' resolved keys ', C.label], [String(snapshot.resolvedEntries.length), C.value],
223
- [' conflicts ', C.label], ...buildStatusPill(snapshot.conflicts.length > 0 ? 'bad' : 'good', String(snapshot.conflicts.length)),
224
- [' failures ', C.label], ...buildStatusPill(snapshot.recentFailures.length > 0 ? 'warn' : 'good', String(snapshot.recentFailures.length)),
225
- ]),
226
- buildPanelLine(width, [
227
- [' managed locks ', C.label], [String(snapshot.managedLockCount), snapshot.managedLockCount > 0 ? C.warn : C.dim],
228
- [' staged bundle ', C.label], [snapshot.stagedManagedBundle ? snapshot.stagedManagedBundle.profileName : 'none', snapshot.stagedManagedBundle ? C.info : C.dim],
229
- ]),
230
- buildPanelLine(width, [
231
- [' effective local ', C.label], [String(snapshot.resolvedCounts.local), C.info],
232
- [' synced ', C.label], [String(snapshot.resolvedCounts.synced), snapshot.resolvedCounts.synced > 0 ? C.good : C.dim],
233
- [' managed ', C.label], [String(snapshot.resolvedCounts.managed), snapshot.resolvedCounts.managed > 0 ? C.warn : C.dim],
234
- ]),
235
- buildPanelLine(width, [
236
- [' last sync ', C.label], [snapshot.lastSync ? `${snapshot.lastSync.surface}/${snapshot.lastSync.direction}` : 'none', snapshot.lastSync ? C.good : C.dim],
237
- [' mode ', C.label], [this.browseMode, C.info],
238
- ]),
239
- ];
240
- return buildSummaryBlock(width, 'Settings posture', rows, C);
241
- }
242
-
243
- private _buildResolvePromptLines(width: number, key: string): Line[] {
244
- return [
245
- buildPanelLine(width, [[` Resolve conflict for "${key}"?`, C.warn]]),
246
- buildPanelLine(width, [
247
- [' l', C.info], [' keep local', C.dim],
248
- [' s', C.info], [' use synced', C.dim],
249
- [' Esc', C.info], [' cancel', C.dim],
250
- ]),
251
- ];
252
- }
253
-
254
- private _buildBrowseModeContent(width: number, snapshot: SettingsSnapshot): BrowseModeContent {
255
- switch (this.browseMode) {
256
- case 'events':
257
- return {
258
- title: 'Recent Sync & Managed-Setting Events',
259
- rows: snapshot.recentEvents.map((event, index) => buildPanelListRow(width, [
260
- { text: fitDisplay(` ${event.surface}/${event.direction}`, 18).padEnd(18), fg: C.info },
261
- { text: truncateDisplay(` ${event.detail}`, Math.max(0, width - 20)), fg: C.dim },
262
- ], C, { selected: index === this.browseIndex })),
263
- emptyMessage: ' No sync or managed-setting events recorded yet.',
264
- };
265
- case 'locks':
266
- return {
267
- title: 'Managed Locks',
268
- rows: snapshot.managedLocks.map((lock, index) => buildPanelListRow(width, [
269
- { text: fitDisplay(` ${lock.key}`, 30).padEnd(30), fg: C.value },
270
- { text: fitDisplay(` source=${lock.source}`, 24).padEnd(24), fg: C.info },
271
- { text: truncateDisplay(` ${lock.reason}`, Math.max(0, width - 56)), fg: C.dim },
272
- ], C, { selected: index === this.browseIndex })),
273
- emptyMessage: ' No managed locks are currently active.',
274
- };
275
- case 'failures':
276
- return {
277
- title: 'Sync & Managed-Setting Failures',
278
- rows: snapshot.recentFailures.map((failure, index) => buildPanelListRow(width, [
279
- { text: ` ${failure.surface}`.padEnd(10), fg: C.bad },
280
- { text: truncateDisplay(` ${failure.message}`, Math.max(0, width - 12)), fg: C.dim },
281
- ], C, { selected: index === this.browseIndex })),
282
- emptyMessage: ' No recent sync or managed-setting failures.',
283
- };
284
- case 'conflicts':
285
- return {
286
- title: 'Settings Conflicts',
287
- rows: snapshot.conflicts.map((conflict, index) => buildPanelListRow(width, [
288
- { text: fitDisplay(` ${conflict.key}`, 30).padEnd(30), fg: C.value },
289
- { text: fitDisplay(` ${conflict.source}`, 10).padEnd(10), fg: C.warn },
290
- { text: truncateDisplay(` local=${String(conflict.localValue)} incoming=${String(conflict.incomingValue)}`, Math.max(0, width - 42)), fg: C.dim },
291
- ], C, { selected: index === this.browseIndex })),
292
- emptyMessage: ' No settings conflicts detected. Resolve conflicts from the keys list (Enter on a conflicted entry).',
293
- };
294
- case 'rollback':
295
- return {
296
- title: 'Managed Rollback History',
297
- rows: snapshot.rollbackHistory.map((entry, index) => buildPanelListRow(width, [
298
- { text: fitDisplay(` ${entry.token}`, 18).padEnd(18), fg: C.info },
299
- { text: fitDisplay(` ${entry.profileName}`, 18).padEnd(18), fg: C.value },
300
- { text: ` restored=${String(entry.restoredKeys.length).padEnd(4)}`, fg: C.warn },
301
- { text: truncateDisplay(` ${new Date(entry.appliedAt).toLocaleString()}`, Math.max(0, width - 46)), fg: C.dim },
302
- ], C, { selected: index === this.browseIndex })),
303
- emptyMessage: ' No managed rollback records yet.',
304
- };
305
- default:
306
- return { title: '', rows: [], emptyMessage: '' };
307
- }
308
- }
309
-
310
- private _renderKeysMode(width: number, height: number, snapshot: SettingsSnapshot): Line[] {
311
- const headerLines = this._buildPostureHeader(width, snapshot);
312
-
313
- this.clampSelection();
314
- // Detail must describe the row the (possibly filtered) list highlights —
315
- // raw resolvedEntries desyncs selectedIndex under an applied '/' filter.
316
- const selectedEntry = this.getSelectedItem();
317
- const detailLines: Line[] = this._resolvePrompt
318
- ? this._buildResolvePromptLines(width, this._resolvePrompt.key)
319
- : (selectedEntry
320
- ? buildDetailBlock(width, 'Selected setting', [
321
- buildPanelLine(width, [[' key ', C.label], [selectedEntry.key, C.value], [' category ', C.label], [selectedEntry.category, C.info]]),
322
- buildPanelLine(width, [[' effective ', C.label], [selectedEntry.effectiveSource, selectedEntry.effectiveSource === 'managed' ? C.warn : selectedEntry.effectiveSource === 'synced' ? C.good : selectedEntry.effectiveSource === 'local' ? C.info : C.dim], [' locked ', C.label], [selectedEntry.locked ? 'yes' : 'no', selectedEntry.locked ? C.warn : C.dim], [' conflict ', C.label], [selectedEntry.conflict ? 'yes' : 'no', selectedEntry.conflict ? C.bad : C.good]]),
323
- buildPanelLine(width, [[' source ', C.label], [truncateDisplay(selectedEntry.sourceLabel ?? 'local/default', Math.max(0, width - 10)), C.dim]]),
324
- buildPanelLine(width, [[' overrides ', C.label], [truncateDisplay(selectedEntry.overriddenSources.length > 0 ? selectedEntry.overriddenSources.join(', ') : 'none', Math.max(0, width - 13)), C.dim]]),
325
- buildPanelLine(width, [[' local ', C.label], [truncateDisplay(String(selectedEntry.localValue), Math.max(0, width - 9)), C.dim]]),
326
- buildPanelLine(width, [[' synced ', C.label], [truncateDisplay(String(selectedEntry.syncedValue ?? '(unset)'), Math.max(0, width - 10)), C.good]]),
327
- buildPanelLine(width, [[' managed ', C.label], [truncateDisplay(String(selectedEntry.managedValue ?? '(unset)'), Math.max(0, width - 11)), C.warn]]),
328
- ], C)
329
- : [buildPanelLine(width, [[' Select a setting above to inspect its effective value, source, and overrides.', C.dim]])]);
330
-
331
- return this.renderList(width, height, {
332
- title: 'Settings Sync',
333
- header: headerLines,
334
- // Keyboard hints are placed BEFORE the detail block (rather than after,
335
- // which is the usual convention) so that if the fixed-row budget is
336
- // tight, the tail of the detail block is what gets clipped by the
337
- // panel-workspace row budget — not the hints row itself.
338
- footer: [
339
- buildKeyboardHints(width, [
340
- { keys: '↑/↓', label: 'browse' },
341
- { keys: '/', label: 'filter' },
342
- { keys: 'enter', label: 'resolve conflict' },
343
- { keys: 'tab', label: 'events/locks/failures/conflicts/rollback' },
344
- { keys: 'm', label: 'managed review' },
345
- ], C),
346
- ...detailLines,
347
- ],
348
- });
349
- }
350
-
351
- private _renderBrowseMode(width: number, height: number, snapshot: SettingsSnapshot): Line[] {
352
- this.needsRender = false;
353
- const postureSection: PanelWorkspaceSection = { lines: this._buildPostureHeader(width, snapshot) };
354
- const content = this._buildBrowseModeContent(width, snapshot);
355
- const itemCount = content.rows.length;
356
- this.browseIndex = itemCount > 0 ? Math.min(this.browseIndex, itemCount - 1) : 0;
357
- const scrollableLines = itemCount > 0 ? content.rows : [buildPanelLine(width, [[content.emptyMessage, C.dim]])];
358
-
359
- const hintsLine = buildKeyboardHints(width, [
360
- { keys: '↑/↓', label: 'browse' },
361
- { keys: 'tab', label: 'next mode' },
362
- { keys: 'm', label: 'managed review' },
363
- ], C);
364
-
365
- const resolved = resolveScrollablePanelSection(width, height, {
366
- palette: C,
367
- beforeSections: [postureSection],
368
- footerLines: [hintsLine],
369
- section: {
370
- title: content.title,
371
- scrollableLines,
372
- selectedIndex: itemCount > 0 ? this.browseIndex : undefined,
373
- scrollOffset: this.browseScrollOffset,
374
- guardRows: 1,
375
- minRows: 4,
376
- appendWindowSummary: { dimColor: C.dim },
377
- },
378
- });
379
- this.browseScrollOffset = resolved.scrollOffset;
380
-
381
- const lines = buildPanelWorkspace(width, height, {
382
- title: 'Settings Sync',
383
- sections: [postureSection, resolved.section],
384
- footerLines: [hintsLine],
385
- palette: C,
386
- });
387
- while (lines.length < height) lines.push(createEmptyLine(width));
388
- return lines.slice(0, height);
389
- }
390
-
391
- public render(width: number, height: number): Line[] {
392
- const snapshot = getSettingsControlPlaneSnapshot(this.configManager);
393
- if (this.browseMode !== 'keys') {
394
- return this._renderBrowseMode(width, height, snapshot);
395
- }
396
- return this._renderKeysMode(width, height, snapshot);
397
- }
398
- }