@pellux/goodvibes-tui 1.16.1 → 1.17.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 (219) hide show
  1. package/CHANGELOG.md +89 -9
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +1091 -271
  4. package/package.json +7 -7
  5. package/src/cli/completions/generate.ts +5 -4
  6. package/src/cli/config-overrides.ts +51 -11
  7. package/src/cli/doctor.ts +4 -5
  8. package/src/cli/entrypoint.ts +13 -8
  9. package/src/cli/help.ts +7 -6
  10. package/src/cli/launch-auto-update.ts +214 -0
  11. package/src/cli/parser.ts +5 -0
  12. package/src/cli/status.ts +13 -0
  13. package/src/cli/surface-command.ts +5 -5
  14. package/src/cli/tui-startup.ts +60 -97
  15. package/src/cli/types.ts +2 -0
  16. package/src/config/index.ts +1 -1
  17. package/src/config/tui-extension-settings.ts +33 -0
  18. package/src/core/composer-state.ts +1 -1
  19. package/src/core/conversation-line-cache.ts +3 -3
  20. package/src/core/conversation-rendering.ts +4 -4
  21. package/src/core/conversation.ts +2 -2
  22. package/src/core/long-task-notifier.ts +7 -7
  23. package/src/core/stream-event-wiring.ts +8 -6
  24. package/src/core/system-message-noise.ts +1 -1
  25. package/src/core/system-message-router.ts +6 -6
  26. package/src/core/turn-cancellation.ts +1 -1
  27. package/src/core/turn-event-wiring.ts +8 -8
  28. package/src/daemon/cli.ts +20 -5
  29. package/src/daemon/lifecycle.ts +41 -0
  30. package/src/daemon/service-commands.ts +5 -5
  31. package/src/export/cost-utils.ts +128 -6
  32. package/src/input/autocomplete.ts +2 -2
  33. package/src/input/command-registry.ts +34 -9
  34. package/src/input/commands/ci-runtime.ts +5 -1
  35. package/src/input/commands/codebase-runtime.ts +6 -6
  36. package/src/input/commands/cost-runtime.ts +1 -1
  37. package/src/input/commands/health-runtime.ts +2 -2
  38. package/src/input/commands/intelligence-runtime.ts +1 -1
  39. package/src/input/commands/local-auth-runtime.ts +1 -1
  40. package/src/input/commands/local-provider-runtime.ts +49 -10
  41. package/src/input/commands/local-runtime.ts +53 -5
  42. package/src/input/commands/memory.ts +2 -2
  43. package/src/input/commands/operator-panel-runtime.ts +5 -5
  44. package/src/input/commands/permissions-provenance.ts +1 -1
  45. package/src/input/commands/permissions-runtime.ts +99 -8
  46. package/src/input/commands/planning-runtime.ts +3 -3
  47. package/src/input/commands/platform-sandbox-runtime.ts +1 -1
  48. package/src/input/commands/provider-accounts-runtime.ts +71 -6
  49. package/src/input/commands/provider.ts +7 -5
  50. package/src/input/commands/recall-files-git.ts +13 -51
  51. package/src/input/commands/recall-files.ts +8 -11
  52. package/src/input/commands/recall-review.ts +2 -2
  53. package/src/input/commands/relay-runtime.ts +1 -1
  54. package/src/input/commands/remote-runtime.ts +1 -1
  55. package/src/input/commands/runtime-services.ts +4 -4
  56. package/src/input/commands/schedule-runtime.ts +14 -7
  57. package/src/input/commands/services-runtime.ts +1 -1
  58. package/src/input/commands/session-workflow.ts +2 -2
  59. package/src/input/commands/settings-sync-runtime.ts +2 -2
  60. package/src/input/commands/share-runtime.ts +1 -1
  61. package/src/input/commands/shell-core.ts +46 -68
  62. package/src/input/commands/subscription-runtime.ts +1 -1
  63. package/src/input/commands/update-runtime.ts +153 -96
  64. package/src/input/commands/work-plan-runtime.ts +1 -1
  65. package/src/input/commands/workstream-runtime.ts +2 -2
  66. package/src/input/commands.ts +0 -2
  67. package/src/input/config-modal-types.ts +3 -3
  68. package/src/input/config-modal.ts +10 -11
  69. package/src/input/feature-unit-layout.ts +93 -82
  70. package/src/input/feed-context-factory.ts +2 -2
  71. package/src/input/handler-command-route.ts +2 -2
  72. package/src/input/handler-content-actions.ts +1 -1
  73. package/src/input/handler-feed-routes.ts +7 -7
  74. package/src/input/handler-feed.ts +4 -4
  75. package/src/input/handler-interactions.ts +3 -2
  76. package/src/input/handler-modal-routes.ts +12 -4
  77. package/src/input/handler-modal-token-routes.ts +1 -1
  78. package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
  79. package/src/input/handler-onboarding.ts +3 -2
  80. package/src/input/handler-picker-routes.ts +26 -13
  81. package/src/input/handler-shortcuts.ts +6 -6
  82. package/src/input/handler.ts +2 -2
  83. package/src/input/keybindings.ts +2 -2
  84. package/src/input/onboarding/onboarding-feature-units.ts +34 -27
  85. package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
  86. package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
  87. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
  88. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
  89. package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
  90. package/src/input/panel-integration-actions.ts +2 -2
  91. package/src/input/panel-mouse-geometry.ts +1 -1
  92. package/src/input/panel-paste-flood-guard.ts +1 -1
  93. package/src/input/provider-key-intake.ts +167 -0
  94. package/src/input/sandbox-exec-config.ts +2 -2
  95. package/src/input/selection-modal.ts +1 -1
  96. package/src/input/settings-modal-activation.ts +5 -0
  97. package/src/input/settings-modal-adjustment.ts +4 -31
  98. package/src/input/settings-modal-data.ts +22 -21
  99. package/src/input/settings-modal-mutations.ts +12 -68
  100. package/src/input/settings-modal-types.ts +23 -16
  101. package/src/input/settings-modal.ts +74 -55
  102. package/src/main.ts +48 -48
  103. package/src/panels/agent-inspector-shared.ts +4 -4
  104. package/src/panels/base-panel.ts +2 -2
  105. package/src/panels/builtin/agent.ts +4 -4
  106. package/src/panels/builtin/development.ts +4 -4
  107. package/src/panels/builtin/knowledge.ts +1 -1
  108. package/src/panels/builtin/operations.ts +29 -10
  109. package/src/panels/builtin/session.ts +5 -5
  110. package/src/panels/builtin/shared.ts +4 -4
  111. package/src/panels/builtin-modals.ts +10 -10
  112. package/src/panels/builtin-panels.ts +1 -1
  113. package/src/panels/cost-tracker-panel.ts +124 -7
  114. package/src/panels/diff-panel.ts +5 -5
  115. package/src/panels/eval-registry.ts +2 -2
  116. package/src/panels/fleet-deep-link.ts +1 -1
  117. package/src/panels/fleet-panel-format.ts +44 -8
  118. package/src/panels/fleet-panel.ts +16 -16
  119. package/src/panels/fleet-read-model.ts +52 -13
  120. package/src/panels/fleet-steer.ts +2 -2
  121. package/src/panels/fleet-stop.ts +3 -3
  122. package/src/panels/fleet-tabs.ts +13 -14
  123. package/src/panels/fleet-transcript.ts +5 -5
  124. package/src/panels/git-panel.ts +2 -2
  125. package/src/panels/index.ts +4 -1
  126. package/src/panels/local-auth-panel.ts +1 -1
  127. package/src/panels/modals/hooks-modal.ts +1 -1
  128. package/src/panels/modals/keybindings-modal.ts +2 -2
  129. package/src/panels/modals/knowledge-modal.ts +1 -1
  130. package/src/panels/modals/marketplace-modal.ts +4 -4
  131. package/src/panels/modals/memory-modal.ts +4 -4
  132. package/src/panels/modals/modal-surface-helpers.ts +1 -1
  133. package/src/panels/modals/modal-theme.ts +1 -1
  134. package/src/panels/modals/pairing-modal.ts +2 -2
  135. package/src/panels/modals/planning-modal.ts +2 -2
  136. package/src/panels/modals/plugins-modal.ts +1 -1
  137. package/src/panels/modals/policy-modal.ts +1 -1
  138. package/src/panels/modals/security-modal.ts +1 -1
  139. package/src/panels/modals/skills-modal.ts +1 -1
  140. package/src/panels/modals/work-plan-modal.ts +3 -3
  141. package/src/panels/notifications-feed.ts +127 -0
  142. package/src/panels/notifications-panel.ts +160 -0
  143. package/src/panels/panel-manager.ts +9 -9
  144. package/src/panels/plugins-panel.ts +4 -4
  145. package/src/panels/project-planning-answer-actions.ts +1 -1
  146. package/src/panels/provider-health-routes.ts +2 -2
  147. package/src/panels/skills-panel.ts +4 -5
  148. package/src/panels/token-budget-panel.ts +11 -3
  149. package/src/panels/types.ts +5 -5
  150. package/src/permissions/broker-approval-card.ts +201 -0
  151. package/src/permissions/hunk-selection.ts +28 -4
  152. package/src/permissions/prompt.ts +244 -29
  153. package/src/renderer/autocomplete-overlay.ts +107 -54
  154. package/src/renderer/code-block.ts +2 -2
  155. package/src/renderer/compaction-history-modal.ts +1 -1
  156. package/src/renderer/compaction-preview.ts +1 -1
  157. package/src/renderer/compaction-quality.ts +1 -1
  158. package/src/renderer/compositor.ts +2 -2
  159. package/src/renderer/config-modal.ts +1 -1
  160. package/src/renderer/conversation-overlays.ts +1 -1
  161. package/src/renderer/footer-tips.ts +3 -3
  162. package/src/renderer/hint-grammar.ts +1 -1
  163. package/src/renderer/modal-factory.ts +1 -1
  164. package/src/renderer/model-picker-overlay.ts +1 -1
  165. package/src/renderer/model-workspace.ts +7 -2
  166. package/src/renderer/panel-composite.ts +2 -2
  167. package/src/renderer/panel-workspace-bar.ts +1 -1
  168. package/src/renderer/process-indicator.ts +1 -1
  169. package/src/renderer/selection-modal-overlay.ts +1 -1
  170. package/src/renderer/session-picker-modal.ts +2 -2
  171. package/src/renderer/settings-modal-helpers.ts +14 -5
  172. package/src/renderer/settings-modal.ts +127 -39
  173. package/src/renderer/shell-surface.ts +3 -0
  174. package/src/renderer/terminal-bg-probe.ts +1 -1
  175. package/src/renderer/theme.ts +3 -3
  176. package/src/renderer/tool-call.ts +1 -1
  177. package/src/renderer/tool-result-summary.ts +1 -1
  178. package/src/renderer/turn-injection.ts +7 -6
  179. package/src/renderer/ui-factory.ts +6 -1
  180. package/src/runtime/bootstrap-command-context.ts +4 -1
  181. package/src/runtime/bootstrap-command-parts.ts +6 -4
  182. package/src/runtime/bootstrap-core.ts +35 -35
  183. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  184. package/src/runtime/bootstrap-shell.ts +6 -5
  185. package/src/runtime/bootstrap.ts +34 -32
  186. package/src/runtime/channel-composition.ts +46 -0
  187. package/src/runtime/code-index-services.ts +3 -4
  188. package/src/runtime/daemon-attach-notices.ts +120 -0
  189. package/src/runtime/durability-services.ts +55 -0
  190. package/src/runtime/embedded-service-factories.ts +40 -0
  191. package/src/runtime/feature-settings.ts +147 -0
  192. package/src/runtime/index.ts +4 -21
  193. package/src/runtime/legacy-daemon-migration.ts +29 -19
  194. package/src/runtime/memory-fold.ts +1 -1
  195. package/src/runtime/notification-dispatch.ts +110 -0
  196. package/src/runtime/onboarding/apply.ts +3 -29
  197. package/src/runtime/onboarding/fast-path.ts +39 -6
  198. package/src/runtime/onboarding/provider-key-capture.ts +126 -0
  199. package/src/runtime/onboarding/snapshot.ts +0 -1
  200. package/src/runtime/onboarding/types.ts +9 -3
  201. package/src/runtime/provider-fallback.ts +55 -0
  202. package/src/runtime/provider-optimizer-wiring.ts +55 -0
  203. package/src/runtime/release-artifacts.ts +19 -103
  204. package/src/runtime/resume-notice.ts +1 -1
  205. package/src/runtime/services.ts +81 -82
  206. package/src/runtime/session-continuity-hints.ts +38 -0
  207. package/src/runtime/surface-feature-flags.ts +40 -9
  208. package/src/runtime/terminal-output-guard.ts +2 -2
  209. package/src/runtime/trust/workspace-registration.ts +10 -2
  210. package/src/runtime/trust/workspace-trust.ts +58 -31
  211. package/src/runtime/ui-services.ts +3 -1
  212. package/src/runtime/update-check.ts +15 -69
  213. package/src/runtime/workstream-services.ts +2 -2
  214. package/src/shell/blocking-input.ts +102 -6
  215. package/src/shell/ui-openers.ts +38 -6
  216. package/src/utils/format-duration.ts +1 -1
  217. package/src/verification/verification-ledger.ts +21 -4
  218. package/src/version.ts +1 -1
  219. package/src/input/commands/flags-runtime.ts +0 -328
