@deepseek-harness-tui/dsh-tui 0.7.2 → 0.8.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 (119) hide show
  1. package/README.md +3 -3
  2. package/bin/dsh-tui.js +24 -3
  3. package/cordis.patch.yml +24 -11
  4. package/cordis.yml +9 -1
  5. package/lib/types/commands.d.ts.map +1 -1
  6. package/lib/types/commands.js +2 -1
  7. package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
  8. package/lib/types/components/GoalTodoPanel.js +6 -1
  9. package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
  10. package/lib/types/components/HistorySearchDialog.js +5 -1
  11. package/lib/types/components/MessageList.d.ts +3 -1
  12. package/lib/types/components/MessageList.d.ts.map +1 -1
  13. package/lib/types/components/MessageList.js +9 -5
  14. package/lib/types/components/PluginSceneBoundary.d.ts +31 -0
  15. package/lib/types/components/PluginSceneBoundary.d.ts.map +1 -0
  16. package/lib/types/components/PluginSceneBoundary.js +29 -0
  17. package/lib/types/components/PromptInput.d.ts.map +1 -1
  18. package/lib/types/components/PromptInput.js +52 -74
  19. package/lib/types/components/SearchBox.d.ts +4 -0
  20. package/lib/types/components/SearchBox.d.ts.map +1 -1
  21. package/lib/types/components/SearchBox.js +101 -2
  22. package/lib/types/components/SkillsPicker.d.ts +18 -0
  23. package/lib/types/components/SkillsPicker.d.ts.map +1 -0
  24. package/lib/types/components/SkillsPicker.js +50 -0
  25. package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts +4 -1
  26. package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts.map +1 -1
  27. package/lib/types/components/Spinner/SpinnerAnimationRow.js +10 -5
  28. package/lib/types/components/SplitDiffView.d.ts +28 -0
  29. package/lib/types/components/SplitDiffView.d.ts.map +1 -0
  30. package/lib/types/components/SplitDiffView.js +417 -0
  31. package/lib/types/components/WorkingSpinner.d.ts +3 -1
  32. package/lib/types/components/WorkingSpinner.d.ts.map +1 -1
  33. package/lib/types/components/WorkingSpinner.js +2 -2
  34. package/lib/types/components/design-system/ThemedText.d.ts +7 -1
  35. package/lib/types/components/design-system/ThemedText.d.ts.map +1 -1
  36. package/lib/types/components/design-system/ThemedText.js +2 -2
  37. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +3 -1
  38. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  39. package/lib/types/components/messages/AssistantToolUseMessage.js +17 -7
  40. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  41. package/lib/types/components/questions/AskUserQuestionPanel.js +52 -8
  42. package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
  43. package/lib/types/components/questions/PlanReviewPanel.js +12 -3
  44. package/lib/types/dsh-adapter/channel.d.ts +78 -0
  45. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  46. package/lib/types/dsh-adapter/channel.js +165 -14
  47. package/lib/types/dsh-adapter/contract.d.ts +1 -1
  48. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  49. package/lib/types/dsh-adapter/contract.js +1 -0
  50. package/lib/types/dsh-adapter/index.d.ts +4 -0
  51. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  52. package/lib/types/dsh-adapter/index.js +1 -0
  53. package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -1
  54. package/lib/types/dsh-adapter/packaged-presets.js +5 -2
  55. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  56. package/lib/types/dsh-adapter/plugin.js +66 -3
  57. package/lib/types/dsh-adapter/promptDebug.d.ts +10 -0
  58. package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -0
  59. package/lib/types/dsh-adapter/promptDebug.js +148 -0
  60. package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
  61. package/lib/types/dsh-adapter/providerWizard.js +3 -1
  62. package/lib/types/dsh-adapter/scenes.d.ts +75 -0
  63. package/lib/types/dsh-adapter/scenes.d.ts.map +1 -0
  64. package/lib/types/dsh-adapter/scenes.js +78 -0
  65. package/lib/types/dsh-adapter/settings-sections.d.ts +116 -0
  66. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -0
  67. package/lib/types/dsh-adapter/settings-sections.js +63 -0
  68. package/lib/types/dsh-adapter/settingsEditor.d.ts +128 -0
  69. package/lib/types/dsh-adapter/settingsEditor.d.ts.map +1 -0
  70. package/lib/types/dsh-adapter/settingsEditor.js +244 -0
  71. package/lib/types/i18n.d.ts +169 -18
  72. package/lib/types/i18n.d.ts.map +1 -1
  73. package/lib/types/i18n.js +45 -6
  74. package/lib/types/ink/components/Text.d.ts +9 -1
  75. package/lib/types/ink/components/Text.d.ts.map +1 -1
  76. package/lib/types/ink/components/Text.js +7 -6
  77. package/lib/types/ink/ink.d.ts.map +1 -1
  78. package/lib/types/ink/ink.js +4 -2
  79. package/lib/types/ink/log-update.d.ts.map +1 -1
  80. package/lib/types/ink/log-update.js +76 -9
  81. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  82. package/lib/types/ink/parse-keypress.js +4 -1
  83. package/lib/types/ink/terminal.d.ts +40 -4
  84. package/lib/types/ink/terminal.d.ts.map +1 -1
  85. package/lib/types/ink/terminal.js +57 -5
  86. package/lib/types/ink/termio/tokenize.d.ts +6 -0
  87. package/lib/types/ink/termio/tokenize.d.ts.map +1 -1
  88. package/lib/types/ink/termio/tokenize.js +18 -4
  89. package/lib/types/jsx-runtime.d.ts +2 -0
  90. package/lib/types/jsx-runtime.d.ts.map +1 -0
  91. package/lib/types/jsx-runtime.js +13 -0
  92. package/lib/types/modelRoute.d.ts +10 -2
  93. package/lib/types/modelRoute.d.ts.map +1 -1
  94. package/lib/types/modelRoute.js +33 -4
  95. package/lib/types/scenes.d.ts +3 -0
  96. package/lib/types/scenes.d.ts.map +1 -0
  97. package/lib/types/scenes.js +4 -0
  98. package/lib/types/screens/Chat.d.ts.map +1 -1
  99. package/lib/types/screens/Chat.js +182 -26
  100. package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
  101. package/lib/types/screens/SessionBrowser.js +30 -15
  102. package/lib/types/screens/Settings.d.ts +20 -0
  103. package/lib/types/screens/Settings.d.ts.map +1 -0
  104. package/lib/types/screens/Settings.js +333 -0
  105. package/lib/types/screens/StatusLine.js +1 -1
  106. package/lib/types/sessions/view.d.ts.map +1 -1
  107. package/lib/types/sessions/view.js +13 -1
  108. package/lib/types/settings-sections.d.ts +3 -0
  109. package/lib/types/settings-sections.d.ts.map +1 -0
  110. package/lib/types/settings-sections.js +4 -0
  111. package/lib/types/theme.d.ts +12 -0
  112. package/lib/types/theme.d.ts.map +1 -1
  113. package/lib/types/theme.js +36 -0
  114. package/lib/types/update.d.ts +21 -7
  115. package/lib/types/update.d.ts.map +1 -1
  116. package/lib/types/update.js +66 -16
  117. package/lib/types/utils/paths.d.ts.map +1 -1
  118. package/lib/types/utils/paths.js +4 -1
  119. package/package.json +49 -9
