fix: keep unsaved editor drafts after Refresh remounts - #611
Conversation
354f47f to
f99823c
Compare
Refresh left an acknowledged externalTextUpdate in parent state. Preview or file-tab remounts replayed that snapshot and cleared dirty. Consume the matching seq on applied and no-op acks.
f99823c to
ee32471
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee32471855
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| setExternalTextUpdate((current) => | ||
| current?.seq === externalTextUpdate.seq ? undefined : current | ||
| ); |
There was a problem hiding this comment.
Preserve acknowledged live snapshots across editor remounts
When subscribeText delivers a genuine external change to a clean file, data.snapshot remains the original open value and the acknowledgement records the new text only in latestEditorTextRef. Clearing the event here means that toggling Preview and then returning remounts the editor without anything to replay; because neither hasAcceptedLocalContentChangeRef nor dirty state is set, lines 1261–1284 initialize it from the stale data.snapshot.text, making the accepted remote update disappear until another refresh. Consume refresh snapshots only after their corresponding data has advanced, or make the acknowledged live snapshot the remount source as well.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed on b1f25458 (landed in 67169559). After consume-on-ack, a clean remount uses the last acked live text while that snapshot object is unchanged. Regression: retains acknowledged provider live text after preview remount. Refresh draft cases still pass. Owning suite 35/35; GitHub Tests / Static / Desktop E2E green on this head. Outdated on an old line is not treated as resolved.
Model: grok-4.6
Model: grok-4.6
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 671695596e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (hasAcceptedLocalContentChangeRef.current || isProviderEditorDirty) { | ||
| return latestEditorTextRef.current ?? snapshotText; | ||
| } | ||
| return lastAckedExternalTextRef.current ?? snapshotText; |
There was a problem hiding this comment.
Drop stale acknowledged text when the snapshot advances
When a live update has populated lastAckedExternalTextRef and the provider is later rebuilt with the same liveFileId (for example, after a file-index revision), openFile can advance data.snapshot to newer disk text without clearing this ref. A subsequent Preview/source remount therefore chooses the older acknowledged text here instead of the newly loaded snapshot; editing and saving that stale buffer can overwrite the newer contents because the provider cache already holds the newer digest. Associate the acknowledged value with its source snapshot or invalidate it whenever an authoritative snapshot advances.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed on b1f25458 (landed in 39c102bc / ff50cf94). The ack is bound to the SessionFileContentSnapshot object, not string equality, so a later openFile/Refresh that replaces the snapshot drops the stale ack. Regression: drops a stale live ack when a later open advances the snapshot. Owning suite 35/35; GitHub Tests / Static / Desktop E2E green on this head. Outdated on an old line is not treated as resolved.
Model: grok-4.6
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39c102bc95
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| return latestEditorTextRef.current ?? snapshotText; | ||
| } | ||
| const acked = lastAckedExternalTextRef.current; | ||
| if (acked && acked.snapshotText === snapshotText) { |
There was a problem hiding this comment.
Invalidate acknowledged text when snapshot identity advances
When the provider is rebuilt with the same liveFileId after disk content returns to the original text (for example, snapshot A → acknowledged live update B → authoritative open A), this string comparison still treats the old B acknowledgement as current. Provider rebuilds occur on shared-state revisions while the reset watches only liveFileId, so a subsequent editor remount displays B instead of authoritative A and saving can overwrite A. Fresh evidence beyond the prior comment is that the new provider-rebuild test covers only a snapshot whose text differs; track the snapshot/provider generation rather than text equality.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed on b1f25458 (landed in ff50cf94). Identity is the snapshot object, so A → live ack B → fresh open A follows the new open. openedLiveSnapshot is the effect dependency so latestEditorTextRef updates even when the text string is unchanged; Preview matches source. Regression: discards a live ack when a fresh open returns the original text. Owning suite 35/35; GitHub Tests / Static / Desktop E2E green on this head. Outdated on an old line is not treated as resolved.
Model: grok-4.6
Model: grok-4.6
|
Thanks! |
Related issue
Closes #610
Problem / pressure
After Refresh, unsaved Code Collab text is thrown away when the editor remounts. Switching file tabs or preview replays the acknowledged snapshot, the extra line disappears, and Unsaved clears even though nothing was saved to disk. Skipping Refresh keeps the draft. The reporter manually confirmed this bug in the Windows desktop app, version 0.93.3: click Refresh, edit until Unsaved appears, switch to another file tab, then return. The unsaved text disappears and the original text returns. This is a confirmed human reproduction during normal UI use.
Summary
handleExternalTextUpdateAppliednow consumes the matchingexternalTextUpdateseq on bothappliedandno-op. A newer pending seq is left in place. Save APIs, disk writes, and conflictpreservePendingare unchanged.Visual explanation
Before / after
Test plan
f99823cf): Tests fails insparkle-packaging.test.mjs, which still readsrelease-electron.ymlremoved by chore: replace desktop publishing with changelog releases #609. This failure is present on the PR base and unchanged by this patch; editor regressions (32/32), component tests (3426/3426), static checks, and desktop smoke passed.packages/components/tests/session-file-content-view.test.tsx: 32 passed, including real native Markdown preview remount with and without Refresh.CodeCollabSessionFileProvider, using a synthetic in-memory runtime. Before the fix, both Refresh-then-switch cases lost drafts while both no-Refresh controls kept them. After the fix, all four retained drafts; explicit Save reached the synthetic backend and a later Refresh still updated the model. These browser scripts are local evidence and are not committed in this PR.pnpm run docs check: 0 new errors (pre-existing AGENTS.md size warnings).Context handoff
Instructions for reviewing agents
handleExternalTextUpdateAppliedinsession-file-content-view.tsx; confirm bothappliedandno-opconsume the matching seq, and a newer seq is kept.load_with_conflictsstill keeps pending after apply.preservePending(pre-existing).Authoring context
Original user prompt
Shared conversation
Status: unavailable
Reason: The user cannot supply a public conversation URL, and this Grok session cannot publish the authoring conversation to an HTTP(S) link.