@deepseek-harness-tui/dsh-tui 0.10.2 → 0.11.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 (277) hide show
  1. package/README.md +171 -124
  2. package/bin/dsh-tui.js +748 -22
  3. package/cordis.patch.yml +38 -10
  4. package/cordis.yml +2 -0
  5. package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
  6. package/lib/types/adapter/channel/session-projection.js +2 -0
  7. package/lib/types/adapter/channel/ui-policy.d.ts +4 -1
  8. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  9. package/lib/types/adapter/channel/ui-policy.js +5 -1
  10. package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -1
  11. package/lib/types/adapter/kernel/host-facade.js +1 -0
  12. package/lib/types/adapter/kernel/replay.d.ts.map +1 -1
  13. package/lib/types/adapter/kernel/replay.js +3 -0
  14. package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
  15. package/lib/types/adapter/kernel/runtime.js +2 -0
  16. package/lib/types/adapter/kernel/slices/workspace.d.ts.map +1 -1
  17. package/lib/types/adapter/kernel/slices/workspace.js +1 -0
  18. package/lib/types/adapter/ports/channel-ui.d.ts +22 -2
  19. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  20. package/lib/types/adapter/ports/channel-view.d.ts +66 -0
  21. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  22. package/lib/types/adapter/ports/channel-workspace.d.ts +23 -0
  23. package/lib/types/adapter/ports/channel-workspace.d.ts.map +1 -1
  24. package/lib/types/adapter/ports/workspace.d.ts +2 -0
  25. package/lib/types/adapter/ports/workspace.d.ts.map +1 -1
  26. package/lib/types/adapter/upstream/workspace-driver.d.ts.map +1 -1
  27. package/lib/types/adapter/upstream/workspace-driver.js +10 -2
  28. package/lib/types/commands.d.ts.map +1 -1
  29. package/lib/types/commands.js +1 -0
  30. package/lib/types/components/ContextBarView.d.ts +15 -7
  31. package/lib/types/components/ContextBarView.d.ts.map +1 -1
  32. package/lib/types/components/ContextBarView.js +26 -22
  33. package/lib/types/components/ImagePreviewOverlay.d.ts.map +1 -1
  34. package/lib/types/components/ImagePreviewOverlay.js +11 -5
  35. package/lib/types/components/Markdown.d.ts +11 -1
  36. package/lib/types/components/Markdown.d.ts.map +1 -1
  37. package/lib/types/components/Markdown.js +20 -2
  38. package/lib/types/components/MermaidDiagram.d.ts +13 -0
  39. package/lib/types/components/MermaidDiagram.d.ts.map +1 -0
  40. package/lib/types/components/MermaidDiagram.js +61 -0
  41. package/lib/types/components/MessageList.d.ts +26 -1
  42. package/lib/types/components/MessageList.d.ts.map +1 -1
  43. package/lib/types/components/MessageList.js +72 -7
  44. package/lib/types/components/PageMargin.d.ts +1 -1
  45. package/lib/types/components/PromptInput.d.ts +37 -1
  46. package/lib/types/components/PromptInput.d.ts.map +1 -1
  47. package/lib/types/components/PromptInput.js +243 -28
  48. package/lib/types/components/ScrollbarGutter.d.ts +20 -5
  49. package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
  50. package/lib/types/components/ScrollbarGutter.js +88 -10
  51. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  52. package/lib/types/components/StreamingMarkdown.js +30 -28
  53. package/lib/types/components/Tooltip.d.ts.map +1 -1
  54. package/lib/types/components/Tooltip.js +45 -7
  55. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/UserPromptMessage.js +5 -7
  57. package/lib/types/components/messages/useImageInspection.d.ts.map +1 -1
  58. package/lib/types/components/messages/useImageInspection.js +19 -9
  59. package/lib/types/components/promptDraftCache.d.ts +102 -0
  60. package/lib/types/components/promptDraftCache.d.ts.map +1 -0
  61. package/lib/types/components/promptDraftCache.js +76 -0
  62. package/lib/types/components/questions/AskUserQuestionPanel.d.ts +17 -1
  63. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  64. package/lib/types/components/questions/AskUserQuestionPanel.js +40 -7
  65. package/lib/types/components/questions/QuestionMinimizedBar.d.ts +12 -0
  66. package/lib/types/components/questions/QuestionMinimizedBar.d.ts.map +1 -0
  67. package/lib/types/components/questions/QuestionMinimizedBar.js +43 -0
  68. package/lib/types/components/sessions/SessionListRow.d.ts +23 -1
  69. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  70. package/lib/types/components/sessions/SessionListRow.js +52 -5
  71. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +49 -0
  72. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -0
  73. package/lib/types/components/workspaces/HomeWorkspaceRow.js +50 -0
  74. package/lib/types/dsh-adapter/bundled-presets.d.ts +10 -0
  75. package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -0
  76. package/lib/types/dsh-adapter/bundled-presets.js +80 -0
  77. package/lib/types/dsh-adapter/channel/action-readiness.d.ts +1 -1
  78. package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
  79. package/lib/types/dsh-adapter/channel/action-readiness.js +3 -0
  80. package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts +1 -1
  81. package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts.map +1 -1
  82. package/lib/types/dsh-adapter/channel/agent-view-projection.js +19 -2
  83. package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
  84. package/lib/types/dsh-adapter/channel/background-action.js +52 -29
  85. package/lib/types/dsh-adapter/channel/binding.d.ts +19 -2
  86. package/lib/types/dsh-adapter/channel/binding.d.ts.map +1 -1
  87. package/lib/types/dsh-adapter/channel/binding.js +78 -18
  88. package/lib/types/dsh-adapter/channel/composer-images.d.ts +5 -0
  89. package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -1
  90. package/lib/types/dsh-adapter/channel/composer-images.js +105 -6
  91. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +8 -0
  92. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -1
  93. package/lib/types/dsh-adapter/channel/context-bookkeeping.js +3 -2
  94. package/lib/types/dsh-adapter/channel/ide-selection.d.ts +58 -0
  95. package/lib/types/dsh-adapter/channel/ide-selection.d.ts.map +1 -0
  96. package/lib/types/dsh-adapter/channel/ide-selection.js +226 -0
  97. package/lib/types/dsh-adapter/channel/input-delivery.d.ts +6 -1
  98. package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
  99. package/lib/types/dsh-adapter/channel/input-delivery.js +9 -1
  100. package/lib/types/dsh-adapter/channel/local-actions.d.ts +3 -19
  101. package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
  102. package/lib/types/dsh-adapter/channel/local-actions.js +2 -1
  103. package/lib/types/dsh-adapter/channel/model-actions.d.ts +4 -1
  104. package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -1
  105. package/lib/types/dsh-adapter/channel/model-actions.js +82 -6
  106. package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
  107. package/lib/types/dsh-adapter/channel/model-switch.js +49 -31
  108. package/lib/types/dsh-adapter/channel/projection.d.ts +4 -0
  109. package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
  110. package/lib/types/dsh-adapter/channel/projection.js +29 -19
  111. package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
  112. package/lib/types/dsh-adapter/channel/reports.js +4 -12
  113. package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
  114. package/lib/types/dsh-adapter/channel/session-fork.js +26 -2
  115. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts +3 -0
  116. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -1
  117. package/lib/types/dsh-adapter/channel/session-live-adoption.js +1 -0
  118. package/lib/types/dsh-adapter/channel/session-metadata.d.ts +10 -0
  119. package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
  120. package/lib/types/dsh-adapter/channel/session-metadata.js +2 -2
  121. package/lib/types/dsh-adapter/channel/session-resume.d.ts +11 -2
  122. package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
  123. package/lib/types/dsh-adapter/channel/session-resume.js +291 -94
  124. package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
  125. package/lib/types/dsh-adapter/channel/session-rewind.js +34 -16
  126. package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
  127. package/lib/types/dsh-adapter/channel/session-tree-actions.js +19 -3
  128. package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
  129. package/lib/types/dsh-adapter/channel/settings-host.js +4 -3
  130. package/lib/types/dsh-adapter/channel/state.d.ts +1 -1
  131. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  132. package/lib/types/dsh-adapter/channel/state.js +1 -1
  133. package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
  134. package/lib/types/dsh-adapter/channel/transcript.js +6 -12
  135. package/lib/types/dsh-adapter/channel/types.d.ts +6 -1
  136. package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
  137. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts +4 -1
  138. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -1
  139. package/lib/types/dsh-adapter/channel/workspace-actions.js +41 -1
  140. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  141. package/lib/types/dsh-adapter/channel.js +67 -6
  142. package/lib/types/dsh-adapter/compat/messages.d.ts +15 -0
  143. package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -0
  144. package/lib/types/dsh-adapter/compat/messages.js +15 -0
  145. package/lib/types/dsh-adapter/compat/settings.d.ts +33 -0
  146. package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -0
  147. package/lib/types/dsh-adapter/compat/settings.js +43 -0
  148. package/lib/types/dsh-adapter/compat/shell.d.ts +25 -0
  149. package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -0
  150. package/lib/types/dsh-adapter/compat/shell.js +11 -0
  151. package/lib/types/dsh-adapter/contract.d.ts +4 -4
  152. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  153. package/lib/types/dsh-adapter/contract.js +15 -6
  154. package/lib/types/dsh-adapter/ide-channel.d.ts +224 -0
  155. package/lib/types/dsh-adapter/ide-channel.d.ts.map +1 -0
  156. package/lib/types/dsh-adapter/ide-channel.js +556 -0
  157. package/lib/types/dsh-adapter/index.d.ts +18 -3
  158. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  159. package/lib/types/dsh-adapter/index.js +32 -5
  160. package/lib/types/dsh-adapter/plugin.d.ts +5 -2
  161. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  162. package/lib/types/dsh-adapter/plugin.js +173 -42
  163. package/lib/types/dsh-adapter/preset-resolution.d.ts +8 -0
  164. package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
  165. package/lib/types/dsh-adapter/preset-resolution.js +10 -0
  166. package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts +53 -0
  167. package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts.map +1 -0
  168. package/lib/types/dsh-adapter/session-mount-heartbeat.js +100 -0
  169. package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
  170. package/lib/types/dsh-adapter/sessionTree.js +3 -2
  171. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  172. package/lib/types/dsh-adapter/subagents.js +6 -5
  173. package/lib/types/dsh-adapter/workspaces.d.ts +46 -3
  174. package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
  175. package/lib/types/dsh-adapter/workspaces.js +116 -0
  176. package/lib/types/effortPrefs.d.ts +15 -3
  177. package/lib/types/effortPrefs.d.ts.map +1 -1
  178. package/lib/types/effortPrefs.js +37 -3
  179. package/lib/types/homePrefs.d.ts +15 -0
  180. package/lib/types/homePrefs.d.ts.map +1 -0
  181. package/lib/types/homePrefs.js +53 -0
  182. package/lib/types/i18n.d.ts +257 -296
  183. package/lib/types/i18n.d.ts.map +1 -1
  184. package/lib/types/i18n.js +78 -84
  185. package/lib/types/ink/colorize.d.ts +10 -0
  186. package/lib/types/ink/colorize.d.ts.map +1 -1
  187. package/lib/types/ink/colorize.js +21 -0
  188. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
  189. package/lib/types/ink/components/ScrollBox.js +12 -1
  190. package/lib/types/ink/dom.d.ts +1 -0
  191. package/lib/types/ink/dom.d.ts.map +1 -1
  192. package/lib/types/ink/hooks/use-copy-on-select.d.ts +5 -1
  193. package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -1
  194. package/lib/types/ink/hooks/use-copy-on-select.js +15 -1
  195. package/lib/types/ink/ink.d.ts.map +1 -1
  196. package/lib/types/ink/ink.js +36 -1
  197. package/lib/types/ink/output.d.ts +10 -1
  198. package/lib/types/ink/output.d.ts.map +1 -1
  199. package/lib/types/ink/output.js +109 -17
  200. package/lib/types/ink/render-border.d.ts +6 -1
  201. package/lib/types/ink/render-border.d.ts.map +1 -1
  202. package/lib/types/ink/render-border.js +25 -9
  203. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  204. package/lib/types/ink/render-node-to-output.js +48 -6
  205. package/lib/types/ink/renderer.d.ts +6 -0
  206. package/lib/types/ink/renderer.d.ts.map +1 -1
  207. package/lib/types/ink/renderer.js +2 -1
  208. package/lib/types/ink/selection.d.ts +60 -0
  209. package/lib/types/ink/selection.d.ts.map +1 -1
  210. package/lib/types/ink/selection.js +129 -0
  211. package/lib/types/ink/sixel-graphics.d.ts +19 -1
  212. package/lib/types/ink/sixel-graphics.d.ts.map +1 -1
  213. package/lib/types/ink/sixel-graphics.js +124 -12
  214. package/lib/types/ink/terminal-image.d.ts +18 -1
  215. package/lib/types/ink/terminal-image.d.ts.map +1 -1
  216. package/lib/types/ink/terminal.d.ts +17 -0
  217. package/lib/types/ink/terminal.d.ts.map +1 -1
  218. package/lib/types/ink/terminal.js +20 -1
  219. package/lib/types/screens/Chat.d.ts +17 -1
  220. package/lib/types/screens/Chat.d.ts.map +1 -1
  221. package/lib/types/screens/Chat.js +284 -72
  222. package/lib/types/screens/SessionSupervisor.d.ts +84 -0
  223. package/lib/types/screens/SessionSupervisor.d.ts.map +1 -0
  224. package/lib/types/screens/SessionSupervisor.js +350 -0
  225. package/lib/types/screens/SessionTree.js +2 -2
  226. package/lib/types/screens/Settings.d.ts.map +1 -1
  227. package/lib/types/screens/Settings.js +32 -9
  228. package/lib/types/screens/StatusLine.d.ts +13 -0
  229. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  230. package/lib/types/screens/StatusLine.js +36 -17
  231. package/lib/types/screens/StatusMetrics.d.ts +79 -22
  232. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  233. package/lib/types/screens/StatusMetrics.js +153 -88
  234. package/lib/types/screens/sessionSupervisor/model.d.ts +83 -0
  235. package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -0
  236. package/lib/types/screens/sessionSupervisor/model.js +69 -0
  237. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +120 -0
  238. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -0
  239. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +593 -0
  240. package/lib/types/sessionMounts.d.ts +272 -0
  241. package/lib/types/sessionMounts.d.ts.map +1 -0
  242. package/lib/types/sessionMounts.js +706 -0
  243. package/lib/types/sessions/format.d.ts.map +1 -1
  244. package/lib/types/sessions/format.js +4 -1
  245. package/lib/types/sessions/resumeFailure.d.ts +38 -0
  246. package/lib/types/sessions/resumeFailure.d.ts.map +1 -0
  247. package/lib/types/sessions/resumeFailure.js +58 -0
  248. package/lib/types/terminal-utils/mermaid.d.ts +44 -0
  249. package/lib/types/terminal-utils/mermaid.d.ts.map +1 -0
  250. package/lib/types/terminal-utils/mermaid.js +83 -0
  251. package/lib/types/tips.d.ts.map +1 -1
  252. package/lib/types/tips.js +175 -19
  253. package/lib/types/tuiDisplayPrefs.d.ts +7 -9
  254. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  255. package/lib/types/tuiDisplayPrefs.js +31 -28
  256. package/lib/types/update.d.ts +61 -0
  257. package/lib/types/update.d.ts.map +1 -1
  258. package/lib/types/update.js +194 -0
  259. package/lib/types/utils/imageResize.d.ts +90 -0
  260. package/lib/types/utils/imageResize.d.ts.map +1 -0
  261. package/lib/types/utils/imageResize.js +272 -0
  262. package/lib/types/utils/keymap.d.ts +5 -1
  263. package/lib/types/utils/keymap.d.ts.map +1 -1
  264. package/lib/types/utils/keymap.js +23 -0
  265. package/lib/types/workspaces.d.ts +2 -1
  266. package/lib/types/workspaces.d.ts.map +1 -1
  267. package/lib/types/workspaces.js +1 -1
  268. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +10 -10
  269. package/package.json +120 -103
  270. package/presets/liangshen/.dsh-tui-managed.json +1 -1
  271. package/presets/liangshen/agent.cordis.yml +15 -0
  272. package/lib/types/screens/AgentView.d.ts +0 -23
  273. package/lib/types/screens/AgentView.d.ts.map +0 -1
  274. package/lib/types/screens/AgentView.js +0 -546
  275. package/lib/types/screens/SessionBrowser.d.ts +0 -42
  276. package/lib/types/screens/SessionBrowser.d.ts.map +0 -1
  277. package/lib/types/screens/SessionBrowser.js +0 -911
