Editing an artifact and switching to another conversation silently discards the unsaved changes. Returning to the original conversation restores the saved file, rather than the draft the user was working on. This happens during ordinary sidebar navigation, even though the artifact editor already has an unsaved-change indicator and a browser-unload warning.
The fix is available for review in #6700.
Before you start
Affected area
The affected area is the frontend artifact editor and its React state lifecycle. The file on the server remains unchanged; the lost content is the user's unsaved local edit.
What happens
Consider a completed report at /mnt/user-data/outputs/report.txt in conversation A. Its saved content is Original saved report. The user opens the artifact, selects Edit, and replaces that text with My revised report, without selecting Save. While checking another conversation, they click conversation B in the sidebar and then return to A.
The editor now shows Original saved report again. The local revision has disappeared, and neither navigation step asks the user to discard it. Opening the artifact again does not recover it. This is especially easy to encounter when comparing generated documents, consulting another conversation, or briefly opening another workspace page while editing.
The unsaved-change warning creates a reasonable expectation that unfinished work is protected. It currently protects a document unload, such as a refresh, but following a Next.js link keeps the browser document alive. The user therefore takes an ordinary application navigation action and loses work without encountering the warning.
Steps to reproduce
Use a normal workspace session with two existing conversations. Conversation A should contain a completed, editable UTF-8 text artifact under /mnt/user-data/outputs, such as report.txt or report.md, and its run should be idle.
- Open conversation A and select the artifact from its file card or artifact panel.
- Select Edit and make an easily recognizable change, such as appending
UNSAVED NAVIGATION TEST.
- Leave the change unsaved. Confirm that the artifact has an unsaved draft.
- Follow conversation B's link in the sidebar. If selecting the artifact collapsed the sidebar, expand it first.
- Follow conversation A's link in the sidebar and reopen the same artifact if necessary.
- Observe that the marker is gone and the editor shows the saved version. No discard confirmation was shown during navigation.
The important trigger is an internal route transition. Refreshing the page is a different lifecycle event and may invoke beforeunload; it does not reproduce the missing SPA protection by itself.
Expected behavior
An unsaved draft should remain associated with its conversation and file until the user saves or discards it, while they navigate within the same application session and account. Returning to A should show My revised report, and editing the same relative file path in B should have its own draft.
Save should continue to be an explicit server write, using the revision that established the editing baseline. Discard changes should continue to restore the appropriate saved content. Navigation should not become an implicit discard action.
The account boundary matters when extending the lifetime of this state. Signing out or changing accounts should clear the previous account's drafts. A subsequent login should not recover content from the previous authenticated session, including when the same account signs in again after an intervening account change.
Keeping drafts in tab memory is sufficient for this issue. Refreshing or closing the document can continue to use the browser's existing unsaved-change warning. Durable draft recovery after a reload would require a separate persistence policy and is not needed to fix conversation switching.
Root cause
At the affected baseline, ArtifactsProvider owns drafts in a local useState<Record<string, ArtifactDraftState>>({}). Its pathname hydration effect restores the artifact list, selected artifact, and panel visibility for the incoming route, then calls setDrafts({}).
That mixes two different kinds of state. Restoring the incoming conversation's panel is appropriate on a pathname change. Clearing the outgoing conversation's unfinished file edits is not. When the same provider instance survives the transition, the hydration effect alone is enough to lose the draft.
There is also an independent unmount path. ChatProviders mounts the artifact provider within the chat UI. When that route subtree is replaced, its local state is destroyed. Removing the pathname reset would address the first path but would leave this second path unresolved.
The existing session-storage payload contains panel state: artifact paths, the selected path, and whether the panel is open. It contains no draft contents. Consequently, restoring the panel after a route transition or refresh cannot reconstruct the discarded edit. A restored panel can make the loss less obvious because the same artifact is selected again, but the selected file is read from its saved content.
The existing beforeunload effect is attached to that route-local provider and inspects only its own drafts. Next.js navigation does not unload the document, and unmounting the provider also removes its listener. A reliable fix therefore needs a longer-lived draft owner and an unload check that can still see drafts belonging to inactive routes.
Reproduction evidence and environment
The defect was reproduced against upstream commit 78540ac0e4622caa42b2c4d2a51a91059422e2f9, audited on October 11, 2026. The investigation used Windows 11 / AMD64, PowerShell, Node.js 24.11.1, and the repository-pinned pnpm 10.26.2.
A React regression exercised the production provider with controlled pathname, sidebar, and environment boundaries. Both conversation switching and replacing the route-local provider reproduced the loss:
A -> B -> A:
expected: "unsaved edit"
received: "no draft"
Route provider unmount -> remount:
expected: "unsaved edit"
received: "no draft"
An additional production-provider harness recorded:
{"discardedOnRouteChange":true,"recoveredOnReturn":false,"beforeUnloadEvents":0}
The absence of an unload event is consistent with the application staying in the same browser document while its React route state changes. The regression captures the loss directly, rather than relying on a particular generated report or model response.
Proposed resolution and existing work
PR #6700 implements a focused fix by separating draft ownership from route-local panel ownership. The intended design is a React draft provider mounted in the root layout, with the current account as its owner and a separate draft map for each route. Individual artifact providers continue to manage the panel and editing mode, but read and update their route's drafts through this longer-lived provider.
The root layout is the useful lifetime here because both chat subtrees and the workspace/auth layout can unmount during internal navigation. Returning to an existing route can retrieve its draft without writing file bodies to browser storage. The unload warning can inspect all retained route maps, so a dirty draft remains protected even while its original chat is inactive.
Extending the lifetime also requires explicit account handling. Binding the store to the authenticated user, clearing it on an owner change or logout, and filtering reads by the current owner prevents a route from showing another account's retained content. Update callbacks also need to carry the account identity and a binding generation. The generation distinguishes two separate visits by account A around an A → B → A transition, allowing late callbacks from the earlier session to be ignored.
An unavailable Gateway auth probe must remain distinct from a confirmed logout. Preserve the store and unload warning during that unknown state, hide its contents until the account is confirmed, and clear it only on a definite anonymous verdict or account change. The owner-binding hook should subscribe to a stable setter so editor updates do not rerender unrelated auth consumers.
The retained draft should include its original content and SHA-256 baseline, not just the edited text. Existing remote-refresh reconciliation can then preserve a dirty draft and report a conflict if the agent has changed the file while the user was elsewhere. Save and Discard keep their existing behavior; the fix changes where drafts live, rather than the artifact write API.
Related inline-editing work was checked in #4595 / #4596, along with complete-content baseline fixes in #6031 / #6036. Those changes address editing and revision safety; this report concerns losing the local draft during route changes and provider unmounts. The linked PR includes navigation, account-lifecycle, Save/Discard, and late-callback regressions. Its browser verification uses the production application with controlled API routes; home-return and account-boundary behavior are covered by React lifecycle tests.
Editing an artifact and switching to another conversation silently discards the unsaved changes. Returning to the original conversation restores the saved file, rather than the draft the user was working on. This happens during ordinary sidebar navigation, even though the artifact editor already has an unsaved-change indicator and a browser-unload warning.
The fix is available for review in #6700.
Before you start
78540ac0e4622caa42b2c4d2a51a91059422e2f9.Affected area
The affected area is the frontend artifact editor and its React state lifecycle. The file on the server remains unchanged; the lost content is the user's unsaved local edit.
What happens
Consider a completed report at
/mnt/user-data/outputs/report.txtin conversation A. Its saved content isOriginal saved report. The user opens the artifact, selects Edit, and replaces that text withMy revised report, without selecting Save. While checking another conversation, they click conversation B in the sidebar and then return to A.The editor now shows
Original saved reportagain. The local revision has disappeared, and neither navigation step asks the user to discard it. Opening the artifact again does not recover it. This is especially easy to encounter when comparing generated documents, consulting another conversation, or briefly opening another workspace page while editing.The unsaved-change warning creates a reasonable expectation that unfinished work is protected. It currently protects a document unload, such as a refresh, but following a Next.js link keeps the browser document alive. The user therefore takes an ordinary application navigation action and loses work without encountering the warning.
Steps to reproduce
Use a normal workspace session with two existing conversations. Conversation A should contain a completed, editable UTF-8 text artifact under
/mnt/user-data/outputs, such asreport.txtorreport.md, and its run should be idle.UNSAVED NAVIGATION TEST.The important trigger is an internal route transition. Refreshing the page is a different lifecycle event and may invoke
beforeunload; it does not reproduce the missing SPA protection by itself.Expected behavior
An unsaved draft should remain associated with its conversation and file until the user saves or discards it, while they navigate within the same application session and account. Returning to A should show
My revised report, and editing the same relative file path in B should have its own draft.Save should continue to be an explicit server write, using the revision that established the editing baseline. Discard changes should continue to restore the appropriate saved content. Navigation should not become an implicit discard action.
The account boundary matters when extending the lifetime of this state. Signing out or changing accounts should clear the previous account's drafts. A subsequent login should not recover content from the previous authenticated session, including when the same account signs in again after an intervening account change.
Keeping drafts in tab memory is sufficient for this issue. Refreshing or closing the document can continue to use the browser's existing unsaved-change warning. Durable draft recovery after a reload would require a separate persistence policy and is not needed to fix conversation switching.
Root cause
At the affected baseline,
ArtifactsProviderownsdraftsin a localuseState<Record<string, ArtifactDraftState>>({}). Its pathname hydration effect restores the artifact list, selected artifact, and panel visibility for the incoming route, then callssetDrafts({}).That mixes two different kinds of state. Restoring the incoming conversation's panel is appropriate on a pathname change. Clearing the outgoing conversation's unfinished file edits is not. When the same provider instance survives the transition, the hydration effect alone is enough to lose the draft.
There is also an independent unmount path.
ChatProvidersmounts the artifact provider within the chat UI. When that route subtree is replaced, its local state is destroyed. Removing the pathname reset would address the first path but would leave this second path unresolved.The existing session-storage payload contains panel state: artifact paths, the selected path, and whether the panel is open. It contains no draft contents. Consequently, restoring the panel after a route transition or refresh cannot reconstruct the discarded edit. A restored panel can make the loss less obvious because the same artifact is selected again, but the selected file is read from its saved content.
The existing
beforeunloadeffect is attached to that route-local provider and inspects only its own drafts. Next.js navigation does not unload the document, and unmounting the provider also removes its listener. A reliable fix therefore needs a longer-lived draft owner and an unload check that can still see drafts belonging to inactive routes.Reproduction evidence and environment
The defect was reproduced against upstream commit
78540ac0e4622caa42b2c4d2a51a91059422e2f9, audited on October 11, 2026. The investigation used Windows 11 / AMD64, PowerShell, Node.js 24.11.1, and the repository-pinned pnpm 10.26.2.A React regression exercised the production provider with controlled pathname, sidebar, and environment boundaries. Both conversation switching and replacing the route-local provider reproduced the loss:
An additional production-provider harness recorded:
{"discardedOnRouteChange":true,"recoveredOnReturn":false,"beforeUnloadEvents":0}The absence of an unload event is consistent with the application staying in the same browser document while its React route state changes. The regression captures the loss directly, rather than relying on a particular generated report or model response.
Proposed resolution and existing work
PR #6700 implements a focused fix by separating draft ownership from route-local panel ownership. The intended design is a React draft provider mounted in the root layout, with the current account as its owner and a separate draft map for each route. Individual artifact providers continue to manage the panel and editing mode, but read and update their route's drafts through this longer-lived provider.
The root layout is the useful lifetime here because both chat subtrees and the workspace/auth layout can unmount during internal navigation. Returning to an existing route can retrieve its draft without writing file bodies to browser storage. The unload warning can inspect all retained route maps, so a dirty draft remains protected even while its original chat is inactive.
Extending the lifetime also requires explicit account handling. Binding the store to the authenticated user, clearing it on an owner change or logout, and filtering reads by the current owner prevents a route from showing another account's retained content. Update callbacks also need to carry the account identity and a binding generation. The generation distinguishes two separate visits by account A around an A → B → A transition, allowing late callbacks from the earlier session to be ignored.
An unavailable Gateway auth probe must remain distinct from a confirmed logout. Preserve the store and unload warning during that unknown state, hide its contents until the account is confirmed, and clear it only on a definite anonymous verdict or account change. The owner-binding hook should subscribe to a stable setter so editor updates do not rerender unrelated auth consumers.
The retained draft should include its original content and SHA-256 baseline, not just the edited text. Existing remote-refresh reconciliation can then preserve a dirty draft and report a conflict if the agent has changed the file while the user was elsewhere. Save and Discard keep their existing behavior; the fix changes where drafts live, rather than the artifact write API.
Related inline-editing work was checked in #4595 / #4596, along with complete-content baseline fixes in #6031 / #6036. Those changes address editing and revision safety; this report concerns losing the local draft during route changes and provider unmounts. The linked PR includes navigation, account-lifecycle, Save/Discard, and late-callback regressions. Its browser verification uses the production application with controlled API routes; home-return and account-boundary behavior are covered by React lifecycle tests.