@@ -26,7 +26,7 @@ const THINKING_GLOW_PERIOD_S = 2;
26
26
  * animation clock (frame, glimmer, token counter animation, elapsed time,
27
27
  * stalled intensity, thinking shimmer).
28
28
  */
29
- export function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, responseLengthRef, message, messageColor, shimmerColor, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, spinnerSuffix, verbose, columns, thinkingStatus, }) {
29
+ export function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, responseLengthRef, uploadTokensRef, message, messageColor, shimmerColor, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, spinnerSuffix, verbose, columns, thinkingStatus, }) {
30
30
  const [viewportRef, time] = useAnimationFrame(reducedMotion ? null : 50);
31
31
  // === Elapsed time (wall-clock, derived from refs each frame) ===
32
32
  const now = Date.now();
@@ -81,8 +81,13 @@ export function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, respo
81
81
  const timerText = formatDuration(elapsedTimeMs);
82
82
  const timerWidth = stringWidth(timerText);
83
83
  const tokenCount = formatNumber(leaderTokens);
84
- const tokensText = `${figures.arrowDown} ${tokenCount} tokens`;
85
- const tokensWidth = stringWidth(tokensText);
84
+ const uploadTokens = uploadTokensRef.current;
85
+ // Real upload tokens (last request's input + cache) ride beside the
86
+ // animated download estimate; both labeled once to keep the row short.
87
+ const tokensLabel = uploadTokens > 0
88
+ ? `↑ ${formatNumber(uploadTokens)} · ↓ ${tokenCount} tokens`
89
+ : `↓ ${tokenCount} tokens`;
90
+ const tokensWidth = stringWidth(tokensLabel);
86
91
  // === Thinking text (may shrink to fit) ===
87
92
  let thinkingText = thinkingStatus === 'thinking'
88
93
  ? 'thinking'
@@ -108,7 +113,7 @@ export function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, respo
108
113
  const showTimer = wantsTimerAndTokens && availableSpace > usedAfterThinking + timerWidth;
109
114
  const usedAfterTimer = usedAfterThinking + (showTimer ? timerWidth + sep : 0);
110
115
  const showTokens = wantsTimerAndTokens &&
111
- leaderTokens > 0 &&
116
+ (leaderTokens > 0 || uploadTokens > 0) &&
112
117
  availableSpace > usedAfterTimer + tokensWidth;
113
118
  const thinkingOnly = showThinking &&
114
119
  thinkingStatus === 'thinking' &&
@@ -137,7 +142,7 @@ export function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, respo
137
142
  : []),
138
143
  ...(showTokens
139
144
  ? [
140
- _jsxs(Box, { flexDirection: "row", children: [_jsx(SpinnerModeGlyph, { mode: mode }), _jsxs(Text, { dimColor: true, children: [tokenCount, " tokens"] })] }, "tokens"),
145
+ _jsxs(Box, { flexDirection: "row", children: [_jsx(SpinnerModeGlyph, { mode: mode }), _jsx(Text, { dimColor: true, children: tokensLabel })] }, "tokens"),
141
146
  ]
142
147
  : []),