@@ -17,7 +17,7 @@ import { ContextBarView } from '../components/ContextBarView.js';
17
17
  import { TooltipTarget } from '../components/Tooltip.js';
18
18
  import { formatProject } from '../sessions/format.js';
19
19
  import { homeDir } from '../utils/paths.js';
20
- import { USED_SEGMENTS, renderMiniContextBar, renderTpsGauge, renderTpsSparkline, speedColor, tpsStats, } from './StatusMetrics.js';
20
+ import { FREE_SEGMENT_FILL, USED_SEGMENTS, contextBarBreakdown, renderMiniContextBar, renderTpsGauge, renderTpsSparkline, speedColor, tpsStats, } from './StatusMetrics.js';
21
21
  /**
22
22
  * The footer under the prompt input: the segmented context progress bar on
23
23
  * its own first line, the
@@ -31,7 +31,8 @@ import { USED_SEGMENTS, renderMiniContextBar, renderTpsGauge, renderTpsSparkline
31
31
  * swaps the ctx readout for a mini pressure gauge and parks that field's
32
32
  * detailed breakdown on the supplemental row where the idle hint lives —
33
33
  * the footer stays one line tall, the detail is a peek, not a layout
34
- * change. Hovering a context-bar segment does the same for that segment.
34
+ * change. The context bar answers the same way: it carries no labels of its
35
+ * own, so hovering it is how its colors get their names and numbers.
35
36
  */
