diff --git a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts index 445814ef38..71ecd0e0ea 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts @@ -785,6 +785,9 @@ export class AIChatManager implements ChatViewHost { /** Use, Plan or Build. A session keeps its own: the runtime seeds it from the session record * and stores each change through `onWorkModeChanged`. */ workMode = $state(AIWorkMode.BUILD) + /** The session is moving into a fork. Its editors stay closed meanwhile: an edit would save a + * draft in the workspace being left, after the move listed what it brings along. */ + moving = $state(false) onWorkModeChanged: ((mode: AIWorkMode) => void) | undefined = undefined workModesAvailable = $derived(this.isSessionChat && supportsPlanMode(this.mode)) /** Use is the only mode: the workspace is protected, or the user's role builds nothing in it. */ diff --git a/frontend/src/lib/components/sessions/SessionWrapper.svelte b/frontend/src/lib/components/sessions/SessionWrapper.svelte index bd9dcfb075..d1844171ed 100644 --- a/frontend/src/lib/components/sessions/SessionWrapper.svelte +++ b/frontend/src/lib/components/sessions/SessionWrapper.svelte @@ -541,6 +541,7 @@ return } moving = true + if (runtime) runtime.manager.moving = true const releaseAutoResume = runtime?.manager.holdAutoResume() // Bracketed like a turn, so another tab on this session cannot send while its drafts // move, then re-reads the session (its new workspace, the model note) once it ends. @@ -614,6 +615,7 @@ sendUserToast(`Session moved to ${req.targetName}`) } finally { moving = false + if (runtime) runtime.manager.moving = false releaseAutoResume?.() if (chatId) localRunEnded(session.id, runtime?.manager.historyManager.getCurrentChatId() ?? chatId) diff --git a/frontend/src/lib/components/sessions/sessionPermissions.svelte.ts b/frontend/src/lib/components/sessions/sessionPermissions.svelte.ts index 0db9ac09e7..b8b8476f56 100644 --- a/frontend/src/lib/components/sessions/sessionPermissions.svelte.ts +++ b/frontend/src/lib/components/sessions/sessionPermissions.svelte.ts @@ -42,7 +42,7 @@ export function useSessionReadOnly(workspace: () => string | undefined) { } /** The session chat whose work mode the session UI follows. */ -type WorkModeHost = { readonly workMode: AIWorkMode } +type WorkModeHost = { readonly workMode: AIWorkMode; readonly moving?: boolean } /** * What the session UI may offer in `workspace`. Its editors save drafts, so it reads @@ -51,7 +51,7 @@ type WorkModeHost = { readonly workMode: AIWorkMode } * resolves: hiding every editor from a developer while `whoami` is in flight is the worse * mistake, and the server refuses an operator anyway. A session held in Use opens no editor: one * in a protected workspace, one whose role builds nothing there, or one whose chat is in Use - * mode — the chat `chat` returns, else the `aiChatManager` context's. + * mode — the chat `chat` returns, else the `aiChatManager` context's — and one moving into a fork. * Reads context and registers an effect: call during component initialisation. */ export function useSessionPermissions( @@ -62,10 +62,12 @@ export function useSessionPermissions( const rights = useEditRights(workspace) const user = useActingUser(workspace) const protectedWorkspace = useSessionReadOnly(workspace) + const host = $derived(chat?.() ?? contextChat) const readOnly = $derived( protectedWorkspace.current || !buildsAnything(rights.capabilities) || - (chat?.() ?? contextChat)?.workMode === AIWorkMode.USE + host?.workMode === AIWorkMode.USE || + !!host?.moving ) const workspaces = fromStore(userWorkspaces) const entry = $derived(workspaces.current.find((w) => w.id === workspace()))