143
148
  ...(showThinking && thinkingText
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import type { ToolFileDiff } from '../dsh-adapter/channel.js';
3
+ import { type CliHighlight } from '../cc/cliHighlight.js';
4
+ /** chalk style for one raw theme value. Accepts every form the theme
5
+ * loader documents: #rgb, #rrggbb, #rrggbbaa (alpha stripped), rgb() with
6
+ * or without spaces, ansi256(n), ansi:name (issue #250, P2-5). */
7
+ export declare function chalkFromToken(token: string): (text: string) => string;
8
+ /** Parse a cli-highlight ANSI string into colored runs, preserving open
9
+ * spans across embedded newlines (multi-line strings/comments). Handles
10
+ * truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
11
+ * and single-code 16-color SGR. */
12
+ export declare function parseAnsiRuns(text: string): {
13
+ text: string;
14
+ color?: string;
15
+ }[];
16
+ export declare function highlightLines(text: string, language: string | undefined, hl: CliHighlight | null, chTheme: Record<string, (text: string) => string> | undefined, themeSig: string): readonly {
17
+ text: string;
18
+ color?: string;
19
+ }[][] | undefined;
20
+ export declare function SplitDiffView({ diffs, width, maxRows, verbose, }: {
21
+ readonly diffs: readonly ToolFileDiff[];
22
+ /** Content width available to the whole two-pane block (divider included). */
23
+ readonly width: number;
24
+ /** Row budget when not verbose; overflow folds into one hint row. */
25
+ readonly maxRows: number;
26
+ readonly verbose: boolean;
27
+ }): React.ReactNode;
28
+ //# sourceMappingURL=SplitDiffView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SplitDiffView.d.ts","sourceRoot":"","sources":["../../../src/components/SplitDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAwFjF;;mEAEmE;AACnE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAmBtE;AAUD;;;oCAGoC;AACpC,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAuB9E;AAuBD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,EAAE,EAAE,YAAY,GAAG,IAAI,EACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EAC7D,QAAQ,EAAE,MAAM,GACf,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,GAAG,SAAS,CAe3D;AA6ND,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,GACR,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B,GAAG,KAAK,CAAC,SAAS,CAiGlB"}
@@ -0,0 +1,417 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text } from '../ui.js';
4
+ import * as JsDiff from 'diff';
5
+ import chalk from 'chalk';
6
+ import { extname } from 'node:path';
7
+ import { getCliHighlightPromise } from '../cc/cliHighlight.js';
8
+ import { getTheme } from '../theme.js';
9
+ import { useTheme } from './design-system/ThemeProvider.js';
10
+ /** Replace tabs so width math and alignment hold (pi convention). */
11
+ const expandTabs = (text) => text.replaceAll('\t', ' ');
12
+ // --- syntax highlighting ----------------------------------------------------
13
+ /** highlight.js token classes → theme syntax tokens. `default` catches
14
+ * every unmapped class so cli-highlight's own yellow never leaks through
15
+ * (issue #250, P2-6). */
16
+ const SYNTAX_CLASS_TO_TOKEN = {
17
+ keyword: 'syntaxKeyword',
18
+ built_in: 'syntaxKeyword',
19
+ literal: 'syntaxKeyword',
20
+ string: 'syntaxString',
21
+ subst: 'syntaxString',
22
+ quote: 'syntaxString',
23
+ comment: 'syntaxComment',
24
+ number: 'syntaxNumber',
25
+ title: 'syntaxFunction',
26
+ 'title.function_': 'syntaxFunction',
27
+ function: 'syntaxFunction',
28
+ 'title.class_': 'syntaxType',
29
+ type: 'syntaxType',
30
+ class: 'syntaxType',
31
+ tag: 'syntaxType',
32
+ name: 'syntaxType',
33
+ attr: 'syntaxVariable',
34
+ attribute: 'syntaxVariable',
35
+ variable: 'syntaxVariable',
36
+ 'template-variable': 'syntaxVariable',
37
+ params: 'syntaxVariable',
38
+ operator: 'syntaxOperator',
39
+ punctuation: 'syntaxPunctuation',
40
+ meta: 'syntaxPunctuation',
41
+ symbol: 'syntaxConstant',
42
+ regexp: 'syntaxConstant',
43
+ default: 'syntaxVariable',
44
+ };
45
+ /** chalk style for one raw theme value. Accepts every form the theme
46
+ * loader documents: #rgb, #rrggbb, #rrggbbaa (alpha stripped), rgb() with
47
+ * or without spaces, ansi256(n), ansi:name (issue #250, P2-5). */
48
+ export function chalkFromToken(token) {
49
+ let match = /^#([0-9a-fA-F]{3})$/.exec(token);
50
+ if (match !== null) {
51
+ const [r, g, b] = match[1].split('').map(c => parseInt(c + c, 16));
52
+ return chalk.rgb(r, g, b);
53
+ }
54
+ match = /^#([0-9a-fA-F]{6})(?:[0-9a-fA-F]{2})?$/.exec(token);
55
+ if (match !== null)
56
+ return chalk.hex(`#${match[1]}`);
57
+ match = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/.exec(token);
58
+ if (match !== null)
59
+ return chalk.rgb(Number(match[1]), Number(match[2]), Number(match[3]));
60
+ match = /^ansi256\((\d+)\)$/.exec(token);
61
+ if (match !== null)
62
+ return chalk.ansi256(Number(match[1]));
63
+ match = /^ansi:(\w+)$/.exec(token);
64
+ if (match !== null) {
65
+ const name = match[1] === 'blackBright' ? 'gray' : match[1];
66
+ const style = chalk[name];
67
+ if (style !== undefined)
68
+ return style;
69
+ }
70
+ return (text) => text;
71
+ }
72
+ /** ANSI 16-color SGR codes → rgb() strings (the dark-ansi palette path). */
73
+ const ANSI16 = {
74
+ 30: 'rgb(0,0,0)', 31: 'rgb(128,0,0)', 32: 'rgb(0,128,0)', 33: 'rgb(128,128,0)',
75
+ 34: 'rgb(0,0,128)', 35: 'rgb(128,0,128)', 36: 'rgb(0,128,128)', 37: 'rgb(192,192,192)',
76
+ 90: 'rgb(128,128,128)', 91: 'rgb(255,0,0)', 92: 'rgb(0,255,0)', 93: 'rgb(255,255,0)',
77
+ 94: 'rgb(0,0,255)', 95: 'rgb(255,0,255)', 96: 'rgb(0,255,255)', 97: 'rgb(255,255,255)',
78
+ };
79
+ /** Parse a cli-highlight ANSI string into colored runs, preserving open
80
+ * spans across embedded newlines (multi-line strings/comments). Handles
81
+ * truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
82
+ * and single-code 16-color SGR. */
83
+ export function parseAnsiRuns(text) {
84
+ const runs = [];
85
+ let color;
86
+ let rest = text;
87
+ // eslint-disable-next-line no-control-regex -- parsing SGR is the point
88
+ const sgr = /\x1b\[([0-9;]+)m/;
89
+ while (rest.length > 0) {
90
+ const match = sgr.exec(rest);
91
+ const chunk = match === null ? rest : rest.slice(0, match.index);
92
+ if (chunk !== '')
93
+ runs.push(color === undefined ? { text: chunk } : { text: chunk, color });
94
+ if (match === null)
95
+ break;
96
+ const codes = match[1].split(';').map(Number);
97
+ if (codes.includes(0) || codes.includes(39))
98
+ color = undefined;
99
+ else if (codes[0] === 38 && codes[1] === 2 && codes.length >= 5) {
100
+ color = `rgb(${codes[2]},${codes[3]},${codes[4]})`;
101
+ }
102
+ else if (codes[0] === 38 && codes[1] === 5 && codes.length >= 3) {
103
+ color = `ansi256(${codes[2]})`;
104
+ }
105
+ else if (codes.length === 1 && ANSI16[codes[0]] !== undefined) {
106
+ color = ANSI16[codes[0]];
107
+ }
108
+ rest = rest.slice(match.index + match[0].length);
109
+ }
110
+ return runs;
111
+ }
112
+ /** Split flat runs into per-line run arrays at embedded newlines. */
113
+ function splitRunsToLines(runs) {
114
+ const lines = [[]];
115
+ for (const run of runs) {
116
+ const parts = run.text.split('\n');
117
+ for (let i = 0; i < parts.length; i++) {
118
+ if (i > 0)
119
+ lines.push([]);
120
+ if (parts[i] !== '') {
121
+ lines[lines.length - 1].push(run.color === undefined ? { text: parts[i] } : { text: parts[i], color: run.color });
122
+ }
123
+ }
124
+ }
125
+ return lines;
126
+ }
127
+ /** Whole-hunk syntax runs per line, cached per (theme signature + language
128
+ * + text): one entry survives any amount of card re-rendering, and the
129
+ * palette change invalidates it (issue #250, P1-2 + P2-8). */
130
+ const syntaxCache = new Map();
131
+ const SYNTAX_CACHE_MAX = 40;
132
+ export function highlightLines(text, language, hl, chTheme, themeSig) {
133
+ if (hl === null || chTheme === undefined || language === undefined)
134
+ return undefined;
135
+ if (text === '' || !hl.supportsLanguage(language))
136
+ return undefined;
137
+ const key = `${themeSig}${language}${text}`;
138
+ const cached = syntaxCache.get(key);
139
+ if (cached !== undefined)
140
+ return cached;
141
+ let lines;
142
+ try {
143
+ lines = splitRunsToLines(parseAnsiRuns(hl.highlight(text, { language, theme: chTheme })));
144
+ }
145
+ catch {
146
+ return undefined;
147
+ }
148
+ if (syntaxCache.size >= SYNTAX_CACHE_MAX)
149
+ syntaxCache.clear();
150
+ syntaxCache.set(key, lines);
151
+ return lines;
152
+ }
153
+ /**
154
+ * Merge syntax runs with word-diff change flags over the same string: both
155
+ * partition it, so an offset sweep yields runs carrying a syntax color AND
156
+ * a changed flag. The render layer lets `changed` override the color.
157
+ */
158
+ function mergeRuns(syntax, words) {
159
+ const out = [];
160
+ let si = 0;
161
+ let wi = 0;
162
+ let sOff = 0;
163
+ let wOff = 0;
164
+ while (si < syntax.length && wi < words.length) {
165
+ const s = syntax[si];
166
+ const w = words[wi];
167
+ const sLen = s.text.length - sOff;
168
+ const wLen = w.text.length - wOff;
169
+ const take = Math.min(sLen, wLen);
170
+ if (take > 0) {
171
+ out.push({
172
+ text: w.text.slice(wOff, wOff + take),
173
+ changed: w.changed,
174
+ color: w.changed ? undefined : s.color,
175
+ });
176
+ }
177
+ sOff += take;
178
+ wOff += take;
179
+ if (sOff >= s.text.length) {
180
+ si++;
181
+ sOff = 0;
182
+ }
183
+ if (wOff >= w.text.length) {
184
+ wi++;
185
+ wOff = 0;
186
+ }
187
+ }
188
+ return out;
189
+ }
190
+ // --- word-level diff --------------------------------------------------------
191
+ /** Strip the shared leading whitespace before word-diffing: otherwise the
192
+ * whole indent reads as one changed blob (pi's renderIntraLineDiff trick). */
193
+ function wordSegments(oldLine, newLine) {
194
+ const oldIndent = /^\s*/.exec(oldLine)?.[0] ?? '';
195
+ const newIndent = /^\s*/.exec(newLine)?.[0] ?? '';
196
+ const sharedIndent = oldIndent === newIndent ? oldIndent : '';
197
+ const parts = JsDiff.diffWords(oldLine.slice(sharedIndent.length), newLine.slice(sharedIndent.length));
198
+ const oldSegments = sharedIndent === '' ? [] : [{ text: sharedIndent, changed: false }];
199
+ const newSegments = sharedIndent === '' ? [] : [{ text: sharedIndent, changed: false }];
200
+ for (const part of parts) {
201
+ if (part.added)
202
+ newSegments.push({ text: part.value, changed: true });
203
+ else if (part.removed)
204
+ oldSegments.push({ text: part.value, changed: true });
205
+ else {
206
+ oldSegments.push({ text: part.value, changed: false });
207
+ newSegments.push({ text: part.value, changed: false });
208
+ }
209
+ }
210
+ return { old: oldSegments, new: newSegments };
211
+ }
212
+ const plainSegments = (line) => [{ text: line, changed: false }];
213
+ // --- hunk alignment ---------------------------------------------------------
214
+ /**
215
+ * Pair the lines of an unequal removed+added block via a case-insensitive
216
+ * LCS: lines equal modulo case align as change pairs, everything before the
217
+ * first match stays block order. An inserted line above an edited one
218
+ * (`foo,bar` → `insert,FOO,bar`) therefore yields `insert` add-only, then
219
+ * `foo ↔ FOO`, instead of the index-zip misalignment (issue #250, P2-4).
220
+ */
221
+ function lcsPairs(oldLines, newLines) {
222
+ const m = oldLines.length;
223
+ const n = newLines.length;
224
+ // dp[i][j] = LCS length of oldLines[i:] and newLines[j:].
225
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
226
+ const eq = (a, b) => a.toLowerCase() === b.toLowerCase();
227
+ for (let i = m - 1; i >= 0; i--) {
228
+ for (let j = n - 1; j >= 0; j--) {
229
+ dp[i][j] = eq(oldLines[i], newLines[j])
230
+ ? dp[i + 1][j + 1] + 1
231
+ : Math.max(dp[i + 1][j], dp[i][j + 1]);
232
+ }
233
+ }
234
+ const pairs = [];
235
+ let i = 0;
236
+ let j = 0;
237
+ while (i < m && j < n) {
238
+ if (eq(oldLines[i], newLines[j])) {
239
+ pairs.push([i, j]);
240
+ i++;
241
+ j++;
242
+ }
243
+ else if (dp[i + 1][j] >= dp[i][j + 1]) {
244
+ i++;
245
+ }
246
+ else {
247
+ j++;
248
+ }
249
+ }
250
+ return pairs;
251
+ }
252
+ /** Align one file's hunks into rows (plain text + word flags; styling is a
253
+ * render-time concern over the visible slice). */
254
+ function alignFileDiff(fileIndex, oldText, newText) {
255
+ const rows = [];
256
+ const parts = JsDiff.diffLines(expandTabs(oldText ?? ''), expandTabs(newText));
257
+ let oldIndex = 0;
258
+ let newIndex = 0;
259
+ for (let i = 0; i < parts.length; i++) {
260
+ const part = parts[i];
261
+ const lines = part.value.replace(/\n$/, '').split('\n');
262
+ if (!part.added && !part.removed) {
263
+ for (const line of lines) {
264
+ rows.push({
265
+ kind: 'context',
266
+ fileIndex,
267
+ oldIndex: oldIndex++,
268
+ newIndex: newIndex++,
269
+ oldWords: plainSegments(line),
270
+ newWords: plainSegments(line),
271
+ });
272
+ }
273
+ continue;
274
+ }
275
+ if (part.removed) {
276
+ const addedPart = parts[i + 1]?.added === true ? parts[i + 1] : undefined;
277
+ const addedLines = addedPart === undefined ? [] : addedPart.value.replace(/\n$/, '').split('\n');
278
+ // Same-length replacement blocks zip index-for-index (the common
279
+ // edit case); unequal blocks pair through the ci-LCS so an inserted
280
+ // line cannot shift the alignment (issue #250, P2-4).
281
+ const pairs = lines.length === addedLines.length
282
+ ? lines.map((_, index) => [index, index])
283
+ : lcsPairs(lines, addedLines);
284
+ // Block order: walk both lists; unpaired entries emit in place, pairs
285
+ // emit at the later of the two positions.
286
+ const events = [];
287
+ let oi = 0;
288
+ let ai = 0;
289
+ for (const [o, a] of pairs) {
290
+ while (oi < o)
291
+ events.push({ kind: 'del', o: oi++ });
292
+ while (ai < a)
293
+ events.push({ kind: 'add', a: ai++ });
294
+ events.push({ kind: 'change', o: oi++, a: ai++ });
295
+ }
296
+ while (oi < lines.length)
297
+ events.push({ kind: 'del', o: oi++ });
298
+ while (ai < addedLines.length)
299
+ events.push({ kind: 'add', a: ai++ });
300
+ for (const event of events) {
301
+ if (event.kind === 'change') {
302
+ const segments = wordSegments(lines[event.o], addedLines[event.a]);
303
+ rows.push({
304
+ kind: 'change',
305
+ fileIndex,
306
+ oldIndex: oldIndex++,
307
+ newIndex: newIndex++,
308
+ oldWords: segments.old,
309
+ newWords: segments.new,
310
+ });
311
+ }
312
+ else if (event.kind === 'del') {
313
+ rows.push({ kind: 'del', fileIndex, oldIndex: oldIndex++, oldWords: plainSegments(lines[event.o]) });
314
+ }
315
+ else {
316
+ rows.push({ kind: 'add', fileIndex, newIndex: newIndex++, newWords: plainSegments(addedLines[event.a]) });
317
+ }
318
+ }
319
+ if (addedPart !== undefined)
320
+ i++;
321
+ continue;
322
+ }
323
+ for (const line of lines) {
324
+ rows.push({ kind: 'add', fileIndex, newIndex: newIndex++, newWords: plainSegments(line) });
325
+ }
326
+ }
327
+ return rows;
328
+ }
329
+ // --- rendering --------------------------------------------------------------
330
+ function PaneLine({ side, kind, width, tone, padLeft = false, }) {
331
+ const backgroundColor = kind === 'context'
332
+ ? 'toolCardBackground'
333
+ : tone === 'old'
334
+ ? 'diffRemovedDimmed'
335
+ : 'diffAddedDimmed';
336
+ const wordColor = tone === 'old' ? 'diffRemovedWord' : 'diffAddedWord';
337
+ // Status gutter instead of line numbers: ToolFileDiff has no file
338
+ // offsets, and an invented number misleads (issue #250, P2-3).
339
+ const marker = kind === 'context' ? ' ' : tone === 'old' ? '−' : '+';
340
+ const prefix = padLeft ? ` ${marker}` : marker;
341
+ return (_jsxs(Box, { width: width, flexShrink: 0, backgroundColor: backgroundColor, children: [_jsx(Text, { dimColor: true, backgroundColor: backgroundColor, children: `${prefix} ` }), side === undefined ? (_jsx(Text, { backgroundColor: backgroundColor, children: " " })) : (_jsx(Text, { wrap: "truncate", backgroundColor: backgroundColor, children: side.segments.map((segment, index) => segment.changed ? (_jsx(Text, { color: wordColor, bold: true, backgroundColor: backgroundColor, children: segment.text }, index)) : (_jsx(Text, { color: segment.color, backgroundColor: backgroundColor, children: segment.text }, index))) }))] }));
342
+ }
343
+ export function SplitDiffView({ diffs, width, maxRows, verbose, }) {
344
+ const [hl, setHl] = React.useState(null);
345
+ React.useEffect(() => {
346
+ let mounted = true;
347
+ void getCliHighlightPromise().then(loaded => {
348
+ if (mounted)
349
+ setHl(loaded);
350
+ });
351
+ return () => { mounted = false; };
352
+ }, []);
353
+ const [themeName] = useTheme();
354
+ // Resolved-palette signature: covers dark→light, light→dark, AND the
355
+ // `auto` base flip where the name never changes (issue #250, P1-2).
356
+ const themeSig = React.useMemo(() => {
357
+ const theme = getTheme(themeName);
358
+ return Object.values(SYNTAX_CLASS_TO_TOKEN)
359
+ .map(key => theme[key] ?? '')
360
+ .join('|');
361
+ }, [themeName]);
362
+ const chTheme = React.useMemo(() => {
363
+ const theme = getTheme(themeName);
364
+ const out = {};
365
+ for (const [tokenClass, tokenKey] of Object.entries(SYNTAX_CLASS_TO_TOKEN)) {
366
+ const value = theme[tokenKey];
367
+ if (value !== undefined)
368
+ out[tokenClass] = chalkFromToken(value);
369
+ }
370
+ return out;
371
+ // themeSig changes exactly when any syntax token's resolved value does.
372
+ // eslint-disable-next-line react-hooks/exhaustive-deps
373
+ }, [themeSig]);
374
+ // Alignment is text-only and capped BEFORE styling: the highlighter and
375
+ // word-diff work lands only on the visible slice (issue #250, P2-8).
376
+ const rows = [];
377
+ let prevPath;
378
+ diffs.forEach((diff, fileIndex) => {
379
+ if (diffs.length > 1) {
380
+ if (diff.path !== prevPath)
381
+ rows.push({ separator: diff.path });
382
+ else
383
+ rows.push({ separator: '⋯' });
384
+ }
385
+ prevPath = diff.path;
386
+ rows.push(...alignFileDiff(fileIndex, diff.oldText, diff.newText));
387
+ });
388
+ const totalRows = rows.length;
389
+ const capped = verbose || totalRows <= maxRows || totalRows - maxRows === 1;
390
+ const visible = capped ? rows : rows.slice(0, maxRows);
391
+ const hidden = totalRows - visible.length;
392
+ const paneWidth = Math.max(20, Math.floor((width - 1) / 2));
393
+ // Whole-hunk highlight per file (multi-line lexer state preserved);
394
+ // only the visible rows merge syntax runs with word flags below.
395
+ const fileSyntax = diffs.map(diff => {
396
+ const language = extname(diff.path).replace(/^\./, '') || undefined;
397
+ return {
398
+ old: highlightLines(expandTabs(diff.oldText ?? ''), language, hl, chTheme, themeSig),
399
+ next: highlightLines(expandTabs(diff.newText), language, hl, chTheme, themeSig),
400
+ };
401
+ });
402
+ return (_jsxs(Box, { flexDirection: "column", width: paneWidth * 2 + 1, backgroundColor: "toolCardBackgroundDim", children: [visible.map((row, index) => {
403
+ if ('separator' in row) {
404
+ return (_jsx(Box, { width: paneWidth * 2 + 1, children: _jsx(Text, { dimColor: true, wrap: "truncate", children: row.separator === '⋯' ? '⋯' : ` ${row.separator}` }) }, index));
405
+ }
406
+ const syntax = fileSyntax[row.fileIndex];
407
+ const oldRuns = row.oldIndex !== undefined ? syntax?.old?.[row.oldIndex] : undefined;
408
+ const newRuns = row.newIndex !== undefined ? syntax?.next?.[row.newIndex] : undefined;
409
+ const oldSide = row.oldWords === undefined
410
+ ? undefined
411
+ : { segments: oldRuns !== undefined ? mergeRuns(oldRuns, row.oldWords) : row.oldWords };
412
+ const newSide = row.newWords === undefined
413
+ ? undefined
414
+ : { segments: newRuns !== undefined ? mergeRuns(newRuns, row.newWords) : row.newWords };
415
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsx(PaneLine, { side: oldSide, kind: row.kind === 'add' ? 'context' : row.kind, tone: "old", width: paneWidth }), _jsx(Box, { width: 1, flexShrink: 0, backgroundColor: "toolCardBackgroundDim", children: _jsx(Text, { dimColor: true, children: "\u2502" }) }), _jsx(PaneLine, { side: newSide, kind: row.kind === 'del' ? 'context' : row.kind, tone: "new", width: paneWidth, padLeft: true })] }, index));
416
+ }), hidden > 0 && (_jsx(Text, { dimColor: true, children: `… +${hidden} lines (ctrl+o to expand)` }))] }));
417
+ }
@@ -8,10 +8,12 @@ import type { SpinnerMode } from './Spinner/spinnerMode.js';
8
8
  *