@@ -1,5 +1,6 @@
1
1
  import { type Line } from '../types/grid.ts';
2
2
  import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
3
+ import { wrapWithHangingIndent } from './text-layout.ts';
3
4
  import type { AutocompleteEngine } from '../input/autocomplete.ts';
4
5
  import {
5
6
  createOverlayBorderLine,
@@ -29,6 +30,14 @@ function putText(line: Line, startX: number, maxWidth: number, text: string, sty
29
30
 
30
31
  /**
31
32
  * Render the slash command autocomplete dropdown as Line[] for overlay in the viewport.
33
+ *
34
+ * A row's description renders inline only when it genuinely fits beside the
35
+ * command name; otherwise the FULL description wraps onto its own line(s)
36
+ * below the command name, with no line cap — descriptive text is never
37
+ * clipped (same fix as selection-modal-overlay.ts's modal rule). Item
38
+ * visibility is row-budget aware (a wrapped description can cost more than
39
+ * one physical row), always showing the selected item in full and falling
40
+ * back to the existing position counter rather than losing text.
32
41
  */
33
42
  export function renderAutocompleteOverlay(
34
43
  autocomplete: AutocompleteEngine,
@@ -39,12 +48,15 @@ export function renderAutocompleteOverlay(
39
48
  if (!state.active || state.results.length === 0) return [];
40
49
 
41
50
  const lines: Line[] = [];
51
+ const chromeRows = 4;
42
52
  const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
43
53
  margin: 2,
44
54
  maxWidth: 88,
45
- chromeRows: 4,
55
+ chromeRows,
46
56
  minContentRows: 6,
47
- maxContentRows: 10,
57
+ // Rows grow with wrapped descriptions (never clipped) rather than
58
+ // staying pinned at a small constant — still bounded by the viewport.
59
+ maxContentRows: Math.max(10, viewportHeight - chromeRows - 4),
48
60
  });
49
61
  const layout = createOverlayBoxLayout(width, metrics.margin, metrics.boxWidth);
50
62
 
@@ -68,88 +80,129 @@ export function renderAutocompleteOverlay(
68
80
 
69
81
  const results = state.results;
70
82
  const total = results.length;
71
- const maxVisible = metrics.contentRows;
72
- let startIdx = 0;
73
- if (total > maxVisible) {
74
- startIdx = Math.max(
75
- 0,
76
- Math.min(
77
- state.selectedIndex - Math.floor(maxVisible / 2),
78
- total - maxVisible,
79
- ),
80
- );
81
- }
82
- const endIdx = Math.min(startIdx + maxVisible, total);
83
+ const rowBudget = Math.max(1, metrics.contentRows);
84
+
85
+ const indicatorWidth = 2;
86
+ const maxCommandWidth = Math.min(18, Math.max(10, Math.floor(layout.innerWidth * 0.28)));
87
+ const gapWidth = 2;
88
+ const descWidth = Math.max(0, layout.innerWidth - indicatorWidth - maxCommandWidth - gapWidth);
89
+ const wrapWidth = Math.max(8, layout.innerWidth - indicatorWidth);
83
90
 
84
- // UX-C palette curation (item 4): on a bare '/' (query === ''), the results
91
+ // palette curation (item 4): on a bare '/' (query === ''), the results
85
92
  // list is "common tier" (score 2) followed by "alphabetical rest" (score
86
93
  // 1) — see CommandRegistry.fuzzyMatch. commonCount marks that boundary;
87
- // draw a one-row separator there when it falls inside the visible window,
88
- // consuming one of the maxVisible slots so the box height never changes.
94
+ // a one-row separator draws there when it falls inside the visible window.
89
95
  const hasCommonSeparator = state.query === '' && state.commonCount > 0 && state.commonCount < total;
90
- type DisplayRow = { type: 'item'; index: number } | { type: 'separator' };
91
- const displayRows: DisplayRow[] = [];
92
- for (let i = startIdx; i < endIdx && displayRows.length < maxVisible; i++) {
93
- if (hasCommonSeparator && i === state.commonCount) {
94
- displayRows.push({ type: 'separator' });
95
- if (displayRows.length >= maxVisible) break;
96
- }
97
- displayRows.push({ type: 'item', index: i });
96
+
97
+ function descriptionFitsInline(description: string): boolean {
98
+ return descWidth >= 12 && getDisplayWidth(description) <= descWidth;
98
99
  }
99
100
 
100
- const indicatorWidth = 2;
101
- const maxCommandWidth = Math.min(18, Math.max(10, Math.floor(layout.innerWidth * 0.28)));
102
- const gapWidth = 2;
103
- const descWidth = Math.max(0, layout.innerWidth - indicatorWidth - maxCommandWidth - gapWidth);
101
+ /** Physical rows a result occupies: 1 for the command-name row, plus one more per wrapped description line when it doesn't fit inline, plus 1 more when the common/rest separator sits immediately before it. */
102
+ function rowCostFor(index: number): number {
103
+ const { command } = results[index];
104
+ const rows = descriptionFitsInline(command.description)
105
+ ? 1
106
+ : 1 + wrapWithHangingIndent(command.description, wrapWidth, '').length;
107
+ const separatorCost = hasCommonSeparator && index === state.commonCount ? 1 : 0;
108
+ return rows + separatorCost;
109
+ }
104
110
 
105
- for (const row of displayRows) {
106
- if (row.type === 'separator') {
111
+ // Row-budget-aware windowing (mirrors selection-modal-overlay.ts's fix): a
112
+ // window of results is chosen by their REAL rendered height rather than
113
+ // assuming one row per item, always including the selected item in full,
114
+ // growing outward (roughly centered) until the budget is spent.
115
+ const selectedIdx = Math.max(0, Math.min(state.selectedIndex, total - 1));
116
+ let startIdx = selectedIdx;
117
+ let endIdx = selectedIdx + 1;
118
+ let usedRows = rowCostFor(selectedIdx);
119
+ let growBefore = true;
120
+ while (startIdx > 0 || endIdx < total) {
121
+ const canGrowBefore = growBefore && startIdx > 0;
122
+ const canGrowAfter = !growBefore && endIdx < total;
123
+ if (canGrowBefore) {
124
+ const cost = rowCostFor(startIdx - 1);
125
+ if (usedRows + cost > rowBudget) break;
126
+ startIdx -= 1;
127
+ usedRows += cost;
128
+ } else if (canGrowAfter) {
129
+ const cost = rowCostFor(endIdx);
130
+ if (usedRows + cost > rowBudget) break;
131
+ endIdx += 1;
132
+ usedRows += cost;
133
+ } else if (startIdx > 0) {
134
+ const cost = rowCostFor(startIdx - 1);
135
+ if (usedRows + cost > rowBudget) break;
136
+ startIdx -= 1;
137
+ usedRows += cost;
138
+ } else if (endIdx < total) {
139
+ const cost = rowCostFor(endIdx);
140
+ if (usedRows + cost > rowBudget) break;
141
+ endIdx += 1;
142
+ usedRows += cost;
143
+ } else {
144
+ break;
145
+ }
146
+ growBefore = !growBefore;
147
+ }
148
+
149
+ for (let i = startIdx; i < endIdx; i++) {
150
+ if (hasCommonSeparator && i === state.commonCount) {
107
151
  const sepLine = createOverlayContentLine(width, layout);
108
152
  putText(sepLine, layout.margin + 2, layout.innerWidth, '─'.repeat(layout.innerWidth), { fg: BORDER_FG, dim: true });
109
153
  lines.push(sepLine);
110
- continue;
111
154
  }
112
- const i = row.index;
155
+
113
156
  const { command } = results[i];
114
157
  const isSelected = i === state.selectedIndex;
115
- const line = createOverlayContentLine(width, layout, BORDER_FG, isSelected ? SELECTED_BG : '');
158
+ const bg = isSelected ? SELECTED_BG : '';
116
159
  const indicator = isSelected ? '▸ ' : ' ';
117
160
  const commandText = fitDisplay(
118
161
  truncateDisplay(`/${command.name}`, maxCommandWidth),
119
162
  maxCommandWidth,
120
163
  );
121
- const descriptionText = fitDisplay(
122
- truncateDisplay(command.description, descWidth),
123
- descWidth,
124
- );
164
+
165
+ const line = createOverlayContentLine(width, layout, BORDER_FG, bg);
125
166
  let x = layout.margin + 2;
126
167
  putText(line, x, indicatorWidth, indicator, {
127
168
  fg: isSelected ? TITLE_FG : MUTED_FG,
128
- bg: isSelected ? SELECTED_BG : '',
169
+ bg,
129
170
  bold: isSelected,
130
171
  });
131
172
  x += indicatorWidth;
132
173
  putText(line, x, maxCommandWidth, commandText, {
133
174
  fg: isSelected ? TITLE_FG : BODY_FG,
134
- bg: isSelected ? SELECTED_BG : '',
175
+ bg,
135
176
  bold: isSelected,
136
177
  });
137
178
  x += maxCommandWidth;
138
- putText(line, x, gapWidth, ' ', {
139
- fg: BODY_FG,
140
- bg: isSelected ? SELECTED_BG : '',
141
- });
142
- x += gapWidth;
143
- putText(line, x, descWidth, descriptionText, {
144
- fg: isSelected ? BODY_FG : MUTED_FG,
145
- bg: isSelected ? SELECTED_BG : '',
146
- bold: false,
147
- });
148
- lines.push(line);
179
+
180
+ if (descriptionFitsInline(command.description)) {
181
+ putText(line, x, gapWidth, ' ', { fg: BODY_FG, bg });
182
+ x += gapWidth;
183
+ putText(line, x, descWidth, fitDisplay(command.description, descWidth), {
184
+ fg: isSelected ? BODY_FG : MUTED_FG,
185
+ bg,
186
+ bold: false,
187
+ });
188
+ lines.push(line);
189
+ } else {
190
+ // Doesn't fit beside the command name — wrap the FULL description onto
191
+ // its own line(s) below at the full row width, rather than clipping it.
192
+ lines.push(line);
193
+ for (const wrapped of wrapWithHangingIndent(command.description, wrapWidth, '')) {
194
+ const descLine = createOverlayContentLine(width, layout, BORDER_FG, bg);
195
+ putText(descLine, layout.margin + 2 + indicatorWidth, wrapWidth, fitDisplay(wrapped, wrapWidth), {
196
+ fg: isSelected ? BODY_FG : MUTED_FG,
197
+ bg,
198
+ });
199
+ lines.push(descLine);
200
+ }
201
+ }
149
202
  }
150
203
 
151
- if (total > maxVisible) {
152
- const scrollLine = createOverlayContentLine(width, layout);
204
+ if (startIdx > 0 || endIdx < total) {
205
+ const scrollLine = createOverlayContentLine(width, layout);
153
206
  const scrollText = `${state.selectedIndex + 1}/${total}`;
154
207
  putText(
155
208
  scrollLine,
@@ -162,7 +215,7 @@ export function renderAutocompleteOverlay(
162
215
  }
163
216
 
164
217
  const footerLine = createOverlayContentLine(width, layout);
165
- // UX-C vocab unification: "Run"/"Close" match the /help selection modal's
218
+ // vocab unification: "Run"/"Close" match the /help selection modal's
166
219
  // hint bar (selection-modal-overlay.ts) — both surfaces execute a command
167
220
  // on Enter, so they now say so the same way.
168
221
  const hints = '[Tab] Complete [Up/Down] Navigate [Enter] Run [Esc] Close';
@@ -11,11 +11,11 @@ import { UI_TONES } from './ui-primitives.ts';
11
11
  * property/comment) are pinned to the same values as the tree-sitter
12
12
  * Vaporwave palette in syntax-highlighter.ts's TOKEN_STYLES map, so a code
13
13
  * block that starts on the regex fallback and gets replaced by the async
14
- * tree-sitter parse doesn't visibly shift color mid-stream (WO-207c).
14
+ * tree-sitter parse doesn't visibly shift color mid-stream.
15
15
  * `accent` and `bg` are chrome (the language-label header bar and the body
16
16
  * background), not syntax tokens, and keep their original VS Code Dark+
17
17
  * values — folded here from two separate literals (one in the header, one
18
- * duplicated on the footer line) into a single named source (WO-207b).
18
+ * duplicated on the footer line) into a single named source.
19
19
  */
20
20
  const FALLBACK_THEME = {
21
21
  string: '#00ff88',
@@ -17,7 +17,7 @@ import type { CompactionQualityScore } from './compaction-quality.ts';
17
17
  // ─── formatCompactionEvent ────────────────────────────────────────────────────
18
18
 
19
19
  /**
20
- * Exported for direct unit testing (W5.4/B28) — the grade suffix is a pure
20
+ * Exported for direct unit testing () — the grade suffix is a pure
21
21
  * function of the event and an optional score, independent of whichever
22
22
  * runtime lookup (getCompactionQualityScore) supplies that score.
23
23
  */
@@ -47,7 +47,7 @@ export interface CompactionAfterOptions {
47
47
  /** Number of session memories that survived compaction. */
48
48
  readonly pinnedMemoryCount: number;
49
49
  /**
50
- * Out-of-band quality-score grade for this run (W5.4/B28). Omitted or null
50
+ * Out-of-band quality-score grade for this run (). Omitted or null
51
51
  * when no score was computed (e.g. the small-window compaction path, which
52
52
  * has no CompactionEvent to key a score by) — no line is rendered then.
53
53
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Compaction quality-score bridging.
3
3
  *
4
- * Reality check (wo803): the SDK's `computeQualityScore()`
4
+ * Reality check: the SDK's `computeQualityScore()`
5
5
  * (platform/runtime/compaction/quality-score.ts) is only ever called from a
6
6
  * SEPARATE pipeline, `CompactionManager` (platform/runtime/compaction/manager.ts).
7
7
  * The TUI's actual `/compact` path never instantiates `CompactionManager` — it
@@ -95,7 +95,7 @@ export class Compositor {
95
95
 
96
96
  public composite(params: CompositeRequest): void {
97
97
  const { width, height, header, viewport, footer, selection, search, panel, panelWidth } = params;
98
- // R3: Reuse back-buffer instead of allocating each frame
98
+ // Reuse back-buffer instead of allocating each frame
99
99
  if (!this.backBuffer) {
100
100
  this.backBuffer = new TerminalBuffer(width, height);
101
101
  } else {
@@ -284,7 +284,7 @@ export class Compositor {
284
284
  });
285
285
 
286
286
  // 4. Diff and Render
287
- // R3: Diff against front-buffer (last-rendered), then swap front/back — no clone() needed
287
+ // Diff against front-buffer (last-rendered), then swap front/back — no clone() needed
288
288
  const diff = this.diffEngine.diff(this.frontBuffer, newBuffer);
289
289
  if (diff) {
290
290
  allowTerminalWrite(() => this.stdout.write(diff));
@@ -22,7 +22,7 @@ export function renderConfigModal(modal: ConfigModal, width: number, height: num
22
22
  // rather than overflowing (posture header + separator + chrome ≈ 8 rows).
23
23
  const visible = Math.max(3, Math.min(16, height - 8));
24
24
  modal.setViewportRows(visible);
25
- // DEBT-5 item 2: the list section wraps a row label at (contentW - 2),
25
+ // item 2: the list section wraps a row label at (contentW - 2),
26
26
  // where contentW = boxW - 4 (see ModalFactory._renderListSection). Compute
27
27
  // that exact column here so getRenderModel's wrap-clamp measures against
28
28
  // reality instead of a fixed guess.
@@ -76,7 +76,7 @@ export function applyConversationOverlays(
76
76
  next.push(...renderHistorySearchOverlay(input.historySearch, conversationWidth));
77
77
  }
78
78
 
79
- // W6.1 retirement: the process, agent-detail, and live-tail modal overlays
79
+ // retirement: the process, agent-detail, and live-tail modal overlays
80
80
  // were removed with those modals — F2 now opens the Fleet panel, which
81
81
  // subsumes the live process tree.
82
82
 
@@ -2,7 +2,7 @@
2
2
  * footer-tips.ts — the persistent discoverability hint shown in the shell
3
3
  * footer's tip slot.
4
4
  *
5
- * WO-151: instead of a single frozen "/help for commands" line, the footer
5
+ * instead of a single frozen "/help for commands" line, the footer
6
6
  * surfaces a rotating set of the highest-value affordances (panels, process
7
7
  * monitor, help, quit). Rotation is *contextual, not timed*: when an agent turn
8
8
  * is actively running, the process-monitor tip is promoted to the front so the
@@ -24,7 +24,7 @@ export function isAgentActive(composerStatus: string | undefined): boolean {
24
24
  return composerStatus !== undefined && ACTIVE_TURN_STATUSES.has(composerStatus);
25
25
  }
26
26
 
27
- // UX-C: Ctrl+P is a TOGGLE (open+focus when nothing is open or unfocused,
27
+ // Ctrl+P is a TOGGLE (open+focus when nothing is open or unfocused,
28
28
  // hide when the workspace already has focus — see openPanelPicker in
29
29
  // shell/ui-openers.ts). The bare noun 'panels' undersold that; naming the verb
30
30
  // keeps the tip honest about what the chord actually does.
@@ -33,7 +33,7 @@ const TIP_PANELS = 'Ctrl+P toggle panels';
33
33
  // tip names 'fleet', not 'processes'.
34
34
  const TIP_PROCESSES = 'F2 fleet';
35
35
  const TIP_HELP = '? help';
36
- // W6.2 f: an empty-composer Ctrl+C does NOT quit on the first press — it arms a
36
+ // f: an empty-composer Ctrl+C does NOT quit on the first press — it arms a
37
37
  // ~1s "press again to exit" confirm, and only a SECOND Ctrl+C within that window
38
38
  // exits (an intentional accidental-exit guard). The old 'Ctrl+C quit' tip
39
39
  // implied a single press quits, so two presses seconds apart appeared to do
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * hint-grammar.ts — the single hint-bar grammar for every overlay footer.
3
3
  *
4
- * WO-151: one grammar across the model picker, settings modal, help/shortcuts
4
+ * one grammar across the model picker, settings modal, help/shortcuts
5
5
  * overlays, and ModalFactory variants so footers stop drifting into three
6
6
  * different dialects (verbless brackets, prose sentences, ad-hoc separators).
7
7
  *
@@ -195,7 +195,7 @@ export class ModalFactory {
195
195
  }
196
196
 
197
197
  // Footer / hint bar. Hint segments join with the shared middle-dot grammar
198
- // separator (WO-151) so every modal footer reads as one dialect.
198
+ // separator so every modal footer reads as one dialect.
199
199
  const hintStr = config.hints
200
200
  ? config.hints.join(HINT_SEPARATOR)
201
201
  : (config.footer ?? '');
@@ -2,7 +2,7 @@
2
2
  * Number of fixed chrome lines in the model-picker overlay (title + search + divider + detail×2 + footer).
3
3
  * Used by callers to compute maxVisible item rows.
4
4
  *
5
- * WO-206: this file used to also hold `renderModelPickerOverlay`, the
5
+ * this file used to also hold `renderModelPickerOverlay`, the
6
6
  * overlay-style model-picker renderer. It was superseded by
7
7
  * `renderModelWorkspace` (model-workspace.ts) — conversation-overlays.ts
8
8
  * routes `input.modelPicker.active` there, not here — and had no remaining
@@ -395,9 +395,14 @@ export function renderModelWorkspace(picker: ModelPickerModal, width: number, vi
395
395
  const bodyRows = safeHeight - 3 - footerRows;
396
396
  const maxDetailRows = Math.max(3, bodyRows - 2);
397
397
  const minDetailRows = Math.min(6, maxDetailRows);
398
- const detailRows = clamp(Math.round(bodyRows * 0.32), minDetailRows, maxDetailRows);
398
+ const details = detailLines(picker, contentW - 2);
399
+ // Never drop detail lines (the modal rule: UI-authored descriptive text is
400
+ // always shown in full). Size the detail band to the ACTUAL content,
401
+ // growing past the old fixed proportional default up to maxDetailRows —
402
+ // the ceiling that keeps at least a header row and one list row available —
403
+ // rather than silently slicing off whatever didn't fit a 32% guess.
404
+ const detailRows = clamp(Math.max(details.length, Math.round(bodyRows * 0.32)), minDetailRows, maxDetailRows);
399
405
  const listRows = Math.max(1, bodyRows - detailRows - 1);
400
- const details = detailLines(picker, contentW - 2).slice(0, detailRows);
401
406
 
402
407
  for (let row = 0; row < bodyRows; row += 1) {
403
408
  const inDetail = row < detailRows;
@@ -7,7 +7,7 @@ import { createSplitPaneLayout } from './layout-engine.ts';
7
7
  import { renderPanelWorkspaceBar } from './panel-workspace-bar.ts';
8
8
 
9
9
  /**
10
- * R2: Per-panel render cache for dirty-flag skipping.
10
+ * Per-panel render cache for dirty-flag skipping.
11
11
  *
12
12
  * Maintainability hazard — mid-render invalidation race:
13
13
  *
@@ -59,7 +59,7 @@ function getRenderGenState(panel: Panel): { gen: number } {
59
59
  return state;
60
60
  }
61
61
 
62
- /** R2: Render a panel, skipping if nothing changed. Returns cached lines on a skip. */
62
+ /** Render a panel, skipping if nothing changed. Returns cached lines on a skip. */
63
63
  export function renderPanel(panel: Panel, width: number, height: number): Line[] {
64
64
  const cached = panelRenderCache.get(panel);
65
65
  if (cached && !panel.needsRender && cached.width === width && cached.height === height) {
@@ -27,7 +27,7 @@ export function renderPanelWorkspaceBar(
27
27
  // A tab can be active-but-not-focused (selected in the unfocused pane) or
28
28
  // active-and-focused (selected in the focused pane).
29
29
  //
30
- // A14: the pane marker uses ▲/▼ (top/bottom pane). The old '^'/'v'
30
+ // The pane marker uses ▲/▼ (top/bottom pane). The old '^'/'v'
31
31
  // prefixes read as a Ctrl caret (^) implying a non-existent Ctrl+<tab>
32
32
  // hotkey. The REAL per-tab jump key is Alt+N (panel-tab-1..9), rendered
33
33
  // here as ⌥N for the first nine tabs so the shown affordance matches the
@@ -17,7 +17,7 @@ import { formatHints } from './hint-grammar.ts';
17
17
  * Agent-count label. The footer counts only ACTIVE agents, while the fleet
18
18
  * lists every node (running, terminal, chains, watchers). Label the count
19
19
  * "active" so it is never misread as a grand total — the [Enter] View hint
20
- * opens the fleet for the full picture. (UX-B item 5d.)
20
+ * opens the fleet for the full picture. (item 5d.)
21
21
  */
22
22
  function agentCountLabel(agentCount: number): string {
23
23
  return `${agentCount} agent${agentCount !== 1 ? 's' : ''} active`;
@@ -267,7 +267,7 @@ export function renderSelectionModalOverlay(
267
267
 
268
268
  const footerLine = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg);
269
269
  const selectedItem = modal.getSelected();
270
- // UX-C vocab unification: a caller-supplied primaryVerbLabel (e.g. /help's
270
+ // vocab unification: a caller-supplied primaryVerbLabel (e.g. /help's
271
271
  // "Run", matching the slash-command palette) wins over the generic
272
272
  // per-primaryAction default.
273
273
  const primaryVerb = modal.primaryVerbLabel
@@ -42,7 +42,7 @@ function isClosedStatus(status: string): boolean {
42
42
  }
43
43
 
44
44
  /**
45
- * D-TUI/#A2: closed sessions carry an optional, honest reason for WHY they
45
+ * Closed sessions carry an optional, honest reason for WHY they
46
46
  * closed under `metadata.closeReason` (SDK's `SharedSessionCloseReason`,
47
47
  * 'closeReason' key — see `@pellux/goodvibes-sdk` platform/control-plane
48
48
  * session-broker-sessions.ts's `readSessionCloseReason`). `metadata` is an
@@ -58,7 +58,7 @@ function readCloseReason(record: SharedSessionRecord): string | undefined {
58
58
  /**
59
59
  * A GC sweep closing an idle session ('idle-reaped') auto-reopens on the next
60
60
  * heartbeat — it is NOT the same event as a deliberate user/surface close, so
61
- * it must never render under the same "closed" badge (W4/#A2). Tolerant of
61
+ * it must never render under the same "closed" badge. Tolerant of
62
62
  * records without the field (pre-feature builds, or a deliberate close).
63
63
  */
64
64
  function isReapedRecord(record: SharedSessionRecord): boolean {
@@ -11,7 +11,7 @@ import { UI_TONES } from './ui-primitives.ts';
11
11
 
12
12
  /**
13
13
  * "Modified / active" accent for settings rows — cyan-green, no UI_TONES
14
- * role matches it (WO-207b: preserved byte-exact as a named constant).
14
+ * role matches it (preserved byte-exact as a named constant).
15
15
  */
16
16
  const SETTINGS_ACCENT = '#00ffcc';
17
17
 
@@ -31,6 +31,14 @@ export function formatValue(entry: SettingEntry): string {
31
31
  // Array-backed settings (e.g. worktree.setup.commands) display as a
32
32
  // comma-separated list, matching how they're edited (see worktree-setup-config.ts).
33
33
  if (Array.isArray(val)) return val.length > 0 ? val.join(', ') : '(empty)';
34
+ // Object-backed settings (e.g. pricing.modelPrices) display their JSON when
35
+ // short, or an honest entry count — never the useless "[object Object]".
36
+ if (typeof val === 'object') {
37
+ const keys = Object.keys(val as Record<string, unknown>);
38
+ if (keys.length === 0) return '(none set)';
39
+ const json = JSON.stringify(val);
40
+ return json.length <= 64 ? json : `${keys.length} entr${keys.length === 1 ? 'y' : 'ies'} (Enter to edit as JSON)`;
41
+ }
34
42
  return String(val);
35
43
  }
36
44
 
@@ -57,6 +65,8 @@ export const CATEGORY_LABELS: Record<(typeof SETTINGS_CATEGORIES)[number], strin
57
65
  display: 'Display',
58
66
  ui: 'UI',
59
67
  provider: 'Provider',
68
+ pricing: 'Pricing',
69
+ update: 'Updates',
60
70
  subscriptions: 'Subscriptions',
61
71
  behavior: 'Behavior',
62
72
  storage: 'Storage',
@@ -87,7 +97,6 @@ export const CATEGORY_LABELS: Record<(typeof SETTINGS_CATEGORIES)[number], strin
87
97
  release: 'Release',
88
98
  danger: 'Danger',
89
99
  tools: 'Tools',
90
- flags: 'Advanced Features',
91
100
  network: 'Network',
92
101
  relay: 'Relay',
93
102
  learning: 'Learning',
@@ -171,9 +180,9 @@ const SETTING_LABELS: Partial<Record<string, string>> = {
171
180
  };
172
181
 
173
182
  export function getSettingLabel(entry: SettingEntry): string {
174
- // Feature-unit toggle headers carry the flag's human name, not the raw
175
- // `featureFlags.<id>` key tail.
176
- if (entry.flag) return entry.flag.flag.name;
183
+ // Feature-unit headers carry the feature's human name, not the raw
184
+ // enablement-key tail.
185
+ if (entry.flag) return entry.flag.feature.name;
177
186
  return SETTING_LABELS[entry.setting.key] ?? entry.setting.key.replace(/^[^.]+\./, '');
178
187
  }
179
188