Skip to content

Commit 1c818f1

Browse files
cursoragentBob Corbin
andcommitted
Select the new session before /new clears the composer.
Sending /new used to empty the prompt immediately, then activate the session and only then write shadow:selected. The e2e contract — and the UI — treat an empty composer as "already on the new task". Commit the selected id when createSession returns, skip persisting /new as a draft, and let openSession clear the composer so selection is in place before the prompt looks idle. Co-authored-by: Bob Corbin <ShadowfetchLinux@users.noreply.github.com>
1 parent 92f05f8 commit 1c818f1

2 files changed

Lines changed: 18 additions & 13 deletions

File tree

‎ui/e2e/workspace.spec.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,11 @@ test("new-task shortcuts and deletion keep task selection usable", async ({
201201
await prompt.fill("/new");
202202
await page.getByRole("button", { name: "Send task", exact: true }).click();
203203
await expect(prompt).toHaveValue("");
204-
await expect(page.locator(".loading-task")).toHaveCount(0);
205-
await expect
206-
.poll(() => page.evaluate(() => localStorage.getItem("shadow:selected")))
207-
.not.toBe(first);
208204
const toDelete = await page.evaluate(() =>
209205
localStorage.getItem("shadow:selected"),
210206
);
207+
expect(toDelete).not.toBe(first);
208+
await expect(page.locator(".loading-task")).toHaveCount(0);
211209
await page.keyboard.press("Control+k");
212210
await page
213211
.getByRole("textbox", { name: "Search commands" })

‎ui/src/App.tsx‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ const formatTokens = (n: number) =>
5555
n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
5656
const draftKey = (id: string, workspace: string) =>
5757
`shadow:draft:${id || workspace}`;
58+
const isSessionCommand = (text: string) =>
59+
["/new", "/clear"].includes(text.trim());
5860

5961
export default function App() {
6062
const [ready, setReady] = useState(false);
@@ -111,6 +113,7 @@ export default function App() {
111113
const selectedRef = useRef("");
112114
const selection = useRef(0);
113115
const submittingRef = useRef(false);
116+
const taskRef = useRef("");
114117
const toastSeq = useRef(0);
115118
const booted = useRef(false);
116119
const activationQueue = useRef<Promise<unknown>>(Promise.resolve());
@@ -156,6 +159,7 @@ export default function App() {
156159
});
157160
const { transcript, setTranscript, job, busy, connection } = conversation;
158161
const locked = busy || submitting || switching;
162+
taskRef.current = task;
159163

160164
async function reloadConfig() {
161165
const [config, state, modelData, providerData] = await Promise.all([
@@ -173,9 +177,11 @@ export default function App() {
173177
async function openSession(id: string) {
174178
if (submittingRef.current) return;
175179
if (selectedRef.current) {
176-
if (task)
177-
localStorage.setItem(draftKey(selectedRef.current, workspace), task);
178-
else localStorage.removeItem(draftKey(selectedRef.current, workspace));
180+
const draft = taskRef.current;
181+
if (draft && !isSessionCommand(draft))
182+
localStorage.setItem(draftKey(selectedRef.current, workspace), draft);
183+
else if (!draft)
184+
localStorage.removeItem(draftKey(selectedRef.current, workspace));
179185
}
180186
const ticket = ++selection.current;
181187
setSwitching(true);
@@ -255,6 +261,7 @@ export default function App() {
255261
useEffect(() => {
256262
const key = draftKey(sessionId, workspace);
257263
const timer = setTimeout(() => {
264+
if (isSessionCommand(task)) return;
258265
if (task) localStorage.setItem(key, task);
259266
else localStorage.removeItem(key);
260267
}, 200);
@@ -303,14 +310,15 @@ export default function App() {
303310
return () => clearInterval(timer);
304311
}, [job?.id, busy]);
305312

306-
async function newSession() {
307-
if (submitting || switching) return;
313+
async function newSession(opts?: { force?: boolean }) {
314+
if (!opts?.force && (submitting || switching)) return;
308315
if (!workspace) {
309316
setOverlay("project");
310317
return;
311318
}
312319
try {
313320
const created = await api.createSession(workspace, "New task");
321+
localStorage.setItem("shadow:selected", created.id);
314322
await openSession(created.id);
315323
promptRef.current?.focus();
316324
} catch (e) {
@@ -369,7 +377,7 @@ export default function App() {
369377
const [name, ...rest] = text.slice(1).split(" ");
370378
const args = rest.join(" ");
371379
if (name === "new" || name === "clear") {
372-
await newSession();
380+
await newSession({ force: true });
373381
return;
374382
}
375383
const panels: Record<string, DrawerTab> = {
@@ -397,9 +405,8 @@ export default function App() {
397405
async function submit() {
398406
if (locked || submittingRef.current || (!task.trim() && !chips.length))
399407
return;
400-
if (["/new", "/clear"].includes(task.trim())) {
401-
setTask("");
402-
await newSession();
408+
if (isSessionCommand(task)) {
409+
await newSession({ force: true });
403410
return;
404411
}
405412
const original = task;

0 commit comments

Comments
 (0)