Skip to content

Commit 0696655

Browse files
yiliang114qwencoder
andcommitted
fix(web-shell): drop a return location an abandoned hand-over left behind
The remote-workspace-add continuation is one-shot and tab-scoped, but it was only cleared on completion or cancellation. A reload or the browser's Back button abandons the flow without clearing it: App's effect strips the URL marker while the qwen-remote-workspace-return key stays in sessionStorage, so the next Cancel in any Add-workspace dialog -- including a purely local one -- consumed that stale location and navigated the whole shell back to it. Discarding it on a marker-less standalone boot is safe: the key is only ever written by startRemoteWorkspaceAdd immediately before navigateToDaemon, and every navigateToDaemon success path either assigns a URL carrying the marker or replaceStates the marker in before reloading, while a failed switch removes the key again. So a boot without the marker means the hand-over is over. Also aligns the zh copy for workspaceHost.folderOnThisComputer with workspaceHost.thisComputer. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmu7rlkyy52
1 parent 1b5c3d6 commit 0696655

5 files changed

Lines changed: 82 additions & 2 deletions

File tree

‎packages/web-shell/client/App.test.tsx‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17885,6 +17885,42 @@ describe('App session callbacks', () => {
1788517885
window.localStorage.removeItem('qwen-remote-connections');
1788617886
});
1788717887

