Skip to content

Commit da9f099

Browse files
committed
feat: move the resume hint out of the exit summary box
1 parent 05de64c commit da9f099

5 files changed

Lines changed: 67 additions & 32 deletions

File tree

packages/cli/src/tests/exit-summary.test.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { test } from "node:test";
22
import assert from "node:assert/strict";
3-
import { buildExitSummaryText } from "../ui";
3+
import { buildExitSummaryText, buildResumeHintText } from "../ui";
44
import type { ModelUsage, SessionEntry } from "@vegamo/deepcode-core";
55

66
const stripAnsi = (text: string): string => text.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, "");
@@ -90,7 +90,7 @@ test("buildExitSummaryText does not derive usage rows from legacy aggregate usag
9090
assert.doesNotMatch(summary, /11,966/);
9191
});
9292

93-
test("buildExitSummaryText shows resume hint when sessionId is provided", () => {
93+
test("buildExitSummaryText does not show resume hint when sessionId is provided", () => {
9494
const sessionId = "0a5cb7a5-c39d-4c39-a11b-05f8b22b8df6";
9595
const summary = stripAnsi(
9696
buildExitSummaryText({
@@ -100,8 +100,8 @@ test("buildExitSummaryText shows resume hint when sessionId is provided", () =>
100100
);
101101

102102
assert.match(summary, /Goodbye!/);
103-
assert.match(summary, /deepcode --resume 0a5cb7a5-c39d-4c39-a11b-05f8b22b8df6/);
104-
assert.match(summary, /To continue this session/);
103+
assert.doesNotMatch(summary, /deepcode --resume 0a5cb7a5-c39d-4c39-a11b-05f8b22b8df6/);
104+
assert.doesNotMatch(summary, /To continue this session/);
105105
});
106106

107107
test("buildExitSummaryText does not show resume hint when sessionId is omitted", () => {
@@ -116,7 +116,7 @@ test("buildExitSummaryText does not show resume hint when sessionId is omitted",
116116
assert.doesNotMatch(summary, /To continue this session/);
117117
});
118118

119-
test("buildExitSummaryText shows resume hint with null session", () => {
119+
test("buildExitSummaryText does not show resume hint with null session", () => {
120120
const summary = stripAnsi(
121121
buildExitSummaryText({
122122
session: null,
@@ -125,7 +125,18 @@ test("buildExitSummaryText shows resume hint with null session", () => {
125125
);
126126

127127
assert.match(summary, /Goodbye!/);
128-
assert.match(summary, /deepcode --resume test-session-id/);
128+
assert.doesNotMatch(summary, /deepcode --resume test-session-id/);
129+
assert.doesNotMatch(summary, /To continue this session/);
130+
});
131+
132+
test("buildResumeHintText shows resume command when sessionId is provided", () => {
133+
const hint = stripAnsi(buildResumeHintText("0a5cb7a5-c39d-4c39-a11b-05f8b22b8df6") ?? "");
134+
135+
assert.equal(hint, "To continue this session, run deepcode --resume 0a5cb7a5-c39d-4c39-a11b-05f8b22b8df6");
136+
});
137+
138+
test("buildResumeHintText returns null when sessionId is omitted", () => {
139+
assert.equal(buildResumeHintText(), null);
129140
});
130141

131142
function buildSession(usage: ModelUsage | null, usagePerModel: Record<string, ModelUsage> | null = null): SessionEntry {

packages/cli/src/ui/exit-summary.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function extractUsageFields(usage: ModelUsage | null): UsageFields {
6868
}
6969

7070
export function buildExitSummaryText(input: ExitSummaryInput): string {
71-
const { session, sessionId } = input;
71+
const { session } = input;
7272

7373
const innerWidth = 98;
7474
const contentWidth = innerWidth - 4; // "│ " prefix + " │" suffix → 4 chars padding
@@ -135,13 +135,6 @@ export function buildExitSummaryText(input: ExitSummaryInput): string {
135135

136136
rows.push("");
137137

138-
if (sessionId) {
139-
const resumeHint =
140-
chalk.dim(`To continue this session, run `) + chalk.hex("#229ac3")(`deepcode --resume ${sessionId}`);
141-
rows.push(resumeHint);
142-
rows.push("");
143-
}
144-
145138
const border = borderColor("─".repeat(innerWidth));
146139
const top = `${borderColor("╭")}${border}${borderColor("╮")}`;
147140
const bottom = `${borderColor("╰")}${border}${borderColor("╯")}`;
@@ -150,3 +143,10 @@ export function buildExitSummaryText(input: ExitSummaryInput): string {
150143

151144
return [top, body, bottom].join("\n");
152145
}
146+
147+
export function buildResumeHintText(sessionId?: string): string | null {
148+
if (!sessionId) {
149+
return null;
150+
}
151+
return chalk.dim(`To continue this session, run `) + chalk.hex("#229ac3")(`deepcode --resume ${sessionId}`);
152+
}

packages/cli/src/ui/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,4 @@ export {
9090
type FileMentionToken,
9191
} from "./core/file-mentions";
9292
export { findExpandedThinkingId, isCollapsedThinking } from "./core/thinking-state";
93-
export { buildExitSummaryText } from "./exit-summary";
93+
export { buildExitSummaryText, buildResumeHintText } from "./exit-summary";

packages/cli/src/ui/views/App.tsx

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
formatAskUserQuestionAnswers,
2121
} from "../core/ask-user-question";
2222
import { PermissionPrompt, type PermissionPromptResult } from "./PermissionPrompt";
23-
import { buildExitSummaryText } from "../exit-summary";
23+
import { buildExitSummaryText, buildResumeHintText } from "../exit-summary";
2424
import { RawMode, useRawModeContext } from "../contexts";
2525
import { renderMessageToStdout } from "../components/MessageView/utils";
2626
import {
@@ -290,22 +290,40 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
290290
}, [sessionManager]);
291291

292292
writeRef.current = write;
293-
const handlePrompt = useCallback(
294-
async (submission: PromptSubmission) => {
295-
if (submission.command === "exit") {
296-
setIsExiting(true);
297-
setTimeout(() => {
298-
const activeSessionId = sessionManager.getActiveSessionId();
299-
const session = activeSessionId ? sessionManager.getSession(activeSessionId) : null;
300-
const summary = buildExitSummaryText({ session, sessionId: activeSessionId ?? undefined });
301-
process.stdout.write("\n");
293+
const handleExit = useCallback(
294+
({ showCommand, showSummary }: { showCommand: boolean; showSummary: boolean }) => {
295+
setIsExiting(true);
296+
setTimeout(() => {
297+
const activeSessionId = sessionManager.getActiveSessionId();
298+
const session = activeSessionId ? sessionManager.getSession(activeSessionId) : null;
299+
const resumeHint = buildResumeHintText(activeSessionId ?? undefined);
300+
301+
process.stdout.write("\n");
302+
if (showCommand) {
302303
process.stdout.write(chalk.rgb(34, 154, 195)("> /exit "));
303304
process.stdout.write("\n\n");
305+
}
306+
if (showSummary) {
307+
const summary = buildExitSummaryText({ session, sessionId: activeSessionId ?? undefined });
304308
process.stdout.write(summary);
305309
process.stdout.write("\n\n");
306-
sessionManager.dispose();
307-
exit();
308-
}, 0);
310+
}
311+
if (resumeHint) {
312+
process.stdout.write(resumeHint);
313+
process.stdout.write("\n");
314+
}
315+
316+
sessionManager.dispose();
317+
exit();
318+
}, 0);
319+
},
320+
[exit, sessionManager]
321+
);
322+
323+
const handlePrompt = useCallback(
324+
async (submission: PromptSubmission) => {
325+
if (submission.command === "exit") {
326+
handleExit({ showCommand: true, showSummary: true });
309327
return;
310328
}
311329
if (submission.command === "new") {
@@ -400,7 +418,7 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
400418
[
401419
sessionManager,
402420
pendingPermissionReply,
403-
exit,
421+
handleExit,
404422
onRestart,
405423
refreshSkills,
406424
refreshSessionsList,
@@ -477,6 +495,10 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
477495
[handlePrompt]
478496
);
479497

498+
const handleExitShortcut = useCallback(() => {
499+
handleExit({ showCommand: false, showSummary: false });
500+
}, [handleExit]);
501+
480502
const reloadActiveSessionView = useCallback(
481503
(sessionId: string): void => {
482504
resetStaticView(loadVisibleMessages(sessionManager, sessionId), { clearScreen: true });
@@ -959,6 +981,7 @@ function App({ projectRoot, initialPrompt, resumeSessionId, onRestart }: AppProp
959981
onRawModeChange={handleRawModeChange}
960982
onInterrupt={handleInterrupt}
961983
onToggleProcessStdout={handleToggleProcessStdout}
984+
onExitShortcut={handleExitShortcut}
962985
placeholder="Type your message..."
963986
statusLineSegments={statusLineSegments}
964987
statusLineSeparator={resolvedSettings.statusline.separator}

packages/cli/src/ui/views/PromptInput.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useEffect, useMemo, useRef, useState } from "react";
2-
import { Box, Text, useApp, useStdout } from "ink";
2+
import { Box, Text, useStdout } from "ink";
33
import type { DOMElement } from "ink";
44
import chalk from "chalk";
55
import { ARGS_SEPARATOR } from "../constants";
@@ -101,6 +101,7 @@ type Props = {
101101
onRawModeChange?: (mode: string) => void;
102102
onInterrupt: () => void;
103103
onToggleProcessStdout?: () => void;
104+
onExitShortcut?: () => void;
104105
};
105106

106107
const PROMPT_PREFIX_WIDTH = 2;
@@ -132,9 +133,9 @@ export const PromptInput = React.memo(function PromptInput({
132133
onModelConfigChange,
133134
onInterrupt,
134135
onToggleProcessStdout,
136+
onExitShortcut,
135137
onRawModeChange,
136138
}: Props): React.ReactElement {
137-
const { exit } = useApp();
138139
const { stdout } = useStdout();
139140
const inputTextRef = useRef<DOMElement | null>(null);
140141
const [buffer, setBuffer] = useState<PromptBufferState>(EMPTY_BUFFER);
@@ -357,7 +358,7 @@ export const PromptInput = React.memo(function PromptInput({
357358
}
358359
const now = Date.now();
359360
if (pendingExit && now - lastCtrlDAt.current < 2000) {
360-
exit();
361+
onExitShortcut?.();
361362
return;
362363
}
363364
lastCtrlDAt.current = now;

0 commit comments

Comments
 (0)