erosolar-cli 2.1.63 → 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 +36 -16
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/ui/UnifiedUIRenderer.d.ts +26 -5
- package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
- package/dist/ui/UnifiedUIRenderer.js +150 -99
- 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;
|
|
@@ -83,7 +89,6 @@ export declare class UnifiedUIRenderer extends EventEmitter {
|
|
|
83
89
|
private hasRenderedPrompt;
|
|
84
90
|
private hasEverRenderedOverlay;
|
|
85
91
|
private lastOverlay;
|
|
86
|
-
private contentRowCount;
|
|
87
92
|
private allowPromptRender;
|
|
88
93
|
private inputCapture;
|
|
89
94
|
constructor(output?: NodeJS.WriteStream, input?: NodeJS.ReadStream, options?: UnifiedUIRendererOptions);
|
|
@@ -132,6 +137,14 @@ export declare class UnifiedUIRenderer extends EventEmitter {
|
|
|
132
137
|
*/
|
|
133
138
|
addCompactBlock(content: string, label?: string): void;
|
|
134
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;
|
|
135
148
|
getMode(): 'idle' | 'streaming';
|
|
136
149
|
updateStatus(message: string | null): void;
|
|
137
150
|
updateStatusBundle(status: {
|
|
@@ -187,12 +200,20 @@ export declare class UnifiedUIRenderer extends EventEmitter {
|
|
|
187
200
|
private visibleLength;
|
|
188
201
|
private stripAnsi;
|
|
189
202
|
private truncateLine;
|
|
190
|
-
private clearOverlayRows;
|
|
191
203
|
getBuffer(): string;
|
|
192
204
|
getCursor(): number;
|
|
193
205
|
setBuffer(text: string, cursorPos?: number): void;
|
|
194
206
|
clearBuffer(): void;
|
|
195
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;
|
|
196
217
|
emitPrompt(content: string): void;
|
|
197
218
|
/**
|
|
198
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,9 +99,8 @@ 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
|
-
contentRowCount = 0; // Track how many rows of content have been written (for flow-based positioning)
|
|
97
104
|
allowPromptRender = true;
|
|
98
105
|
inputCapture = null;
|
|
99
106
|
constructor(output = process.stdout, input = process.stdin, options) {
|
|
@@ -102,6 +109,8 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
102
109
|
this.input = input;
|
|
103
110
|
this.interactive = Boolean(this.output.isTTY && this.input.isTTY && !process.env['CI']);
|
|
104
111
|
this.plainMode = isPlainOutputMode() || !this.interactive;
|
|
112
|
+
// Initialize animated components
|
|
113
|
+
this.contextMeter = new ContextMeter();
|
|
105
114
|
this.rl = readline.createInterface({
|
|
106
115
|
input: this.input,
|
|
107
116
|
output: this.output,
|
|
@@ -148,6 +157,21 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
148
157
|
cleanup() {
|
|
149
158
|
this.cancelInputCapture(new Error('Renderer disposed'));
|
|
150
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();
|
|
151
175
|
if (!this.interactive) {
|
|
152
176
|
this.rl.close();
|
|
153
177
|
return;
|
|
@@ -801,11 +825,6 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
801
825
|
}
|
|
802
826
|
this.output.write(formatted);
|
|
803
827
|
this.lastOutputEndedWithNewline = formatted.endsWith('\n');
|
|
804
|
-
// Track content row count for flow-based overlay positioning
|
|
805
|
-
const newlines = (formatted.match(/\n/g) || []).length;
|
|
806
|
-
this.contentRowCount += newlines + (this.lastOutputEndedWithNewline ? 0 : 1);
|
|
807
|
-
// Overlay must be re-anchored after new scrollback is written
|
|
808
|
-
this.overlayInvalidated = true;
|
|
809
828
|
// Don't re-render prompt after every event - wait for queue to finish
|
|
810
829
|
// This prevents premature prompt rendering that cuts off responses
|
|
811
830
|
}
|
|
@@ -851,10 +870,19 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
851
870
|
case 'prompt':
|
|
852
871
|
return `\n> ${event.content}\n`; // Plain > like Claude Code
|
|
853
872
|
case 'thought': {
|
|
854
|
-
//
|
|
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');
|
|
855
877
|
const lines = event.content.split('\n');
|
|
878
|
+
// Show thinking header with first line, then indent continuation
|
|
856
879
|
const formatted = lines
|
|
857
|
-
.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
|
+
})
|
|
858
886
|
.join('\n');
|
|
859
887
|
return `\n${formatted}\n`;
|
|
860
888
|
}
|
|
@@ -912,6 +940,13 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
912
940
|
setMode(mode) {
|
|
913
941
|
const wasStreaming = this.mode === 'streaming';
|
|
914
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
|
+
}
|
|
915
950
|
if (wasStreaming && mode === 'idle' && !this.lastOutputEndedWithNewline) {
|
|
916
951
|
// Finish streaming on a fresh line so the next prompt/event doesn't collide
|
|
917
952
|
this.write('\n');
|
|
@@ -922,6 +957,31 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
922
957
|
this.renderPrompt();
|
|
923
958
|
}
|
|
924
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
|
+
}
|
|
925
985
|
getMode() {
|
|
926
986
|
return this.mode;
|
|
927
987
|
}
|
|
@@ -1017,13 +1077,9 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1017
1077
|
if (!this.allowPromptRender) {
|
|
1018
1078
|
return;
|
|
1019
1079
|
}
|
|
1020
|
-
// Rich mode: inline
|
|
1080
|
+
// Rich mode: inline rendering (no full-screen overlay)
|
|
1021
1081
|
this.updateTerminalSize();
|
|
1022
1082
|
const maxWidth = this.safeWidth();
|
|
1023
|
-
if (this.lastRenderWidth !== null && maxWidth !== this.lastRenderWidth) {
|
|
1024
|
-
// Terminal resized; force a clean anchor so the overlay doesn't jitter.
|
|
1025
|
-
this.overlayInvalidated = true;
|
|
1026
|
-
}
|
|
1027
1083
|
this.lastRenderWidth = maxWidth;
|
|
1028
1084
|
const overlay = this.buildOverlayLines();
|
|
1029
1085
|
if (!overlay.lines.length) {
|
|
@@ -1035,53 +1091,39 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1035
1091
|
}
|
|
1036
1092
|
let promptIndex = Math.max(0, Math.min(overlay.promptIndex, renderedLines.length - 1));
|
|
1037
1093
|
let height = renderedLines.length;
|
|
1038
|
-
// Keep at least one free line below the overlay so typing always has breathing room
|
|
1039
|
-
const bottomPadding = this.overlayBottomPadding;
|
|
1040
1094
|
const totalRows = this.rows || 24;
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const bottomPinnedStartRow = Math.max(1, availableRows - height + 1);
|
|
1052
|
-
// Use flow-based positioning when content is small, bottom-pinned when content fills screen
|
|
1053
|
-
const startRow = (flowBasedStartRow + height <= availableRows)
|
|
1054
|
-
? flowBasedStartRow
|
|
1055
|
-
: bottomPinnedStartRow;
|
|
1056
|
-
const promptRow = startRow + promptIndex;
|
|
1057
|
-
const promptCol = Math.min(Math.max(1, 3 + this.cursor), this.cols || 80);
|
|
1058
|
-
// Clear any previous overlay footprint (status, prompt, controls) to avoid leaking into scrollback
|
|
1059
|
-
this.clearOverlayRows(height, startRow);
|
|
1060
|
-
if (bottomPadding > 0 && startRow + height <= totalRows) {
|
|
1061
|
-
this.write(ESC.TO(startRow + height, 1));
|
|
1062
|
-
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
|
+
}
|
|
1063
1105
|
}
|
|
1064
|
-
//
|
|
1065
|
-
for (
|
|
1066
|
-
const row = startRow + idx;
|
|
1067
|
-
const line = renderedLines[idx] ?? '';
|
|
1068
|
-
this.write(ESC.TO(row, 1));
|
|
1106
|
+
// Write prompt lines directly to scrollback (no absolute positioning)
|
|
1107
|
+
for (const line of renderedLines) {
|
|
1069
1108
|
this.write(ESC.CLEAR_LINE);
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1109
|
+
this.write(line);
|
|
1110
|
+
this.write('\n');
|
|
1073
1111
|
}
|
|
1074
|
-
// Position cursor at prompt
|
|
1075
|
-
|
|
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
|
|
1076
1120
|
this.cursorVisibleColumn = promptCol;
|
|
1077
1121
|
this.hasRenderedPrompt = true;
|
|
1078
|
-
this.hasEverRenderedOverlay = true;
|
|
1122
|
+
this.hasEverRenderedOverlay = true;
|
|
1079
1123
|
this.isPromptActive = true;
|
|
1080
1124
|
this.lastOverlayHeight = height;
|
|
1081
|
-
this.lastPromptIndex = promptIndex;
|
|
1082
1125
|
this.lastOverlay = { lines: renderedLines, promptIndex };
|
|
1083
|
-
this.
|
|
1084
|
-
this.lastOutputEndedWithNewline = true;
|
|
1126
|
+
this.lastOutputEndedWithNewline = false;
|
|
1085
1127
|
this.promptHeight = height;
|
|
1086
1128
|
}
|
|
1087
1129
|
buildOverlayLines() {
|
|
@@ -1091,7 +1133,9 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1091
1133
|
for (const line of chromeLines) {
|
|
1092
1134
|
lines.push(this.truncateLine(line, maxWidth));
|
|
1093
1135
|
}
|
|
1094
|
-
|
|
1136
|
+
// Simple divider without label (Claude Code style)
|
|
1137
|
+
const dividerWidth = Math.min(maxWidth, 48);
|
|
1138
|
+
const divider = theme.ui.muted('─'.repeat(dividerWidth));
|
|
1095
1139
|
lines.push(this.truncateLine(divider, maxWidth));
|
|
1096
1140
|
const promptIndex = lines.length;
|
|
1097
1141
|
lines.push(this.truncateLine(this.buildInputLine(), maxWidth));
|
|
@@ -1099,10 +1143,11 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1099
1143
|
for (let index = 0; index < this.suggestions.length; index++) {
|
|
1100
1144
|
const suggestion = this.suggestions[index];
|
|
1101
1145
|
const isActive = index === this.suggestionIndex;
|
|
1102
|
-
|
|
1146
|
+
// Use simple arrow marker like Claude Code
|
|
1147
|
+
const marker = isActive ? theme.primary('>') : theme.ui.muted(' ');
|
|
1103
1148
|
const cmdText = isActive ? theme.primary(suggestion.command) : theme.ui.muted(suggestion.command);
|
|
1104
1149
|
const descText = isActive ? suggestion.description : theme.ui.muted(suggestion.description);
|
|
1105
|
-
lines.push(this.truncateLine(
|
|
1150
|
+
lines.push(this.truncateLine(` ${marker} ${cmdText} — ${descText}`, maxWidth));
|
|
1106
1151
|
}
|
|
1107
1152
|
}
|
|
1108
1153
|
if (this.inlinePanel.length > 0) {
|
|
@@ -1135,15 +1180,22 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1135
1180
|
return [];
|
|
1136
1181
|
}
|
|
1137
1182
|
const segments = [];
|
|
1138
|
-
|
|
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
|
+
}
|
|
1139
1192
|
if (this.statusMeta.sessionTime) {
|
|
1140
1193
|
segments.push(`${theme.ui.muted('runtime')} ${theme.ui.muted(this.statusMeta.sessionTime)}`);
|
|
1141
1194
|
}
|
|
1142
1195
|
if (this.statusMeta.contextPercent !== undefined) {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
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());
|
|
1147
1199
|
}
|
|
1148
1200
|
return this.wrapSegments(segments, maxWidth);
|
|
1149
1201
|
}
|
|
@@ -1313,9 +1365,10 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1313
1365
|
buildInputLine() {
|
|
1314
1366
|
if (this.collapsedPaste) {
|
|
1315
1367
|
const summary = `[pasted ${this.collapsedPaste.lines} lines, ${this.collapsedPaste.chars} chars] (Ctrl+L insert, Backspace discard)`;
|
|
1316
|
-
return this.truncateLine(`${theme.primary('
|
|
1368
|
+
return this.truncateLine(`${theme.primary('> ')}${theme.ui.muted(summary)}`, this.safeWidth());
|
|
1317
1369
|
}
|
|
1318
|
-
|
|
1370
|
+
// Claude Code uses simple '>' prompt
|
|
1371
|
+
const prompt = theme.primary('> ');
|
|
1319
1372
|
const promptWidth = this.visibleLength(prompt);
|
|
1320
1373
|
const maxWidth = this.safeWidth();
|
|
1321
1374
|
const available = Math.max(1, maxWidth - promptWidth);
|
|
@@ -1452,17 +1505,6 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1452
1505
|
}
|
|
1453
1506
|
return result;
|
|
1454
1507
|
}
|
|
1455
|
-
clearOverlayRows(rows, startRow) {
|
|
1456
|
-
const totalRows = this.rows || 24;
|
|
1457
|
-
const limit = Math.max(0, Math.min(rows, totalRows));
|
|
1458
|
-
for (let idx = 0; idx < limit; idx++) {
|
|
1459
|
-
const row = startRow + idx;
|
|
1460
|
-
if (row < 1 || row > totalRows)
|
|
1461
|
-
continue;
|
|
1462
|
-
this.write(ESC.TO(row, 1));
|
|
1463
|
-
this.write(ESC.CLEAR_LINE);
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
1508
|
getBuffer() {
|
|
1467
1509
|
return this.buffer;
|
|
1468
1510
|
}
|
|
@@ -1490,6 +1532,31 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1490
1532
|
setModeStatus(status) {
|
|
1491
1533
|
this.updateStatus(status);
|
|
1492
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
|
+
}
|
|
1493
1560
|
emitPrompt(content) {
|
|
1494
1561
|
this.pushPromptEvent(content);
|
|
1495
1562
|
}
|
|
@@ -1533,31 +1600,15 @@ export class UnifiedUIRenderer extends EventEmitter {
|
|
|
1533
1600
|
const height = this.lastOverlay?.lines.length ?? this.promptHeight ?? 0;
|
|
1534
1601
|
if (height === 0)
|
|
1535
1602
|
return;
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
const availableRows = Math.max(1, totalRows - bottomPadding);
|
|
1540
|
-
// Use same flow-based positioning logic as renderPrompt for consistency
|
|
1541
|
-
const contentEndRow = Math.min(this.contentRowCount + 1, totalRows);
|
|
1542
|
-
const flowBasedStartRow = contentEndRow;
|
|
1543
|
-
const bottomPinnedStartRow = Math.max(1, availableRows - height + 1);
|
|
1544
|
-
const startRow = (flowBasedStartRow + height <= availableRows)
|
|
1545
|
-
? flowBasedStartRow
|
|
1546
|
-
: bottomPinnedStartRow;
|
|
1547
|
-
this.clearOverlayRows(height, startRow);
|
|
1548
|
-
// Keep the padding row clean as well
|
|
1549
|
-
const paddingRow = startRow + height;
|
|
1550
|
-
if (this.overlayBottomPadding > 0 && paddingRow <= totalRows) {
|
|
1551
|
-
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
|
|
1552
1606
|
this.write(ESC.CLEAR_LINE);
|
|
1553
1607
|
}
|
|
1554
|
-
// Move cursor to the bottom ready for new scrollback output
|
|
1555
|
-
this.write(ESC.TO(totalRows, 1));
|
|
1556
|
-
this.lastOverlayHeight = height;
|
|
1557
|
-
this.lastPromptIndex = this.lastOverlay?.promptIndex ?? this.lastPromptIndex;
|
|
1558
1608
|
this.lastOverlay = null;
|
|
1559
|
-
this.overlayInvalidated = true;
|
|
1560
1609
|
this.promptHeight = 0;
|
|
1610
|
+
this.lastOverlayHeight = 0;
|
|
1611
|
+
this.isPromptActive = false;
|
|
1561
1612
|
}
|
|
1562
1613
|
updateTerminalSize() {
|
|
1563
1614
|
if (this.output.isTTY) {
|