9
9
  * Random verb is picked once per turn (per mount of the spinner).
10
10
  */
11
- export declare function WorkingSpinner({ mode, hasActiveTools, responseLengthRef, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, thinkingStatus, }: {
11
+ export declare function WorkingSpinner({ mode, hasActiveTools, responseLengthRef, uploadTokensRef, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, thinkingStatus, }: {
12
12
  mode: SpinnerMode;
13
13
  hasActiveTools: boolean;
14
14
  responseLengthRef: React.RefObject<number>;
15
+ /** Most recent request's real upload tokens; 0 until the first usage event. */
16
+ uploadTokensRef: React.RefObject<number>;
15
17
  loadingStartTimeRef: React.RefObject<number>;
16
18
  totalPausedMsRef: React.RefObject<number>;
17
19
  pauseStartTimeRef: React.RefObject<number | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"WorkingSpinner.d.ts","sourceRoot":"","sources":["../../../src/components/WorkingSpinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAA;AAG1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAK3D;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,GACf,EAAE;IACD,IAAI,EAAE,WAAW,CAAA;IACjB,cAAc,EAAE,OAAO,CAAA;IACvB,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC1C,mBAAmB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC5C,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACzC,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACjD,cAAc,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAA;CAC3C,GAAG,KAAK,CAAC,SAAS,CA2BlB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,OAAO,GAClB,UAAU,GAAG,MAAM,GAAG,IAAI,CA6C5B"}
1
+ {"version":3,"file":"WorkingSpinner.d.ts","sourceRoot":"","sources":["../../../src/components/WorkingSpinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAA;AAG1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAK3D;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,GACf,EAAE;IACD,IAAI,EAAE,WAAW,CAAA;IACjB,cAAc,EAAE,OAAO,CAAA;IACvB,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC1C,+EAA+E;IAC/E,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACxC,mBAAmB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC5C,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACzC,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACjD,cAAc,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAA;CAC3C,GAAG,KAAK,CAAC,SAAS,CA4BlB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,OAAO,GAClB,UAAU,GAAG,MAAM,GAAG,IAAI,CA6C5B"}
@@ -13,12 +13,12 @@ import { sample } from 'lodash-es';
13
13
  *
14
14
  * Random verb is picked once per turn (per mount of the spinner).
15
15
  */
16
- export function WorkingSpinner({ mode, hasActiveTools, responseLengthRef, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, thinkingStatus, }) {
16
+ export function WorkingSpinner({ mode, hasActiveTools, responseLengthRef, uploadTokensRef, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, thinkingStatus, }) {
17
17
  const { columns } = useTerminalSize();
18
18
  // Pick a random verb once per spinner mount (per turn).
19
19
  const [randomVerb] = useState(() => sample(SPINNER_VERBS) ?? 'Working');
20
20
  const message = `${randomVerb}…`;
21
- return (_jsx(Box, { flexDirection: "column", width: "100%", alignItems: "flex-start", children: _jsx(SpinnerAnimationRow, { mode: mode, reducedMotion: false, hasActiveTools: hasActiveTools, responseLengthRef: responseLengthRef, message: message, messageColor: "claude", shimmerColor: "claudeShimmer", loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, spinnerSuffix: null, verbose: true, columns: columns, thinkingStatus: thinkingStatus }) }));
21
+ return (_jsx(Box, { flexDirection: "column", width: "100%", alignItems: "flex-start", children: _jsx(SpinnerAnimationRow, { mode: mode, reducedMotion: false, hasActiveTools: hasActiveTools, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, message: message, messageColor: "claude", shimmerColor: "claudeShimmer", loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, spinnerSuffix: null, verbose: true, columns: columns, thinkingStatus: thinkingStatus }) }));
22
22
  }
23
23
  /**
24
24
  * Tracks thinking status: 'thinking' while the model is streaming reasoning,
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { Color, Styles } from '../../ink/styles.js';
3
+ import type { DOMElement } from '../../ink/dom.js';
3
4
  import { type Theme } from '../../theme.js';
4
5
  /**
5
6
  * Colors uncolored ThemedText in the subtree. Precedence: explicit `color` >
@@ -45,6 +46,11 @@ export type Props = {
45
46
  * This property tells Ink to wrap or truncate text if its width is larger than container.
46
47
  */
47
48
  readonly wrap?: Styles['textWrap'];
49
+ /**
50
+ * Ref to the underlying ink-text DOMElement (e.g. useDeclaredCursor
51
+ * parking the native cursor on an inline caret cell).
52
+ */
53
+ readonly ref?: React.Ref<DOMElement>;
48
54
  readonly children?: React.ReactNode;
49
55
  };
50
56
  /**
@@ -52,5 +58,5 @@ export type Props = {
52
58
  * (in the Claude Code visual language). This is what lets every ported CC
53
59
  * component use `color="subtle"`-style theme keys unchanged.
54
60
  */
55
- export default function ThemedText({ color, backgroundColor, dimColor, bold, italic, underline, strikethrough, inverse, wrap, children, }: Props): React.ReactNode;
61
+ export default function ThemedText({ color, backgroundColor, dimColor, bold, italic, underline, strikethrough, inverse, wrap, ref, children, }: Props): React.ReactNode;
56
62
  //# sourceMappingURL=ThemedText.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ThemedText.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/ThemedText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAY,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAGrD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,wCAEtB,CAAA;AAqBZ,MAAM,MAAM,KAAK,GAAG;IAClB;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAEpC;;OAEG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAE9C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAE3B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IAEzB;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAE5B;;OAEG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;IAEhC;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAE1B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IAElC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACpC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EACjC,KAAK,EACL,eAAe,EACf,QAAgB,EAChB,IAAY,EACZ,MAAc,EACd,SAAiB,EACjB,aAAqB,EACrB,OAAe,EACf,IAAa,EACb,QAAQ,GACT,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CA4BzB"}
1
+ {"version":3,"file":"ThemedText.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/ThemedText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAY,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAGrD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,wCAEtB,CAAA;AAqBZ,MAAM,MAAM,KAAK,GAAG;IAClB;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAEpC;;OAEG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAE9C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAE3B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IAEzB;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAE5B;;OAEG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;IAEhC;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAE1B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IAElC;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAEpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACpC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EACjC,KAAK,EACL,eAAe,EACf,QAAgB,EAChB,IAAY,EACZ,MAAc,EACd,SAAiB,EACjB,aAAqB,EACrB,OAAe,EACf,IAAa,EACb,GAAG,EACH,QAAQ,GACT,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CA6BzB"}
@@ -28,7 +28,7 @@ function resolveColor(color, theme) {
28
28
  * (in the Claude Code visual language). This is what lets every ported CC
29
29
  * component use `color="subtle"`-style theme keys unchanged.
30
30
  */
31
- export default function ThemedText({ color, backgroundColor, dimColor = false, bold = false, italic = false, underline = false, strikethrough = false, inverse = false, wrap = 'wrap', children, }) {
31
+ export default function ThemedText({ color, backgroundColor, dimColor = false, bold = false, italic = false, underline = false, strikethrough = false, inverse = false, wrap = 'wrap', ref, children, }) {
32
32
  const [themeName] = useTheme();
33
33
  const theme = getTheme(themeName);
34
34
  const hoverColor = React.useContext(TextHoverColorContext);
@@ -39,5 +39,5 @@ export default function ThemedText({ color, backgroundColor, dimColor = false, b
39
39
  ? theme.inactive
40
40
  : resolveColor(color, theme);
41
41
  const resolvedBackgroundColor = resolveColor(backgroundColor, theme);
42
- return (_jsx(Text, { color: resolvedColor, backgroundColor: resolvedBackgroundColor, bold: bold, italic: italic, underline: underline, strikethrough: strikethrough, inverse: inverse, wrap: wrap, children: children }));
42
+ return (_jsx(Text, { ref: ref, color: resolvedColor, backgroundColor: resolvedBackgroundColor, bold: bold, italic: italic, underline: underline, strikethrough: strikethrough, inverse: inverse, wrap: wrap, children: children }));
43
43
  }
@@ -19,6 +19,8 @@ type Props = {
19
19
  * is worth less than mentioning it once.
20
20
  */
21
21
  footnote?: string;
22
+ /** Diff presentation preference; `auto` picks by terminal width. */
23
+ diffLayout?: 'auto' | 'split' | 'unified';
22
24
  };
23
25
  /**
24
26
  * Tool-call card: `● Edit /path` header with a blinking status dot, then the
@@ -26,6 +28,6 @@ type Props = {
26
28
  * output, read content — instead of the raw result dump (mirroring Claude Code's `AssistantToolUseMessage.tsx` + the dsh-tools presentation views the
27
29
  * channel captures per call).
28
30
  */
29
- export declare function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected, isExpanded, footnote, }: Props): React.ReactNode;
31
+ export declare function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected, isExpanded, footnote, diffLayout, }: Props): React.ReactNode;
30
32
  export {};
31
33
  //# sourceMappingURL=AssistantToolUseMessage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAIvG,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAsMD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAkB,EAClB,UAAkB,EAClB,QAAQ,GACT,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAwGzB"}
1
+ {"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAKvG,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;CAC1C,CAAA;AAyMD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAkB,EAClB,UAAkB,EAClB,QAAQ,EACR,UAAmB,GACpB,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAoIzB"}