17888+
it('discards a return location an abandoned hand-over left behind', async () => {
17889+
// The reload that abandoned the flow stripped the marker but not the key,
17890+
// so a standalone boot with no marker must not carry it into the next
17891+
// Add-workspace Cancel.
17892+
const returnUrl = `${window.location.origin}/session/original`;
17893+
window.history.replaceState(null, '', '/session/original');
17894+
window.sessionStorage.setItem('qwen-remote-workspace-return', returnUrl);
17895+
17896+
const view = renderApp({}, undefined, true);
17897+
await flush();
17898+
17899+
expect(window.sessionStorage.getItem('qwen-remote-workspace-return')).toBe(
17900+
null,
17901+
);
17902+
view.unmount();
17903+
window.history.replaceState(null, '', '/');
17904+
});
17905+
17906+
it('keeps the return location of a standalone boot that resumes the hand-over', async () => {
17907+
const returnUrl = `${window.location.origin}/session/original`;
17908+
window.history.replaceState(null, '', '/?addRemoteWorkspace=browse');
17909+
window.sessionStorage.setItem('qwen-remote-workspace-return', returnUrl);
17910+
17911+
const view = renderApp({}, undefined, true);
17912+
await flush();
17913+
17914+
expect(window.sessionStorage.getItem('qwen-remote-workspace-return')).toBe(
17915+
returnUrl,
17916+
);
17917+
expect(
17918+
new URLSearchParams(window.location.search).has('addRemoteWorkspace'),
17919+
).toBe(false);
17920+
view.unmount();
17921+
window.history.replaceState(null, '', '/');
17922+
});
17923+
1788817924
it('keeps the add workspace entry out of an embedded shell without the capability', async () => {
1788917925
mockWorkspace.capabilities = {
1789017926
features: [],

‎packages/web-shell/client/App.tsx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ import { StandaloneContext } from './config/standalone';
265265
import {
266266
clearRemoteWorkspaceAddStep,
267267
completeRemoteWorkspaceAdd,
268+
discardAbandonedRemoteWorkspaceAdd,
268269
getRemoteWorkspaceAddStep,
269270
leaveRemoteWorkspaceAdd,
270271
selectRemoteWorkspaceLocation,
@@ -3657,8 +3658,13 @@ export function App({
36573658
initialRemoteWorkspaceAddStep === 'browse',
36583659
);
36593660
useEffect(() => {
3661+
// No marker on a standalone boot means the hand-over that wrote the return
3662+
// location was abandoned (reload or Back), not resumed. Dropping it here
3663+
// keeps a later Cancel in an unrelated Add-workspace dialog from consuming
3664+
// the stale location and navigating the shell away.
36603665
if (initialRemoteWorkspaceAddStep) clearRemoteWorkspaceAddStep();
3661-
}, [initialRemoteWorkspaceAddStep]);
3666+
else if (standalone) discardAbandonedRemoteWorkspaceAdd();
3667+
}, [initialRemoteWorkspaceAddStep, standalone]);
36623668
const [workspaceMutationBusy, setWorkspaceMutationBusy] = useState(false);
36633669
const workspaceMutationTokenRef = useRef<symbol | null>(null);
36643670
const workspaceSwitchTokenRef = useRef<symbol | null>(null);

‎packages/web-shell/client/config/remote-workspace-add.test.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ vi.mock('./daemon', () => ({
2525
const {
2626
clearRemoteWorkspaceAddStep,
2727
completeRemoteWorkspaceAdd,
28+
discardAbandonedRemoteWorkspaceAdd,
2829
getRemoteWorkspaceAddStep,
2930
leaveRemoteWorkspaceAdd,
3031
selectRemoteWorkspaceLocation,
@@ -129,6 +130,25 @@ describe('remote workspace add navigation', () => {
129130
);
130131
});
131132

133+
it('drops a return location an abandoned hand-over left behind', () => {
134+
// A reload or the Back button abandons the flow: the marker is gone but the
135+
// key survives, and the next Cancel in any Add-workspace dialog — including
136+
// a purely local one — would consume that stale location.
137+
window.sessionStorage.setItem(
138+
'qwen-remote-workspace-return',
139+
`${testOrigin}/session/original`,
140+
);
141+
expect(getRemoteWorkspaceAddStep()).toBeUndefined();
142+
143+
discardAbandonedRemoteWorkspaceAdd();
144+
145+
expect(window.sessionStorage.getItem('qwen-remote-workspace-return')).toBe(
146+
null,
147+
);
148+
expect(leaveRemoteWorkspaceAdd()).toBe(false);
149+
expect(assign).not.toHaveBeenCalled();
150+
});
151+
132152
it('removes only the flow marker from the live URL', () => {
133153
setLocation(
134154
`${testOrigin}/session/current?daemon=https%3A%2F%2Fremote.example&addRemoteWorkspace=browse`,

‎packages/web-shell/client/config/remote-workspace-add.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,21 @@ export function completeRemoteWorkspaceAdd(): void {
110110
// The completed add does not depend on cleaning up its return location.
111111
}
112112
}
113+
114+
/**
115+
* Drops a return location whose flow was abandoned rather than finished.
116+
*
117+
* The key is only ever written immediately before a navigation that carries
118+
* `FLOW_PARAM` (see startRemoteWorkspaceAdd and navigateToDaemon), so booting
119+
* without that marker means the hand-over was abandoned — a reload or the
120+
* browser's Back button strips the dialog but leaves this key behind, and the
121+
* next Cancel in any Add-workspace dialog would consume the stale location and
122+
* navigate the whole shell back to it.
123+
*/
124+
export function discardAbandonedRemoteWorkspaceAdd(): void {
125+
try {
126+
window.sessionStorage.removeItem(RETURN_URL_KEY);
127+
} catch {
128+
// Nothing to discard when storage is unavailable.
129+
}
130+
}

‎packages/web-shell/client/i18n.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5588,7 +5588,7 @@ const ZH: Messages = {
55885588
'workspaceHost.source': '目录来源',
55895589
'workspaceHost.thisComputer': '这台计算机',
55905590
'workspaceHost.folderOn': (vars) => `${vars?.address} 上的目录`,
5591-
'workspaceHost.folderOnThisComputer': '这台电脑上的目录',
5591+
'workspaceHost.folderOnThisComputer': '这台计算机上的目录',
55925592
'workspaceHost.browseHint': '请从下方选择目录,或输入绝对路径。',
55935593
'workspaceHost.parent': '上一级',
55945594
'workspaceHost.addFolder': '添加此文件夹',

0 commit comments

Comments
 (0)