36
37
  /**
37
38
  * Minimal mode's footer config. It ignores every SAVED preference (built from
@@ -182,6 +183,7 @@ export function StatusLine({ channel, selectionActive = false, helpOpen = false,
182
183
  };
183
184
  }
184
185
  }
186
+ const selectionBadge = formatSelectionBadge(channel.selection);
185
187
  // Background-job chip (ctx.jobs; /jobs): live count of running/stopping
186
188
  // jobs, shown only while non-zero — a silent zero is not information.
187
189
  // Not preference-gated: it is transient situational state like the goal
@@ -228,6 +230,15 @@ export function StatusLine({ channel, selectionActive = false, helpOpen = false,
228
230
  : []),
229
231
  ];
230
232
  const rightFields = [
233
+ // Live IDE-selection badge first: it tracks the user's in-editor gesture,
234
+ // the freshest signal in the footer (T-FIX-02). Absent without an IDE —
235
+ // no placeholder, matching the official Claude Code footer.
236
+ ...(selectionBadge !== undefined
237
+ ? [{
238
+ key: 'ideSelection',
239
+ node: (_jsx(Text, { color: "ide", wrap: "truncate", children: selectionBadge })),
240
+ }]
241
+ : []),
231
242
  // Goal chip first: session-level state outranks repo/location details.
232
243
  ...(statusBar.goal && channel.goal !== undefined
233
244
  ? [{
@@ -298,7 +309,7 @@ export function StatusLine({ channel, selectionActive = false, helpOpen = false,
298
309
  channel.contextWindow !== undefined;
299
310
  // The supplemental-row readout for the hovered field: replaces the idle
300
311
  // hint (never the activity line) while the pointer dwells on a field.
301
- const detail = buildHoverDetail(hover, channel, usage, contextUsed);
312
+ const detail = buildHoverDetail(hover, channel, usage, contextUsed, columns, barColors);
302
313
  const trailer = detail !== null
303
314
  ? detail
304
315
  : hint !== ''
@@ -321,9 +332,7 @@ export function StatusLine({ channel, selectionActive = false, helpOpen = false,
321
332
  showActivity ||
322
333
  showTrajectory ||
323
334
  hint !== '';
324
- return (_jsx(Box, { paddingX: 1, width: columns, flexShrink: 0, children: _jsxs(Box, { flexDirection: "column", width: "100%", children: [barVisible ? (_jsx(ContextBarView, { segments: channel.contextSegments, usedTokens: contextUsed ?? 0, contextWindow: channel.contextWindow ?? 0, width: barWidth, colors: barColors, onHover: segment => setHover(current => segment === null
325
- ? (current !== null && current.startsWith('segment:') ? null : current)
326
- : `segment:${segment}`) })) : null, hasStatusFields ? statusBar.compact ? (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", gap: 2, children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, flexDirection: "row", overflow: "hidden", children: _jsx(FieldLine, { parts: compactFields, hoverProps: hoverProps }) }), ctxNode !== undefined ? (_jsx(Box, { flexShrink: 0, ...hoverProps('ctx'), children: _jsx(Text, { wrap: "truncate", children: ctxNode }) })) : null] })) : (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", gap: 2, children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, flexDirection: "row", overflow: "hidden", children: _jsx(FieldLine, { parts: fullLeftFields, hoverProps: hoverProps }) }), _jsx(Box, { justifyContent: "flex-end", flexShrink: 2, flexDirection: "row", overflow: "hidden", children: _jsx(FieldLine, { parts: rightFields, hoverProps: hoverProps }) })] })) : null, showSupplementalRow ? _jsxs(Box, { height: 1, overflow: "hidden", flexDirection: "row", justifyContent: "space-between", gap: 2, children: [_jsxs(Box, { flexDirection: "row", flexGrow: 1, justifyContent: showActivity && trailer !== null ? 'space-between' : 'flex-start', gap: 2, children: [showActivity && activity !== undefined ? (_jsx(ActivityLine, { activity: activity, activityFrames: channel.activityFrames, warnPct: contextPressurePct(usage, channel.contextWindow), warnDanger: (contextPressurePct(usage, channel.contextWindow) ?? 0) >= 95 })) : trailer, showActivity ? trailer : null] }), showTrajectory && wake !== undefined ? (_jsx(MiniWake, { band: wake.band, hint: wake.hint, tick: wake.tick })) : null] }) : null] }) }));
335
+ return (_jsx(Box, { paddingX: 1, width: columns, flexShrink: 0, children: _jsxs(Box, { flexDirection: "column", width: "100%", children: [barVisible ? (_jsx(ContextBarView, { segments: channel.contextSegments, usedTokens: contextUsed ?? 0, contextWindow: channel.contextWindow ?? 0, width: barWidth, colors: barColors, onHover: hovered => setHover(current => hovered ? 'bar' : current === 'bar' ? null : current) })) : null, hasStatusFields ? statusBar.compact ? (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", gap: 2, children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, flexDirection: "row", overflow: "hidden", children: _jsx(FieldLine, { parts: compactFields, hoverProps: hoverProps }) }), ctxNode !== undefined ? (_jsx(Box, { flexShrink: 0, ...hoverProps('ctx'), children: _jsx(Text, { wrap: "truncate", children: ctxNode }) })) : null] })) : (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", gap: 2, children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, flexDirection: "row", overflow: "hidden", children: _jsx(FieldLine, { parts: fullLeftFields, hoverProps: hoverProps }) }), _jsx(Box, { justifyContent: "flex-end", flexShrink: 2, flexDirection: "row", overflow: "hidden", children: _jsx(FieldLine, { parts: rightFields, hoverProps: hoverProps }) })] })) : null, showSupplementalRow ? _jsxs(Box, { height: 1, overflow: "hidden", flexDirection: "row", justifyContent: "space-between", gap: 2, children: [_jsxs(Box, { flexDirection: "row", flexGrow: 1, justifyContent: showActivity && trailer !== null ? 'space-between' : 'flex-start', gap: 2, children: [showActivity && activity !== undefined ? (_jsx(ActivityLine, { activity: activity, activityFrames: channel.activityFrames, warnPct: contextPressurePct(usage, channel.contextWindow), warnDanger: (contextPressurePct(usage, channel.contextWindow) ?? 0) >= 95 })) : trailer, showActivity ? trailer : null] }), showTrajectory && wake !== undefined ? (_jsx(MiniWake, { band: wake.band, hint: wake.hint, tick: wake.tick })) : null] }) : null] }) }));
327
336
  }
328
337
  /**
329
338
  * The supplemental-row readout for a hovered footer field. Technical label
@@ -332,24 +341,20 @@ export function StatusLine({ channel, selectionActive = false, helpOpen = false,
332
341
  * hovered (or the hover outlived its data, which the field gating makes
333
342
  * near-impossible).
334
343
  */
