@pellux/goodvibes-tui 1.16.0 → 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 (220) hide show
  1. package/CHANGELOG.md +95 -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/onboarding/onboarding-wizard.ts +22 -8
  167. package/src/renderer/panel-composite.ts +2 -2
  168. package/src/renderer/panel-workspace-bar.ts +1 -1
  169. package/src/renderer/process-indicator.ts +1 -1
  170. package/src/renderer/selection-modal-overlay.ts +102 -24
  171. package/src/renderer/session-picker-modal.ts +2 -2
  172. package/src/renderer/settings-modal-helpers.ts +14 -5
  173. package/src/renderer/settings-modal.ts +127 -39
  174. package/src/renderer/shell-surface.ts +3 -0
  175. package/src/renderer/terminal-bg-probe.ts +1 -1
  176. package/src/renderer/theme.ts +3 -3
  177. package/src/renderer/tool-call.ts +1 -1
  178. package/src/renderer/tool-result-summary.ts +1 -1
  179. package/src/renderer/turn-injection.ts +7 -6
  180. package/src/renderer/ui-factory.ts +6 -1
  181. package/src/runtime/bootstrap-command-context.ts +4 -1
  182. package/src/runtime/bootstrap-command-parts.ts +6 -4
  183. package/src/runtime/bootstrap-core.ts +35 -35
  184. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  185. package/src/runtime/bootstrap-shell.ts +6 -5
  186. package/src/runtime/bootstrap.ts +34 -32
  187. package/src/runtime/channel-composition.ts +46 -0
  188. package/src/runtime/code-index-services.ts +3 -4
  189. package/src/runtime/daemon-attach-notices.ts +120 -0
  190. package/src/runtime/durability-services.ts +55 -0
  191. package/src/runtime/embedded-service-factories.ts +40 -0
  192. package/src/runtime/feature-settings.ts +147 -0
  193. package/src/runtime/index.ts +4 -21
  194. package/src/runtime/legacy-daemon-migration.ts +29 -19
  195. package/src/runtime/memory-fold.ts +1 -1
  196. package/src/runtime/notification-dispatch.ts +110 -0
  197. package/src/runtime/onboarding/apply.ts +3 -29
  198. package/src/runtime/onboarding/fast-path.ts +39 -6
  199. package/src/runtime/onboarding/provider-key-capture.ts +126 -0
  200. package/src/runtime/onboarding/snapshot.ts +0 -1
  201. package/src/runtime/onboarding/types.ts +9 -3
  202. package/src/runtime/provider-fallback.ts +55 -0
  203. package/src/runtime/provider-optimizer-wiring.ts +55 -0
  204. package/src/runtime/release-artifacts.ts +19 -103
  205. package/src/runtime/resume-notice.ts +1 -1
  206. package/src/runtime/services.ts +81 -82
  207. package/src/runtime/session-continuity-hints.ts +38 -0
  208. package/src/runtime/surface-feature-flags.ts +40 -9
  209. package/src/runtime/terminal-output-guard.ts +2 -2
  210. package/src/runtime/trust/workspace-registration.ts +10 -2
  211. package/src/runtime/trust/workspace-trust.ts +58 -31
  212. package/src/runtime/ui-services.ts +3 -1
  213. package/src/runtime/update-check.ts +15 -69
  214. package/src/runtime/workstream-services.ts +2 -2
  215. package/src/shell/blocking-input.ts +102 -6
  216. package/src/shell/ui-openers.ts +38 -6
  217. package/src/utils/format-duration.ts +1 -1
  218. package/src/verification/verification-ledger.ts +21 -4
  219. package/src/version.ts +1 -1
  220. 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;
