erosolar-cli 2.1.62 → 2.1.65
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.
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +40 -19
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/ui/UnifiedUIRenderer.d.ts +26 -4
- package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
- package/dist/ui/UnifiedUIRenderer.js +150 -79
- package/dist/ui/UnifiedUIRenderer.js.map +1 -1
- package/dist/ui/animatedStatus.d.ts +129 -0
- package/dist/ui/animatedStatus.d.ts.map +1 -0
- package/dist/ui/animatedStatus.js +384 -0
- package/dist/ui/animatedStatus.js.map +1 -0
- package/package.json +1 -1
|
@@ -53,16 +53,22 @@ export declare class UnifiedUIRenderer extends EventEmitter {
|
|
|
53
53
|
private statusMessage;
|
|
54
54
|
private statusOverride;
|
|
55
55
|
private statusStreaming;
|
|
56
|
+
private streamingSpinner;
|
|
57
|
+
private thinkingIndicator;
|
|
58
|
+
private contextMeter;
|
|
59
|
+
private spinnerFrame;
|
|
60
|
+
private spinnerInterval;
|
|
61
|
+
private compactingStatusMessage;
|
|
62
|
+
private compactingStatusFrame;
|
|
63
|
+
private compactingStatusInterval;
|
|
64
|
+
private readonly compactingSpinnerFrames;
|
|
56
65
|
private statusMeta;
|
|
57
66
|
private toggleState;
|
|
58
67
|
private formatHotkey;
|
|
59
68
|
private lastPromptEvent;
|
|
60
69
|
private promptHeight;
|
|
61
70
|
private lastOverlayHeight;
|
|
62
|
-
private lastPromptIndex;
|
|
63
|
-
private readonly overlayBottomPadding;
|
|
64
71
|
private inlinePanel;
|
|
65
|
-
private overlayInvalidated;
|
|
66
72
|
private hasConversationContent;
|
|
67
73
|
private isPromptActive;
|
|
68
74
|
private inputRenderOffset;
|
|
@@ -131,6 +137,14 @@ export declare class UnifiedUIRenderer extends EventEmitter {
|
|
|
131
137
|
*/
|
|
132
138
|
addCompactBlock(content: string, label?: string): void;
|
|
133
139
|
setMode(mode: 'idle' | 'streaming'): void;
|
|
140
|
+
/**
|
|
141
|
+
* Start the animated spinner for streaming status
|
|
142
|
+
*/
|
|
143
|
+
private startSpinnerAnimation;
|
|
144
|
+
/**
|
|
145
|
+
* Stop the animated spinner
|
|
146
|
+
*/
|
|
147
|
+
private stopSpinnerAnimation;
|
|
134
148
|
getMode(): 'idle' | 'streaming';
|
|
135
149
|
updateStatus(message: string | null): void;
|
|
136
150
|
updateStatusBundle(status: {
|
|
@@ -186,12 +200,20 @@ export declare class UnifiedUIRenderer extends EventEmitter {
|
|
|
186
200
|
private visibleLength;
|
|
187
201
|
private stripAnsi;
|
|
188
202
|
private truncateLine;
|
|
189
|
-
private clearOverlayRows;
|
|
190
203
|
getBuffer(): string;
|
|
191
204
|
getCursor(): number;
|
|
192
205
|
setBuffer(text: string, cursorPos?: number): void;
|
|
193
206
|
clearBuffer(): void;
|
|
194
207
|
setModeStatus(status: string | null): void;
|
|
208
|
+
/**
|
|
209
|
+
* Show a compacting status with animated spinner (Claude Code style)
|
|
210
|
+
* Uses ✻ character with animation to indicate context compaction in progress
|
|
211
|
+
*/
|
|
212
|
+
showCompactingStatus(message: string): void;
|
|
213
|
+
/**
|
|
214
|
+
* Hide the compacting status and stop spinner animation
|
|
215
|
+
*/
|
|
216
|
+
hideCompactingStatus(): void;
|
|
195
217
|
emitPrompt(content: string): void;
|
|
196
218
|
/**
|
|
197
219
|
* Display user prompt immediately in scrollback (Claude Code style)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UnifiedUIRenderer.d.ts","sourceRoot":"","sources":["../../src/ui/UnifiedUIRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"UnifiedUIRenderer.d.ts","sourceRoot":"","sources":["../../src/ui/UnifiedUIRenderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
import * as readline from 'node:readline';
|
|
12
12
|
import { EventEmitter } from 'node:events';
|
|
13
13
|
import { homedir } from 'node:os';
|
|
14
|
-
import { theme } from './theme.js';
|
|
14
|
+
import { theme, spinnerFrames } from './theme.js';
|
|
15
15
|
import { isPlainOutputMode } from './outputMode.js';
|
|
16
|
-
import {
|
|
16
|
+
import { ContextMeter, disposeAnimations } from './animatedStatus.js';
|
|
17
17
|
const ESC = {
|
|
18
18
|
HIDE_CURSOR: '\x1b[?25l',
|
|
19
19
|
SHOW_CURSOR: '\x1b[?25h',
|
|
@@ -54,6 +54,17 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
54
54
|
statusMessage = null;
|
|
55
55
|
statusOverride = null;
|
|
56
56
|
statusStreaming = null;
|
|
57
|
+
// Animated UI components
|
|
58
|
+
streamingSpinner = null;
|
|
59
|
+
thinkingIndicator = null;
|
|
60
|
+
contextMeter;
|
|
61
|
+
spinnerFrame = 0;
|
|
62
|
+
spinnerInterval = null;
|
|
63
|
+
// Compacting status animation
|
|
64
|
+
compactingStatusMessage = '';
|
|
65
|
+
compactingStatusFrame = 0;
|
|
66
|
+
compactingStatusInterval = null;
|
|
67
|
+
compactingSpinnerFrames = ['✻', '✼', '✻', '✺'];
|
|
57
68
|
statusMeta = {};
|
|
58
69
|
toggleState = {
|
|
59
70
|
verificationEnabled: false,
|
|
@@ -69,10 +80,7 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
69
80
|
lastPromptEvent = null;
|
|
70
81
|
promptHeight = 0;
|
|
71
82
|
lastOverlayHeight = 0;
|
|
72
|
-
lastPromptIndex = 0;
|
|
73
|
-
overlayBottomPadding = 1;
|
|
74
83
|
inlinePanel = [];
|
|
75
|
-
overlayInvalidated = false;
|
|
76
84
|
hasConversationContent = false;
|
|
77
85
|
isPromptActive = false;
|
|
78
86
|
inputRenderOffset = 0;
|
|
@@ -91,7 +99,7 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
91
99
|
lastRenderedEventKey = null;
|
|
92
100
|
lastOutputEndedWithNewline = true;
|
|
93
101
|
hasRenderedPrompt = false;
|
|
94
|
-
hasEverRenderedOverlay = false; // Track if we've ever rendered
|
|
102
|
+
hasEverRenderedOverlay = false; // Track if we've ever rendered for inline clearing
|
|
95
103
|
lastOverlay = null;
|
|
96
104
|
allowPromptRender = true;
|
|
97
105
|
inputCapture = null;
|
|
@@ -101,6 +109,8 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
101
109
|
this.input = input;
|
|
102
110
|
this.interactive = Boolean(this.output.isTTY && this.input.isTTY && !process.env['CI']);
|
|
103
111
|
this.plainMode = isPlainOutputMode() || !this.interactive;
|
|
112
|
+
// Initialize animated components
|
|
113
|
+
this.contextMeter = new ContextMeter();
|
|
104
114
|
this.rl = readline.createInterface({
|
|
105
115
|
input: this.input,
|
|
106
116
|
output: this.output,
|
|
@@ -147,6 +157,21 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
147
157
|
cleanup() {
|
|
148
158
|
this.cancelInputCapture(new Error('Renderer disposed'));
|
|
149
159
|
this.cancelPlainPasteCapture();
|
|
160
|
+
// Stop any running animations
|
|
161
|
+
if (this.spinnerInterval) {
|
|
162
|
+
clearInterval(this.spinnerInterval);
|
|
163
|
+
this.spinnerInterval = null;
|
|
164
|
+
}
|
|
165
|
+
if (this.streamingSpinner) {
|
|
166
|
+
this.streamingSpinner.stop();
|
|
167
|
+
this.streamingSpinner = null;
|
|
168
|
+
}
|
|
169
|
+
if (this.thinkingIndicator) {
|
|
170
|
+
this.thinkingIndicator.stop();
|
|
171
|
+
this.thinkingIndicator = null;
|
|
172
|
+
}
|
|
173
|
+
this.contextMeter.dispose();
|
|
174
|
+
disposeAnimations();
|
|
150
175
|
if (!this.interactive) {
|
|
151
176
|
this.rl.close();
|
|
152
177
|
return;
|
|
@@ -800,8 +825,6 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
800
825
|
}
|
|
801
826
|
this.output.write(formatted);
|
|
802
827
|
this.lastOutputEndedWithNewline = formatted.endsWith('\n');
|
|
803
|
-
// Overlay must be re-anchored after new scrollback is written
|
|
804
|
-
this.overlayInvalidated = true;
|
|
805
828
|
// Don't re-render prompt after every event - wait for queue to finish
|
|
806
829
|
// This prevents premature prompt rendering that cuts off responses
|
|
807
830
|
}
|
|
@@ -847,10 +870,19 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
847
870
|
case 'prompt':
|
|
848
871
|
return `\n> ${event.content}\n`; // Plain > like Claude Code
|
|
849
872
|
case 'thought': {
|
|
850
|
-
//
|
|
873
|
+
// Enhanced thought display with animated thinking indicator style
|
|
874
|
+
// Uses ◐ symbol for thinking with muted styling to distinguish from content
|
|
875
|
+
const thinkingIcon = theme.thinking?.icon ? theme.thinking.icon('◐') : theme.ui.muted('◐');
|
|
876
|
+
const thinkingLabel = theme.thinking?.label ? theme.thinking.label('Thinking') : theme.ui.muted('Thinking');
|
|
851
877
|
const lines = event.content.split('\n');
|
|
878
|
+
// Show thinking header with first line, then indent continuation
|
|
852
879
|
const formatted = lines
|
|
853
|
-
.map((line, i) =>
|
|
880
|
+
.map((line, i) => {
|
|
881
|
+
if (i === 0) {
|
|
882
|
+
return `${thinkingIcon} ${thinkingLabel}: ${theme.ui.muted(line)}`;
|
|
883
|
+
}
|
|
884
|
+
return ` ${theme.ui.muted(line)}`;
|
|
885
|
+
})
|
|
854
886
|
.join('\n');
|
|
855
887
|
return `\n${formatted}\n`;
|
|
856
888
|
}
|
|
@@ -908,6 +940,13 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
908
940
|
setMode(mode) {
|
|
909
941
|
const wasStreaming = this.mode === 'streaming';
|
|
910
942
|
this.mode = mode;
|
|
943
|
+
// Start/stop spinner animation based on streaming state
|
|
944
|
+
if (mode === 'streaming' && !wasStreaming) {
|
|
945
|
+
this.startSpinnerAnimation();
|
|
946
|
+
}
|
|
947
|
+
else if (mode === 'idle' && wasStreaming) {
|
|
948
|
+
this.stopSpinnerAnimation();
|
|
949
|
+
}
|
|
911
950
|
if (wasStreaming && mode === 'idle' && !this.lastOutputEndedWithNewline) {
|
|
912
951
|
// Finish streaming on a fresh line so the next prompt/event doesn't collide
|
|
913
952
|
this.write('\n');
|
|
@@ -918,6 +957,31 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
918
957
|
this.renderPrompt();
|
|
919
958
|
}
|
|
920
959
|
}
|
|
960
|
+
/**
|
|
961
|
+
* Start the animated spinner for streaming status
|
|
962
|
+
*/
|
|
963
|
+
startSpinnerAnimation() {
|
|
964
|
+
if (this.spinnerInterval)
|
|
965
|
+
return; // Already running
|
|
966
|
+
this.spinnerFrame = 0;
|
|
967
|
+
this.spinnerInterval = setInterval(() => {
|
|
968
|
+
this.spinnerFrame = (this.spinnerFrame + 1) % spinnerFrames.braille.length;
|
|
969
|
+
// Re-render to show updated spinner frame
|
|
970
|
+
if (!this.plainMode && this.mode === 'streaming') {
|
|
971
|
+
this.renderPrompt();
|
|
972
|
+
}
|
|
973
|
+
}, 80); // ~12 FPS for smooth spinner animation
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Stop the animated spinner
|
|
977
|
+
*/
|
|
978
|
+
stopSpinnerAnimation() {
|
|
979
|
+
if (this.spinnerInterval) {
|
|
980
|
+
clearInterval(this.spinnerInterval);
|
|
981
|
+
this.spinnerInterval = null;
|
|
982
|
+
}
|
|
983
|
+
this.spinnerFrame = 0;
|
|
984
|
+
}
|
|
921
985
|
getMode() {
|
|
922
986
|
return this.mode;
|
|
923
987
|
}
|
|
@@ -1013,13 +1077,9 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1013
1077
|
if (!this.allowPromptRender) {
|
|
1014
1078
|
return;
|
|
1015
1079
|
}
|
|
1016
|
-
// Rich mode: inline
|
|
1080
|
+
// Rich mode: inline rendering (no full-screen overlay)
|
|
1017
1081
|
this.updateTerminalSize();
|
|
1018
1082
|
const maxWidth = this.safeWidth();
|
|
1019
|
-
if (this.lastRenderWidth !== null && maxWidth !== this.lastRenderWidth) {
|
|
1020
|
-
// Terminal resized; force a clean anchor so the overlay doesn't jitter.
|
|
1021
|
-
this.overlayInvalidated = true;
|
|
1022
|
-
}
|
|
1023
1083
|
this.lastRenderWidth = maxWidth;
|
|
1024
1084
|
const overlay = this.buildOverlayLines();
|
|
1025
1085
|
if (!overlay.lines.length) {
|
|
@@ -1031,45 +1091,39 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1031
1091
|
}
|
|
1032
1092
|
let promptIndex = Math.max(0, Math.min(overlay.promptIndex, renderedLines.length - 1));
|
|
1033
1093
|
let height = renderedLines.length;
|
|
1034
|
-
// Keep at least one free line below the overlay so typing always has breathing room
|
|
1035
|
-
const bottomPadding = this.overlayBottomPadding;
|
|
1036
1094
|
const totalRows = this.rows || 24;
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
this.clearOverlayRows(height, startRow);
|
|
1048
|
-
if (bottomPadding > 0 && startRow + height <= totalRows) {
|
|
1049
|
-
this.write(ESC.TO(startRow + height, 1));
|
|
1050
|
-
this.write(ESC.CLEAR_LINE);
|
|
1095
|
+
// PURE INLINE MODE: Always render the prompt area inline, flowing naturally.
|
|
1096
|
+
// No overlay mode - content flows like Claude Code.
|
|
1097
|
+
// Clear any previous inline render by moving up and clearing
|
|
1098
|
+
if (this.hasEverRenderedOverlay && this.lastOverlayHeight > 0) {
|
|
1099
|
+
// Move to start of previous prompt and clear it
|
|
1100
|
+
const linesToClear = this.lastOverlayHeight;
|
|
1101
|
+
for (let i = 0; i < linesToClear; i++) {
|
|
1102
|
+
this.write('\x1b[A'); // Move up
|
|
1103
|
+
this.write(ESC.CLEAR_LINE);
|
|
1104
|
+
}
|
|
1051
1105
|
}
|
|
1052
|
-
//
|
|
1053
|
-
for (
|
|
1054
|
-
const row = startRow + idx;
|
|
1055
|
-
const line = renderedLines[idx] ?? '';
|
|
1056
|
-
this.write(ESC.TO(row, 1));
|
|
1106
|
+
// Write prompt lines directly to scrollback (no absolute positioning)
|
|
1107
|
+
for (const line of renderedLines) {
|
|
1057
1108
|
this.write(ESC.CLEAR_LINE);
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1109
|
+
this.write(line);
|
|
1110
|
+
this.write('\n');
|
|
1061
1111
|
}
|
|
1062
|
-
// Position cursor at prompt
|
|
1063
|
-
|
|
1112
|
+
// Position cursor at prompt input position
|
|
1113
|
+
const promptCol = Math.min(Math.max(1, 3 + this.cursor), this.cols || 80);
|
|
1114
|
+
// Move up to the prompt line and position cursor
|
|
1115
|
+
const linesToMoveUp = height - promptIndex - 1;
|
|
1116
|
+
if (linesToMoveUp > 0) {
|
|
1117
|
+
this.write(`\x1b[${linesToMoveUp}A`); // Move cursor up
|
|
1118
|
+
}
|
|
1119
|
+
this.write(`\x1b[${promptCol}G`); // Move to column
|
|
1064
1120
|
this.cursorVisibleColumn = promptCol;
|
|
1065
1121
|
this.hasRenderedPrompt = true;
|
|
1066
|
-
this.hasEverRenderedOverlay = true;
|
|
1122
|
+
this.hasEverRenderedOverlay = true;
|
|
1067
1123
|
this.isPromptActive = true;
|
|
1068
1124
|
this.lastOverlayHeight = height;
|
|
1069
|
-
this.lastPromptIndex = promptIndex;
|
|
1070
1125
|
this.lastOverlay = { lines: renderedLines, promptIndex };
|
|
1071
|
-
this.
|
|
1072
|
-
this.lastOutputEndedWithNewline = true;
|
|
1126
|
+
this.lastOutputEndedWithNewline = false;
|
|
1073
1127
|
this.promptHeight = height;
|
|
1074
1128
|
}
|
|
1075
1129
|
buildOverlayLines() {
|
|
@@ -1079,7 +1133,9 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1079
1133
|
for (const line of chromeLines) {
|
|
1080
1134
|
lines.push(this.truncateLine(line, maxWidth));
|
|
1081
1135
|
}
|
|
1082
|
-
|
|
1136
|
+
// Simple divider without label (Claude Code style)
|
|
1137
|
+
const dividerWidth = Math.min(maxWidth, 48);
|
|
1138
|
+
const divider = theme.ui.muted('─'.repeat(dividerWidth));
|
|
1083
1139
|
lines.push(this.truncateLine(divider, maxWidth));
|
|
1084
1140
|
const promptIndex = lines.length;
|
|
1085
1141
|
lines.push(this.truncateLine(this.buildInputLine(), maxWidth));
|
|
@@ -1087,10 +1143,11 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1087
1143
|
for (let index = 0; index < this.suggestions.length; index++) {
|
|
1088
1144
|
const suggestion = this.suggestions[index];
|
|
1089
1145
|
const isActive = index === this.suggestionIndex;
|
|
1090
|
-
|
|
1146
|
+
// Use simple arrow marker like Claude Code
|
|
1147
|
+
const marker = isActive ? theme.primary('>') : theme.ui.muted(' ');
|
|
1091
1148
|
const cmdText = isActive ? theme.primary(suggestion.command) : theme.ui.muted(suggestion.command);
|
|
1092
1149
|
const descText = isActive ? suggestion.description : theme.ui.muted(suggestion.description);
|
|
1093
|
-
lines.push(this.truncateLine(
|
|
1150
|
+
lines.push(this.truncateLine(` ${marker} ${cmdText} — ${descText}`, maxWidth));
|
|
1094
1151
|
}
|
|
1095
1152
|
}
|
|
1096
1153
|
if (this.inlinePanel.length > 0) {
|
|
@@ -1123,15 +1180,22 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1123
1180
|
return [];
|
|
1124
1181
|
}
|
|
1125
1182
|
const segments = [];
|
|
1126
|
-
|
|
1183
|
+
// Add animated spinner when streaming for dynamic visual feedback
|
|
1184
|
+
if (this.mode === 'streaming') {
|
|
1185
|
+
const spinnerChars = spinnerFrames.braille;
|
|
1186
|
+
const spinnerChar = spinnerChars[this.spinnerFrame % spinnerChars.length] ?? '⠋';
|
|
1187
|
+
segments.push(`${theme.info(spinnerChar)} ${this.applyTone(statusLabel.text, statusLabel.tone)}`);
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
segments.push(`${theme.ui.muted('status')} ${this.applyTone(statusLabel.text, statusLabel.tone)}`);
|
|
1191
|
+
}
|
|
1127
1192
|
if (this.statusMeta.sessionTime) {
|
|
1128
1193
|
segments.push(`${theme.ui.muted('runtime')} ${theme.ui.muted(this.statusMeta.sessionTime)}`);
|
|
1129
1194
|
}
|
|
1130
1195
|
if (this.statusMeta.contextPercent !== undefined) {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
segments.push(`${theme.ui.muted('ctx')} ${color(`${ctx}%`)}`);
|
|
1196
|
+
// Use animated context meter for smooth color transitions
|
|
1197
|
+
this.contextMeter.update(this.statusMeta.contextPercent);
|
|
1198
|
+
segments.push(this.contextMeter.render());
|
|
1135
1199
|
}
|
|
1136
1200
|
return this.wrapSegments(segments, maxWidth);
|
|
1137
1201
|
}
|
|
@@ -1301,9 +1365,10 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1301
1365
|
buildInputLine() {
|
|
1302
1366
|
if (this.collapsedPaste) {
|
|
1303
1367
|
const summary = `[pasted ${this.collapsedPaste.lines} lines, ${this.collapsedPaste.chars} chars] (Ctrl+L insert, Backspace discard)`;
|
|
1304
|
-
return this.truncateLine(`${theme.primary('
|
|
1368
|
+
return this.truncateLine(`${theme.primary('> ')}${theme.ui.muted(summary)}`, this.safeWidth());
|
|
1305
1369
|
}
|
|
1306
|
-
|
|
1370
|
+
// Claude Code uses simple '>' prompt
|
|
1371
|
+
const prompt = theme.primary('> ');
|
|
1307
1372
|
const promptWidth = this.visibleLength(prompt);
|
|
1308
1373
|
const maxWidth = this.safeWidth();
|
|
1309
1374
|
const available = Math.max(1, maxWidth - promptWidth);
|
|
@@ -1440,17 +1505,6 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1440
1505
|
}
|
|
1441
1506
|
return result;
|
|
1442
1507
|
}
|
|
1443
|
-
clearOverlayRows(rows, startRow) {
|
|
1444
|
-
const totalRows = this.rows || 24;
|
|
1445
|
-
const limit = Math.max(0, Math.min(rows, totalRows));
|
|
1446
|
-
for (let idx = 0; idx < limit; idx++) {
|
|
1447
|
-
const row = startRow + idx;
|
|
1448
|
-
if (row < 1 || row > totalRows)
|
|
1449
|
-
continue;
|
|
1450
|
-
this.write(ESC.TO(row, 1));
|
|
1451
|
-
this.write(ESC.CLEAR_LINE);
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
1508
|
getBuffer() {
|
|
1455
1509
|
return this.buffer;
|
|
1456
1510
|
}
|
|
@@ -1478,6 +1532,31 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1478
1532
|
setModeStatus(status) {
|
|
1479
1533
|
this.updateStatus(status);
|
|
1480
1534
|
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Show a compacting status with animated spinner (Claude Code style)
|
|
1537
|
+
* Uses ✻ character with animation to indicate context compaction in progress
|
|
1538
|
+
*/
|
|
1539
|
+
showCompactingStatus(message) {
|
|
1540
|
+
this.statusMessage = message;
|
|
1541
|
+
if (!this.spinnerInterval) {
|
|
1542
|
+
this.spinnerInterval = setInterval(() => {
|
|
1543
|
+
this.spinnerFrame++;
|
|
1544
|
+
this.renderPrompt();
|
|
1545
|
+
}, 80);
|
|
1546
|
+
}
|
|
1547
|
+
this.renderPrompt();
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Hide the compacting status and stop spinner animation
|
|
1551
|
+
*/
|
|
1552
|
+
hideCompactingStatus() {
|
|
1553
|
+
if (this.spinnerInterval) {
|
|
1554
|
+
clearInterval(this.spinnerInterval);
|
|
1555
|
+
this.spinnerInterval = null;
|
|
1556
|
+
}
|
|
1557
|
+
this.statusMessage = null;
|
|
1558
|
+
this.renderPrompt();
|
|
1559
|
+
}
|
|
1481
1560
|
emitPrompt(content) {
|
|
1482
1561
|
this.pushPromptEvent(content);
|
|
1483
1562
|
}
|
|
@@ -1521,23 +1600,15 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1521
1600
|
const height = this.lastOverlay?.lines.length ?? this.promptHeight ?? 0;
|
|
1522
1601
|
if (height === 0)
|
|
1523
1602
|
return;
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
this.clearOverlayRows(height, startRow);
|
|
1528
|
-
// Keep the padding row clean as well
|
|
1529
|
-
const paddingRow = startRow + height;
|
|
1530
|
-
if (this.overlayBottomPadding > 0 && paddingRow <= totalRows) {
|
|
1531
|
-
this.write(ESC.TO(paddingRow, 1));
|
|
1603
|
+
// Pure inline mode: clear by moving up and clearing lines
|
|
1604
|
+
for (let i = 0; i < height; i++) {
|
|
1605
|
+
this.write('\x1b[A'); // Move up
|
|
1532
1606
|
this.write(ESC.CLEAR_LINE);
|
|
1533
1607
|
}
|
|
1534
|
-
// Move cursor to the bottom ready for new scrollback output
|
|
1535
|
-
this.write(ESC.TO(totalRows, 1));
|
|
1536
|
-
this.lastOverlayHeight = height;
|
|
1537
|
-
this.lastPromptIndex = this.lastOverlay?.promptIndex ?? this.lastPromptIndex;
|
|
1538
1608
|
this.lastOverlay = null;
|
|
1539
|
-
this.overlayInvalidated = true;
|
|
1540
1609
|
this.promptHeight = 0;
|
|
1610
|
+
this.lastOverlayHeight = 0;
|
|
1611
|
+
this.isPromptActive = false;
|
|
1541
1612
|
}
|
|
1542
1613
|
updateTerminalSize() {
|
|
1543
1614
|
if (this.output.isTTY) {
|