Skip to content

fix: keep unsaved editor drafts after Refresh remounts - #611

Merged
Leeeon233 merged 6 commits into
LodyAI:mainfrom
ladydd:fix/consume-acked-editor-snapshot
Sep 24, 2026
Merged

Leeeon233 merged 6 commits into
LodyAI:mainfrom
ladydd:fix/consume-acked-editor-snapshot

Conversation

@ladydd

@ladydd ladydd commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

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

handleExternalTextUpdateApplied now consumes the matching externalTextUpdate seq on both applied and no-op. A newer pending seq is left in place. Save APIs, disk writes, and conflict preservePending are unchanged.

Visual explanation

Refresh -> set externalTextUpdate{seq:N, snapshot}
editor acks applied|no-op
before: parent keeps {seq:N}
after:  parent drops {seq:N} unless current.seq !== N

later remount (preview or file-tab active/LSP key)
before: lastAppliedSeqRef empty -> replay snapshot, clear dirty
after:  no leftover event -> draft stays

Before / after

Before After
Refresh, type, switch file tab or preview → draft gone, dirty false Draft and Unsaved remain
Same switches without Refresh Unchanged: draft remains
Later Refresh / new seq Still applies

Test plan

  • CI note (f99823cf): Tests fails in sparkle-packaging.test.mjs, which still reads release-electron.yml removed 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.
  • Human confirmation on Windows: the reporter manually reproduced the draft loss in Lody 0.93.3 using Refresh → edit (Unsaved shown) → switch to another file → return. The reporter explicitly confirmed clicking Refresh beforehand.
  • Committed regression tests: packages/components/tests/session-file-content-view.test.tsx: 32 passed, including real native Markdown preview remount with and without Refresh.
  • Independent automated reproduction and fix verification: local Linux Chromium with the real Monaco viewer/controller and 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.
  • oxlint on the changed files: 0 errors. pnpm run docs check: 0 new errors (pre-existing AGENTS.md size warnings).
  • Remaining validation: the fix has not yet been retested in a packaged Windows desktop build. The Windows manual reproduction confirms the original bug; automated fix verification used the source component and isolated browser environments described above.

Context handoff

Instructions for reviewing agents

  • Review focus: handleExternalTextUpdateApplied in session-file-content-view.tsx; confirm both applied and no-op consume the matching seq, and a newer seq is kept.
  • Decisions to challenge: Clearing the parent event rather than storing a consumed-seq watermark; whether conflict load_with_conflicts still keeps pending after apply.
  • Plausible failures / evidence gaps: The fix has not been retested in a packaged Windows desktop build; a no-op conflict ack still does not clear preservePending (pre-existing).

Authoring context

  • User goal / directives: Ship a focused main-repo PR for the confirmed Refresh-then-switch unsaved-draft loss, using the consume-acked-seq fix.
  • Constraints / non-goals: Do not change save APIs, disk IO, adapter/core, or fold into Fix/electron local file browser #38/fix: pin Codex compact-kill waiter rejection #551. Distinguish the reporter's confirmed Windows manual reproduction from automated Linux fix verification.
  • Risk-bearing decisions: Dropping an acknowledged snapshot from parent state so remount cannot replay it, while still applying a newer seq.
  • Destructive or irreversible behavior: No disk rewrite. The change only forgets an already-applied in-memory snapshot. Failed or unacked updates are not consumed.
  • Deliberately not done or tested: No packaged desktop verification of the fix; no adapter changes. Native component regression tests are committed; real Monaco coverage is the local isolated Chromium run, whose browser scripts are not committed.
  • Unknowns / confidence: High confidence in the Refresh-then-remount cause and local fix. A human tester also confirmed the original bug on Windows 0.93.3; packaged Windows verification of the fix remains pending.

Original user prompt

After Refresh, unsaved editor text is discarded when switching file tabs or preview.

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.

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.
@ladydd
ladydd force-pushed the fix/consume-acked-editor-snapshot branch from f99823c to ee32471 Compare September 11, 2026 14:28

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +760 to +762
setExternalTextUpdate((current) =>
current?.seq === externalTextUpdate.seq ? undefined : current
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions github-actions Bot added status:needs-pr-attention External PR needs contributor attention before review and removed status:needs-pr-attention External PR needs contributor attention before review labels Sep 21, 2026
@Leeeon233

Copy link
Copy Markdown
Contributor

Thanks!

@Leeeon233
Leeeon233 merged commit 3b2c855 into LodyAI:main Sep 24, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Refresh then switching file tabs or preview discards unsaved editor drafts

2 participants