335
- function buildHoverDetail(hover, channel, usage, contextUsed) {
344
+ function buildHoverDetail(hover, channel, usage, contextUsed, columns, barColors) {
336
345
  if (hover === null)
337
346
  return null;
338
347
  const window = channel.contextWindow;
339
348
  const dim = (label) => _jsx(Text, { dimColor: true, children: label });
340
- if (hover.startsWith('segment:')) {
349
+ if (hover === 'bar') {
341
350
  if (window === undefined || window <= 0 || contextUsed === undefined)
342
351
  return null;
343
- const key = hover.slice('segment:'.length);
344
- const free = Math.max(0, window - contextUsed);
345
- if (key === 'free') {
346
- return (_jsxs(Text, { wrap: "truncate", children: [dim('free '), formatTokens(free), " \u00B7 ", ((free / window) * 100).toFixed(1), "% ", t('status-detail-of-window')] }));
347
- }
348
- const segment = USED_SEGMENTS.find(s => s.key === key);
349
- if (segment === undefined)
352
+ // The bar's text-free design pays off here: this line is its legend, so
353
+ // every entry leads with a chip of the very color it names.
354
+ const { entries, separator } = contextBarBreakdown(channel.contextSegments, contextUsed, window, columns, barColors?.freeFill ?? FREE_SEGMENT_FILL);
355
+ if (entries.length === 0)
350
356
  return null;
351
- const tokens = channel.contextSegments[segment.key];
352
- return (_jsxs(Text, { wrap: "truncate", children: [dim(`${segment.labels[1] ?? segment.key} `), formatTokens(tokens), " \u00B7", ' ', ((tokens / window) * 100).toFixed(1), "% ", t('status-detail-of-window')] }));
357
+ return (_jsx(Text, { wrap: "truncate", children: entries.map((entry, index) => (_jsxs(React.Fragment, { children: [index > 0 ? dim(separator) : null, _jsx(Text, { backgroundColor: entry.color, children: " " }), entry.label] }, entry.key))) }));
353
358
  }
354
359
  switch (hover) {
355
360
  case 'ctx': {
@@ -428,6 +433,20 @@ export function formatCacheHitRate(usage) {
428
433
  return undefined;
429
434
  return `${((usage.cacheRead / total) * 100).toFixed(1)}%`;
430
435
  }
436
+ /**
437
+ * The prompt footer's live IDE-selection badge (T-FIX-02), mirroring Claude
438
+ * Code's `⧉ N lines selected`: English only (like every other footer field),
439
+ * singular/plural aware, line count from the snapshot's 0-based inclusive
440
+ * range. `undefined` (no IDE yet / cleared by an isEmpty notification)
441
+ * renders no field at all — a manually launched session never shows a
442
+ * placeholder. Exported for scripts/verify-ide-channel.tsx.
443
+ */
444
+ export function formatSelectionBadge(selection) {
445
+ if (selection === undefined || selection.isEmpty === true)
446
+ return undefined;
447
+ const lines = selection.endLine - selection.startLine + 1;
448
+ return `⧉ ${lines} ${lines === 1 ? 'line' : 'lines'} selected`;
449
+ }
431
450
  function basename(path) {
432
451
  const parts = path.split(/[\\/]/);
433
452
  return parts[parts.length - 1] ?? path;
@@ -7,58 +7,115 @@
7
7
  * normalized sparkline after each completed turn; colors green ≥ 50 tps,
8
8
  * yellow ≥ 20, red below.
9
9
  */
10
+ import type { Color } from '../ink/styles.js';
10
11
  /** Context bar segments — DeepSeek blue family (dark-theme friendly: deep
11
- * navy → brand blue, neutral grey free segment; labels adapt to width).
12
- * Exported for the hoverable JSX bar (ContextBarView), which re-derives
13
- * the same column split this module's ANSI path renders. */
12
+ * navy → brand blue, neutral grey free segment).
13
+ *
14
+ * The bar draws NO text inside a used segment: the fill color is the whole
15
+ * signal (community feedback — the old `s`/`p`/`t` letters read as noise on
16
+ * a row that is already decorative). `labels` therefore belongs to the hover
17
+ * breakdown only: index 0 is the readable name, index 1 the short form the
18
+ * supplemental row falls back to on a narrow terminal. Exported for the
19
+ * hoverable JSX bar (ContextBarView), which re-derives the same column split
20
+ * this module's ANSI path renders. */
14
21
  export declare const USED_SEGMENTS: readonly [{
15
22
  readonly key: "system";
16
23
  readonly color: "#22305F";
17
- readonly labels: readonly ["system", "sys", "s"];
24
+ readonly labels: readonly ["system", "sys"];
18
25
  }, {
19
26
  readonly key: "prompt";
20
27
  readonly color: "#2B3D78";
21
- readonly labels: readonly ["prompt", "pr", "p"];
28
+ readonly labels: readonly ["prompt", "pr"];
22
29
  }, {
23
30
  readonly key: "assistant";
24
31
  readonly color: "#344A92";
25
- readonly labels: readonly ["assistant", "ast", "a"];
32
+ readonly labels: readonly ["assistant", "ast"];
26
33
  }, {
27
34
  readonly key: "thinking";
28
35
  readonly color: "#4D6BFE";
29
- readonly labels: readonly ["think", "th", "t"];
36
+ readonly labels: readonly ["thinking", "th"];
30
37
  }, {
31
38
  readonly key: "tools";
32
39
  readonly color: "#5A7CFF";
33
- readonly labels: readonly ["tools", "tl", "x"];
40
+ readonly labels: readonly ["tools", "tl"];
34
41
  }];
35
42
  /** Used tokens per context content type (system, prompt, assistant, thinking, tools). */
36
43
  export type ContextSegments = Record<(typeof USED_SEGMENTS)[number]['key'], number>;
44
+ /** Free-segment colors: light grey fill, dark grey readout. Exported so the
45
+ * JSX bar (ContextBarView) and the hover chip paint the same free color the
46
+ * ANSI path does instead of re-declaring the hex. */
47
+ export declare const FREE_SEGMENT_FILL = "#E8E8E8";
48
+ export declare const FREE_SEGMENT_TEXT = "#4A4A4A";
37
49
  /** Compact token count like pi's: `988`, `3.4k`, `12k`, `1.0M`.
38
50
  * @param count - The raw token count; negative values clamp to zero.
39
51
  * @returns The compact count string.
40
52
  */
41
53
  export declare function formatTokens(count: number): string;
42
- /** Center `text` in `width` cells (plain spaces, no ANSI). Exported for
43
- * ContextBarView's JSX used-segment labels. */
44
- export declare function centeredText(text: string, width: number): string;
45
- /** Pick the longest label that fits `width` cells. Exported for
46
- * ContextBarView's JSX segments (same fallback ladder as the ANSI path). */
47
- export declare function chooseLabel(labels: readonly string[], width: number): string;
54
+ /** Which pressure step a context occupancy falls in: undefined while the
55
+ * context is comfortable, then the shared `warning` / `error` theme colors.
56
+ * @param pct - Context occupancy percent (0–100+).
57
+ * @returns The theme color key, or undefined below the amber threshold.
58
+ */
59
+ export declare function contextPressureStep(pct: number): 'warning' | 'error' | undefined;
60
+ /**
61
+ * The bar's right-aligned usage readout, longest form first: token counts and
62
+ * percent (`13k/64k 19.5%`), then the percent alone once the free segment is
63
+ * too narrow to carry the counts.
64
+ * @param usedTokens - Total context tokens in use.
65
+ * @param contextWindow - The context window size in tokens.
66
+ * @returns The readout ladder, widest option first.
67
+ */
68
+ export declare function contextBarReadout(usedTokens: number, contextWindow: number): readonly string[];
48
69
  /**
49
- * Compose the free segment's plain text: the `free` label centered, the
50
- * usage readout right-aligned into whatever width remains. Shared by the
51
- * ANSI string path (renderContextBar) and the hoverable JSX bar
52
- * (ContextBarView) so both render pixel-identical content.
70
+ * Blank-pad to `width` cells and right-align the first readout option that
71
+ * fits. Shared by the ANSI string path (renderContextBar) and the hoverable
72
+ * JSX bar (ContextBarView) so both render identical readouts.
73
+ */
74
+ export declare function rightAlignBarText(options: readonly string[], width: number): string;
75
+ /** One entry of the context bar's hover breakdown: the fill color, the name
76
+ * to put next to a swatch of it, and the token count already folded into
77
+ * `label` (`system 1.2k`). */
78
+ export type ContextBarBreakdownEntry = {
79
+ /** `system` … `tools`, or `free`. */
80
+ key: string;
81
+ /** The chip label, e.g. `thinking 5.0k`. */
82
+ label: string;
83
+ /** The segment's fill color — the hover chip paints with it, which is what
84
+ * ties each number back to a slice of the bar. */
85
+ color: Color;
86
+ };
87
+ /** Separator between breakdown entries, chosen by the width ladder below. */
88
+ export type ContextBarBreakdown = {
89
+ readonly entries: readonly ContextBarBreakdownEntry[];
90
+ readonly separator: string;
91
+ };
92
+ /**
93
+ * The context bar's hover breakdown — the legend the bar no longer carries
94
+ * itself. One entry per segment the bar actually paints (a zero-token content
95
+ * type gets no columns, so it gets no entry either), in bar order, free last.
96
+ *
97
+ * `columns` picks the label form: readable names with a ` · ` separator while
98
+ * the line fits, then the short forms, then a bare space separator (the color
99
+ * chip already separates the entries). The caller renders each entry as
100
+ * `chip + space + label`, which is what the fit test measures.
101
+ *
102
+ * @param segments - Used tokens per content type.
103
+ * @param usedTokens - Total used tokens; the remainder is the free entry.
104
+ * @param contextWindow - The context window size in tokens.
105
+ * @param columns - Terminal width; the footer's own padding is subtracted here.
106
+ * @param freeFill - The free segment's fill color (callers pass a theme override).
107
+ * @returns The breakdown entries and the separator to join them with.
53
108
  */
54
- export declare function composeFreeSegmentText(options: readonly string[], width: number): string;
109
+ export declare function contextBarBreakdown(segments: ContextSegments, usedTokens: number, contextWindow: number, columns: number, freeFill?: Color): ContextBarBreakdown;
55
110
  /** Give every visible used segment at least one column before sharing the
56
111
  * rest. Exported for ContextBarView (hoverable JSX twin of the bar). */
57
112
  export declare function allocateBarColumns(values: readonly number[], width: number): number[];
58
113
  /**
59
- * The segmented context bar: used segments by content type, the remainder as
60
- * a light free segment whose right edge carries the usage readout
61
- * (`ctx 12.3k/1.0M 1.2% 988.9k`, shrinking as width allows).
114
+ * The segmented context bar: used segments by content type, then the
115
+ * remainder as a light free segment whose right edge carries the usage
116
+ * readout (`13k/64k 19.5%`). No other text — the bar is read by color, and
117
+ * the pointer supplies the names and numbers (contextBarBreakdown). The
118
+ * readout tints amber / red as the context fills (contextPressureStep).
62
119
  * @param segments - Used tokens per content type.
63
120
  * @param usedTokens - Total used tokens, driving the usage readout.
64
121
  * @param contextWindow - The context window size in tokens.
@@ -1 +1 @@
1
- {"version":3,"file":"StatusMetrics.d.ts","sourceRoot":"","sources":["../../../src/screens/StatusMetrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;6DAG6D;AAC7D,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;EAMhB,CAAA;AAEV,yFAAyF;AACzF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAA;AAsBnF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD;AAID;gDACgD;AAChD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED;6EAC6E;AAC7E,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK5E;AA4BD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,KAAK,EAAE,MAAM,GACZ,MAAM,CAmBR;AA+BD;yEACyE;AACzE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBrF;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,eAAe,EACzB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC9C,MAAM,CAkBR;AASD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,KAAK,SAAK,GACT,MAAM,CAaR;AAYD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5D;AAYD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAYhE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,MAAM,CAqB9E;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG;IACxF,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ,CAUA"}
1
+ {"version":3,"file":"StatusMetrics.d.ts","sourceRoot":"","sources":["../../../src/screens/StatusMetrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAG7C;;;;;;;;;uCASuC;AACvC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;EAMhB,CAAA;AAEV,yFAAyF;AACzF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAA;AAEnF;;sDAEsD;AACtD,eAAO,MAAM,iBAAiB,YAAY,CAAA;AAC1C,eAAO,MAAM,iBAAiB,YAAY,CAAA;AAiB1C;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD;AAQD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAIhF;AAID;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,GACpB,SAAS,MAAM,EAAE,CAGnB;AAID;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,KAAK,EAAE,MAAM,GACZ,MAAM,CAYR;AAoBD;;+BAE+B;AAC/B,MAAM,MAAM,wBAAwB,GAAG;IACrC,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb;uDACmD;IACnD,KAAK,EAAE,KAAK,CAAA;CACb,CAAA;AAED,6EAA6E;AAC7E,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,OAAO,EAAE,SAAS,wBAAwB,EAAE,CAAA;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,KAAyB,GAClC,mBAAmB,CA4CrB;AA6BD;yEACyE;AACzE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBrF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,eAAe,EACzB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC9C,MAAM,CAsBR;AAID;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,KAAK,SAAK,GACT,MAAM,CAaR;AAYD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5D;AAYD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAYhE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,MAAM,CAqB9E;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG;IACxF,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ,CAUA"}
@@ -1,27 +1,26 @@
1
- /**
2
- * Status-line metric renderers, ported from two pi extensions:
3
- * - `pi-nano-context`: segmented context progress bar (morandi pastel
4
- * segments by content type, free space right-aligned with the usage
5
- * readout, largest-remainder column allocation).
6
- * - `pi-tps-meter`: live 1/8-cell gauge while streaming and a min-max
7
- * normalized sparkline after each completed turn; colors green ≥ 50 tps,
8
- * yellow ≥ 20, red below.
9
- */
1
+ import { stringWidth } from '../ink/stringWidth.js';
10
2
  /** Context bar segments — DeepSeek blue family (dark-theme friendly: deep
11
- * navy → brand blue, neutral grey free segment; labels adapt to width).
12
- * Exported for the hoverable JSX bar (ContextBarView), which re-derives
13
- * the same column split this module's ANSI path renders. */
3
+ * navy → brand blue, neutral grey free segment).
4
+ *
5
+ * The bar draws NO text inside a used segment: the fill color is the whole
6
+ * signal (community feedback — the old `s`/`p`/`t` letters read as noise on
7
+ * a row that is already decorative). `labels` therefore belongs to the hover
8
+ * breakdown only: index 0 is the readable name, index 1 the short form the
9
+ * supplemental row falls back to on a narrow terminal. Exported for the
10
+ * hoverable JSX bar (ContextBarView), which re-derives the same column split
11
+ * this module's ANSI path renders. */
14
12
  export const USED_SEGMENTS = [
15
- { key: 'system', color: '#22305F', labels: ['system', 'sys', 's'] }, // deep navy
16
- { key: 'prompt', color: '#2B3D78', labels: ['prompt', 'pr', 'p'] }, // navy
17
- { key: 'assistant', color: '#344A92', labels: ['assistant', 'ast', 'a'] }, // indigo
18
- { key: 'thinking', color: '#4D6BFE', labels: ['think', 'th', 't'] }, // DeepSeek brand blue
19
- { key: 'tools', color: '#5A7CFF', labels: ['tools', 'tl', 'x'] }, // lighter blue
13
+ { key: 'system', color: '#22305F', labels: ['system', 'sys'] }, // deep navy
14
+ { key: 'prompt', color: '#2B3D78', labels: ['prompt', 'pr'] }, // navy
15
+ { key: 'assistant', color: '#344A92', labels: ['assistant', 'ast'] }, // indigo
16
+ { key: 'thinking', color: '#4D6BFE', labels: ['thinking', 'th'] }, // DeepSeek brand blue
17
+ { key: 'tools', color: '#5A7CFF', labels: ['tools', 'tl'] }, // lighter blue
20
18
  ];
21
- const USED_SEGMENT_TEXT = '#FFFFFF';
22
- const FREE_SEGMENT_FILL = '#E8E8E8';
23
- const FREE_SEGMENT_TEXT = '#4A4A4A';
24
- const FREE_SEGMENT_LABELS = ['free', 'fr', 'f'];
19
+ /** Free-segment colors: light grey fill, dark grey readout. Exported so the
20
+ * JSX bar (ContextBarView) and the hover chip paint the same free color the
21
+ * ANSI path does instead of re-declaring the hex. */
22
+ export const FREE_SEGMENT_FILL = '#E8E8E8';
23
+ export const FREE_SEGMENT_TEXT = '#4A4A4A';
25
24
  const ANSI_RE = /\x1b\[[0-9;]*m/g;
26
25
  const stripAnsi = (text) => text.replace(ANSI_RE, '');
27
26
  const plainWidth = (text) => Array.from(stripAnsi(text)).length;
@@ -50,74 +49,138 @@ export function formatTokens(count) {
50
49
  return `${(value / 1000000).toFixed(1)}M`;
51
50
  return `${Math.round(value / 1000000)}M`;
52
51
  }
53
- // --- Context bar (pi-nano-context) ---
54
- /** Center `text` in `width` cells (plain spaces, no ANSI). Exported for
55
- * ContextBarView's JSX used-segment labels. */
56
- export function centeredText(text, width) {
57
- const textWidth = plainWidth(text);
58
- if (textWidth > width)
59
- return ' '.repeat(width);
60
- const left = Math.floor((width - textWidth) / 2);
61
- const right = width - textWidth - left;
62
- return `${' '.repeat(left)}${text}${' '.repeat(right)}`;
63
- }
64
- /** Pick the longest label that fits `width` cells. Exported for
65
- * ContextBarView's JSX segments (same fallback ladder as the ANSI path). */
66
- export function chooseLabel(labels, width) {
67
- for (const label of labels) {
68
- if (plainWidth(label) <= width)
69
- return label;
70
- }
71
- return '';
72
- }
73
- function renderUsedSegment(labels, color, width) {
74
- if (width <= 0)
75
- return '';
76
- const label = chooseLabel(labels, width);
77
- const text = label.length > 0
78
- ? foreground(USED_SEGMENT_TEXT, centeredText(label, width))
79
- : ' '.repeat(width);
80
- return background(color, text);
52
+ /** Context-pressure thresholds, shared by the bar readout's tint, the ctx
53
+ * field's hover gauge, and contextPressurePct's amber/red footer convention
54
+ * (amber ≥ 80%, red ≥ 95%). */
55
+ const PRESSURE_WARN = 80;
56
+ const PRESSURE_DANGER = 95;
57
+ /** Which pressure step a context occupancy falls in: undefined while the
58
+ * context is comfortable, then the shared `warning` / `error` theme colors.
59
+ * @param pct - Context occupancy percent (0–100+).
60
+ * @returns The theme color key, or undefined below the amber threshold.
61
+ */
62
+ export function contextPressureStep(pct) {
63
+ if (pct >= PRESSURE_DANGER)
64
+ return 'error';
65
+ if (pct >= PRESSURE_WARN)
66
+ return 'warning';
67
+ return undefined;
81
68
  }
82
- function chooseRightAlignedText(options, width, blockedUntil) {
83
- for (const option of options) {
84
- const start = width - plainWidth(option);
85
- if (start > blockedUntil)
86
- return option;
87
- }
88
- return '';
69
+ // --- Context bar (pi-nano-context) ---
70
+ /**
71
+ * The bar's right-aligned usage readout, longest form first: token counts and
72
+ * percent (`13k/64k 19.5%`), then the percent alone once the free segment is
73
+ * too narrow to carry the counts.
74
+ * @param usedTokens - Total context tokens in use.
75
+ * @param contextWindow - The context window size in tokens.
76
+ * @returns The readout ladder, widest option first.
77
+ */
78
+ export function contextBarReadout(usedTokens, contextWindow) {
79
+ const percent = `${((usedTokens / contextWindow) * 100).toFixed(1)}%`;
80
+ return [`${formatTokens(usedTokens)}/${formatTokens(contextWindow)} ${percent}`, percent];
89
81
  }
82
+ // --- Context bar (pi-nano-context) ---
90
83
  /**
91
- * Compose the free segment's plain text: the `free` label centered, the
92
- * usage readout right-aligned into whatever width remains. Shared by the
93
- * ANSI string path (renderContextBar) and the hoverable JSX bar
94
- * (ContextBarView) so both render pixel-identical content.
84
+ * Blank-pad to `width` cells and right-align the first readout option that
85
+ * fits. Shared by the ANSI string path (renderContextBar) and the hoverable
86
+ * JSX bar (ContextBarView) so both render identical readouts.
95
87
  */
96
- export function composeFreeSegmentText(options, width) {
88
+ export function rightAlignBarText(options, width) {
97
89
  if (width <= 0)
98
90
  return '';
99
91
  const content = Array.from({ length: width }, () => ' ');
100
- const label = chooseLabel(FREE_SEGMENT_LABELS, width);
101
- const labelStart = Math.max(0, Math.floor((width - plainWidth(label)) / 2));
102
- const labelEnd = label.length > 0 ? labelStart + plainWidth(label) : -1;
103
- const rightText = chooseRightAlignedText(options, width, labelEnd);
104
- if (rightText) {
105
- const start = width - plainWidth(rightText);
106
- for (const [offset, char] of Array.from(rightText).entries()) {
92
+ for (const option of options) {
93
+ const start = width - plainWidth(option);
94
+ if (start < 0)
95
+ continue;
96
+ for (const [offset, char] of Array.from(option).entries()) {
107
97
  content[start + offset] = char;
108
98
  }
109
- }
110
- if (label) {
111
- for (const [offset, char] of Array.from(label).entries()) {
112
- content[labelStart + offset] = char;
113
- }
99
+ break;
114
100
  }
115
101
  return content.join('');
116
102
  }
117
- function renderFreeSegment(options, width, fill, text) {
103
+ /** A used segment's fill: background color only, no text. Letters inside the
104
+ * bar read as noise (community feedback) and never fit the narrow segments
105
+ * anyway — the pointer names a color now (contextBarBreakdown). */
106
+ function renderUsedSegment(color, width) {
118
107
  if (width <= 0)
119
108
  return '';
120
- return background(fill, foreground(text, composeFreeSegmentText(options, width)));
109
+ return background(color, ' '.repeat(width));
110
+ }
111
+ function renderFreeSegment(options, width, fill, style) {
112
+ if (width <= 0)
113
+ return '';
114
+ return background(fill, style(rightAlignBarText(options, width)));
115
+ }
116
+ /**
117
+ * The context bar's hover breakdown — the legend the bar no longer carries
118
+ * itself. One entry per segment the bar actually paints (a zero-token content
119
+ * type gets no columns, so it gets no entry either), in bar order, free last.
120
+ *
121
+ * `columns` picks the label form: readable names with a ` · ` separator while
122
+ * the line fits, then the short forms, then a bare space separator (the color
123
+ * chip already separates the entries). The caller renders each entry as
124
+ * `chip + space + label`, which is what the fit test measures.
125
+ *
126
+ * @param segments - Used tokens per content type.
127
+ * @param usedTokens - Total used tokens; the remainder is the free entry.
128
+ * @param contextWindow - The context window size in tokens.
129
+ * @param columns - Terminal width; the footer's own padding is subtracted here.
130
+ * @param freeFill - The free segment's fill color (callers pass a theme override).
131
+ * @returns The breakdown entries and the separator to join them with.
132
+ */
133
+ export function contextBarBreakdown(segments, usedTokens, contextWindow, columns, freeFill = FREE_SEGMENT_FILL) {
134
+ if (contextWindow <= 0)
135
+ return { entries: [], separator: ' · ' };
136
+ const freeTokens = Math.max(0, contextWindow - usedTokens);
137
+ const raw = [];
138
+ for (const segment of USED_SEGMENTS) {
139
+ const tokens = segments[segment.key];
140
+ if (tokens > 0) {
141
+ raw.push({ key: segment.key, tokens, color: segment.color, labels: segment.labels });
142
+ }
143
+ }
144
+ if (freeTokens > 0)
145
+ raw.push({ key: 'free', tokens: freeTokens, color: freeFill, labels: ['free'] });
146
+ if (raw.length === 0)
147
+ return { entries: [], separator: ' · ' };
148
+ // Footer padding (1 cell each side) plus slack for the trajectory wake that
149
+ // shares this row: a breakdown one cell too long would truncate its tail.
150
+ const budget = columns - 6;
151
+ // Widest form first; the last rung wins when nothing fits (the row then
152
+ // truncates like every other hover detail).
153
+ const rungs = [
154
+ { separator: ' · ', labelIndex: 0 },
155
+ { separator: ' ', labelIndex: 0 },
156
+ { separator: ' ', labelIndex: 1 },
157
+ ];
158
+ let rung = { separator: ' ', labelIndex: 1 };
159
+ for (const candidate of rungs) {
160
+ rung = candidate;
161
+ const labels = raw.map(entry => breakdownLabel(entry, candidate.labelIndex));
162
+ // One chip cell per entry prefixes each label on the supplemental row.
163
+ // Measured in terminal cells with the renderer's own helper, not UTF-16
164
+ // units: the ` · ` separator is East-Asian ambiguous, and the label set is
165
+ // free to gain non-ASCII names later.
166
+ const rendered = labels.reduce((sum, label) => sum + stringWidth(label), 0)
167
+ + labels.length
168
+ + stringWidth(candidate.separator) * (labels.length - 1);
169
+ if (rendered <= budget)
170
+ break;
171
+ }
172
+ return {
173
+ entries: raw.map(entry => ({
174
+ key: entry.key,
175
+ color: entry.color,
176
+ label: breakdownLabel(entry, rung.labelIndex),
177
+ })),
178
+ separator: rung.separator,
179
+ };
180
+ }
181
+ function breakdownLabel(entry, labelIndex) {
182
+ const name = entry.labels[labelIndex] ?? entry.labels[0] ?? entry.key;
183
+ return `${name} ${formatTokens(entry.tokens)}`;
121
184
  }
122
185
  /** Largest-remainder column allocation (pi-nano-context). */
123
186
  function allocateProportionally(values, columns) {
@@ -157,9 +220,11 @@ export function allocateBarColumns(values, width) {
157
220
  return minimumColumns.map((minimum, index) => minimum + (remainingColumns[index] ?? 0));
158
221
  }
159
222
  /**
160
- * The segmented context bar: used segments by content type, the remainder as
161
- * a light free segment whose right edge carries the usage readout
162
- * (`ctx 12.3k/1.0M 1.2% 988.9k`, shrinking as width allows).
223
+ * The segmented context bar: used segments by content type, then the
224
+ * remainder as a light free segment whose right edge carries the usage
225
+ * readout (`13k/64k 19.5%`). No other text — the bar is read by color, and
226
+ * the pointer supplies the names and numbers (contextBarBreakdown). The
227
+ * readout tints amber / red as the context fills (contextPressureStep).
163
228
  * @param segments - Used tokens per content type.
164
229
  * @param usedTokens - Total used tokens, driving the usage readout.
165
230
  * @param contextWindow - The context window size in tokens.
@@ -172,25 +237,25 @@ export function renderContextBar(segments, usedTokens, contextWindow, width, col
172
237
  const freeTokens = Math.max(0, contextWindow - usedTokens);
173
238
  const values = [...USED_SEGMENTS.map(segment => segments[segment.key]), freeTokens];
174
239
  const columns = allocateBarColumns(values, width);
175
- const used = USED_SEGMENTS.map((segment, index) => renderUsedSegment(segment.labels, segment.color, columns[index] ?? 0)).join('');
240
+ const used = USED_SEGMENTS.map((segment, index) => renderUsedSegment(segment.color, columns[index] ?? 0)).join('');
176
241
  const freeWidth = columns[USED_SEGMENTS.length] ?? 0;
177
- const percent = `${((usedTokens / contextWindow) * 100).toFixed(1)}%`;
178
- const total = `${formatTokens(usedTokens)}/${formatTokens(contextWindow)}`;
179
- const free = formatTokens(contextWindow - usedTokens);
180
- return `${used}${renderFreeSegment([`ctx ${total} ${percent} ${free}`, `${total} ${percent} ${free}`, `${total} ${percent}`, percent], freeWidth, colors?.freeFill ?? FREE_SEGMENT_FILL, colors?.freeText ?? FREE_SEGMENT_TEXT)}`;
242
+ const pct = (usedTokens / contextWindow) * 100;
243
+ const step = contextPressureStep(pct);
244
+ // Same two-path convention as the free-segment colors: the JSX bar tints
245
+ // through the theme key, this string path through the raw ANSI twin.
246
+ const style = step === undefined
247
+ ? (text) => foreground(colors?.freeText ?? FREE_SEGMENT_TEXT, text)
248
+ : (text) => pressureColor(pct, text);
249
+ return `${used}${renderFreeSegment(contextBarReadout(usedTokens, contextWindow), freeWidth, colors?.freeFill ?? FREE_SEGMENT_FILL, style)}`;
181
250
  }
182
251
  // --- Mini context bar (footer ctx-field hover) ---
183
- /** Context-pressure thresholds, shared with contextPressurePct's amber/red
184
- * footer convention (amber ≥ 80%, red ≥ 95%). */
185
- const PRESSURE_WARN = 80;
186
- const PRESSURE_DANGER = 95;
187
252
  /** Pressure-colored text: green below 80%, amber ≥ 80, red ≥ 95.
188
253
  * @param pct - Context occupancy percent (0–100+).
189
254
  * @param text - Text to color.
190
255
  * @returns The ANSI 24-bit color-wrapped text.
191
256
  */
192
257
  export function pressureColor(pct, text) {
193
- const key = pct >= PRESSURE_DANGER ? 'error' : pct >= PRESSURE_WARN ? 'warning' : 'success';
258
+ const key = contextPressureStep(pct) ?? 'success';
194
259
  return `\x1b[38;2;${colorHex(key)}m${text}\x1b[39m`;
195
260
  }
196
261
  /** Compact 1/8-cell context gauge for the footer's ctx field on hover: