Fix minimal TUI submitted-line duplicate appearance
This commit is contained in:
+13
-1
@@ -3,6 +3,18 @@
|
||||
"updated_at": "2026-02-23",
|
||||
"description": "Tracks the status of all Flynn plans and implementation phases",
|
||||
"plans": {
|
||||
"minimal-tui-submitted-line-dedupe": {
|
||||
"status": "completed",
|
||||
"date": "2026-02-23",
|
||||
"updated": "2026-02-23",
|
||||
"summary": "Fixed duplicate-looking user messages in minimal TUI by clearing the just-submitted readline prompt line before rendering the timestamped `You` block, so sent content is shown once in the conversation transcript.",
|
||||
"files_modified": [
|
||||
"src/frontends/tui/minimal.ts",
|
||||
"src/frontends/tui/minimal.test.ts",
|
||||
"docs/plans/state.json"
|
||||
],
|
||||
"test_status": "pnpm test:run src/frontends/tui/minimal.test.ts passing"
|
||||
},
|
||||
"dashboard-local-backend-update-actions": {
|
||||
"status": "completed",
|
||||
"date": "2026-02-23",
|
||||
@@ -6010,7 +6022,7 @@
|
||||
}
|
||||
},
|
||||
"overall_progress": {
|
||||
"total_test_count": 1941,
|
||||
"total_test_count": 1942,
|
||||
"all_tests_passing": true,
|
||||
"p0_completion": "3/3 (100%)",
|
||||
"p1_completion": "4/4 (100%)",
|
||||
|
||||
@@ -39,6 +39,7 @@ function minimalTuiPrivates(value: MinimalTui): {
|
||||
handleCommand: (command: unknown) => Promise<void>;
|
||||
handleEscapeAction: () => boolean;
|
||||
handleCtrlCPress: (nowMs?: number) => boolean;
|
||||
clearSubmittedPromptLine: () => boolean;
|
||||
prompt: (text: string) => Promise<string>;
|
||||
rl: {
|
||||
once: (event: string, cb: () => void) => void;
|
||||
@@ -61,6 +62,7 @@ function minimalTuiPrivates(value: MinimalTui): {
|
||||
handleCommand: (command: unknown) => Promise<void>;
|
||||
handleEscapeAction: () => boolean;
|
||||
handleCtrlCPress: (nowMs?: number) => boolean;
|
||||
clearSubmittedPromptLine: () => boolean;
|
||||
prompt: (text: string) => Promise<string>;
|
||||
rl: {
|
||||
once: (event: string, cb: () => void) => void;
|
||||
@@ -428,6 +430,46 @@ describe('MinimalTui backend command', () => {
|
||||
});
|
||||
|
||||
describe('MinimalTui prompt cancellation', () => {
|
||||
it('omits leading newline when submitted prompt line was cleared', async () => {
|
||||
const mockSession = {
|
||||
id: 'test',
|
||||
getHistory: () => [],
|
||||
addMessage: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
replaceHistory: vi.fn(),
|
||||
};
|
||||
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
||||
try {
|
||||
const mockAgent = {
|
||||
process: vi.fn(async () => 'ok'),
|
||||
};
|
||||
|
||||
const tui = new MinimalTui({
|
||||
session: asSession(mockSession),
|
||||
modelClient: asRouter({}),
|
||||
agent: asAgent(mockAgent),
|
||||
systemPrompt: 'test',
|
||||
});
|
||||
|
||||
const clearSpy = vi.fn(() => true);
|
||||
minimalTuiPrivates(tui).clearSubmittedPromptLine = clearSpy;
|
||||
|
||||
await minimalTuiPrivates(tui).handleCommand({ type: 'message', content: 'hello' });
|
||||
|
||||
expect(clearSpy).toHaveBeenCalledOnce();
|
||||
const userHeader = writeSpy.mock.calls
|
||||
.map(([chunk]) => String(chunk))
|
||||
.find((chunk) => chunk.includes('You'));
|
||||
expect(userHeader).toBeDefined();
|
||||
expect(userHeader?.startsWith('\n')).toBe(false);
|
||||
} finally {
|
||||
writeSpy.mockRestore();
|
||||
logSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('cancels an active prompt without closing the TUI', async () => {
|
||||
const mockSession = {
|
||||
id: 'test',
|
||||
|
||||
@@ -237,6 +237,21 @@ export class MinimalTui {
|
||||
}
|
||||
}
|
||||
|
||||
private clearSubmittedPromptLine(): boolean {
|
||||
if (!process.stdout.isTTY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
readline.moveCursor(process.stdout, 0, -1);
|
||||
readline.clearLine(process.stdout, 0);
|
||||
readline.cursorTo(process.stdout, 0);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private handleToolEvent(event: ToolUseEvent): void {
|
||||
if (!this.verbose) {
|
||||
return;
|
||||
@@ -1309,9 +1324,10 @@ export class MinimalTui {
|
||||
}
|
||||
|
||||
private async handleMessage(content: string): Promise<void> {
|
||||
const clearedPromptLine = this.clearSubmittedPromptLine();
|
||||
const userTimestamp = formatMessageTimestampParts(Date.now());
|
||||
process.stdout.write(
|
||||
`\n${colors.blue}${colors.bold}You${colors.reset} ${colors.gray}[${userTimestamp.date} | ${userTimestamp.time}]${colors.reset}\n`,
|
||||
`${clearedPromptLine ? '' : '\n'}${colors.blue}${colors.bold}You${colors.reset} ${colors.gray}[${userTimestamp.date} | ${userTimestamp.time}]${colors.reset}\n`,
|
||||
);
|
||||
process.stdout.write(`${content}\n`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user