@@ -295,15 +295,25 @@ function renderWideLayout(
295
295
  const centerStart = leftSeparatorX + 1;
296
296
  const rightSeparatorX = centerStart + centerWidth;
297
297
  const rightStart = rightSeparatorX + 1;
298
- const descriptionLines = wrapText(currentStep.description, Math.max(18, centerWidth - 2)).slice(0, 2);
299
298
  const summaryLines = [
300
299
  currentStep.summaryTitle,
301
300
  ...currentStep.summaryLines.slice(0, 2),
302
301
  `Fields ${wizard.getCompletedFieldCount(wizard.stepIndex)}/${wizard.getStepFieldCount(wizard.stepIndex)} complete`,
303
302
  changedScreensLabel(wizard),
304
303
  ];
305
- const fieldStartRow = 6;
304
+ const centerTextWidth = Math.max(18, centerWidth - 2);
305
+ const descriptionLines = wrapText(currentStep.description, centerTextWidth).slice(0, 2);
306
306
  const selectedText = selectedFieldText(wizard);
307
+ // The selected field's own hint is the direct analog of the reported
308
+ // defect (descriptive text explaining what the currently-focused option
309
+ // does) — some hints run past 400 characters, e.g. security-caveat text.
310
+ // It is never clipped to a single row: it wraps onto as many lines as it
311
+ // needs, and the field list below starts after it, growing/shrinking the
312
+ // field-list area rather than losing hint text. Title/description/controls
313
+ // stay fixed-row (short chrome strings, not the field-specific
314
+ // explanation), matching prior layout exactly when the hint fits on one line.
315
+ const hintLines = wrapText(selectedText.hint, centerTextWidth);
316
+ const fieldStartRow = 5 + hintLines.length;
307
317
  const fieldRows = buildFieldRows(wizard, visibleFields, Math.max(0, bodyRows - fieldStartRow));
308
318
 
309
319
  const topLine = createOverlayFilledBorderLine(
@@ -397,9 +407,9 @@ function renderWideLayout(
397
407
  bg: DEFAULT_OVERLAY_PALETTE.selectedBg,
398
408
  bold: true,
399
409
  });
400
- } else if (row === 5) {
410
+ } else if (row < fieldStartRow) {
401
411
  fillWidth(line, centerStart, centerWidth, DEFAULT_OVERLAY_PALETTE.selectedBg);
402
- putOverlayText(line, centerStart + 1, centerWidth - 2, truncateDisplay(selectedText.hint, centerWidth - 2), {
412
+ putOverlayText(line, centerStart + 1, centerWidth - 2, hintLines[row - 5] ?? '', {
403
413
  fg: UI_TONES.fg.secondary,
404
414
  bg: DEFAULT_OVERLAY_PALETTE.selectedBg,
405
415
  });
@@ -481,9 +491,13 @@ function renderCollapsedLayout(
481
491
  const bodyBg = UI_TONES.bg.base;
482
492
  const innerStart = layout.margin + 1;
483
493
  const innerWidth = layout.innerWidth;
484
- const descriptionLines = wrapText(currentStep.description, Math.max(14, innerWidth - 2)).slice(0, 2);
485
- const fieldStartRow = 6;
494
+ const innerTextWidth = Math.max(14, innerWidth - 2);
495
+ const descriptionLines = wrapText(currentStep.description, innerTextWidth).slice(0, 2);
486
496
  const selectedText = selectedFieldText(wizard);
497
+ // Same rule as the wide layout: the selected field's hint wraps onto as
498
+ // many lines as it needs rather than being clipped to a single row.
499
+ const hintLines = wrapText(selectedText.hint, innerTextWidth);
500
+ const fieldStartRow = 5 + hintLines.length;
487
501
  const fieldRows = buildFieldRows(wizard, visibleFields, Math.max(0, bodyRows - fieldStartRow));
488
502
 
489
503
  const topLine = createOverlayFilledBorderLine(
@@ -559,9 +573,9 @@ function renderCollapsedLayout(
559
573
  bg: DEFAULT_OVERLAY_PALETTE.selectedBg,
560
574
  bold: true,
561
575
  });
562
- } else if (row === 5) {
576
+ } else if (row < fieldStartRow) {
563
577
  fillWidth(line, innerStart, innerWidth, DEFAULT_OVERLAY_PALETTE.selectedBg);
564
- putOverlayText(line, innerStart + 1, innerWidth - 2, truncateDisplay(selectedText.hint, innerWidth - 2), {
578
+ putOverlayText(line, innerStart + 1, innerWidth - 2, hintLines[row - 5] ?? '', {
565
579
  fg: UI_TONES.fg.secondary,
566
580
  bg: DEFAULT_OVERLAY_PALETTE.selectedBg,
567
581
  });
@@ -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`;