[diffs] Refactor Edit Persistance - #1092
Merged
amadeus merged 20 commits intoAug 28, 2026
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This comment has been minimized.
This comment has been minimized.
ije
reviewed
Aug 24, 2026
| * in memory so a later editor using the same kind and key can resume them. | ||
| */ | ||
| constructor( | ||
| documentKind: EditorDocumentKind, |
Collaborator
There was a problem hiding this comment.
maybe introduce DiffsEditor and FileEditor instead of argument?
Member
Author
There was a problem hiding this comment.
That's a possibility, but felt like that was yet another large refactor that would happen on top of an already large refactor heh, so figured this was simpler and matched what we do in other places with like InteractionManager
amadeus
force-pushed
the
amadeus/remove-editor-persistance
branch
from
August 25, 2026 00:14
7d30437 to
a92d427
Compare
This cleans up a ton of internal code around session persistance. Also fixed a bug with collapsed state resetting undo state when it shouldn't
Still some rough edged to be worked out in the next couple phases.
This is a lot, and I had to fight my way through a bunch of different things, but I think we also fixed a bunch of bad bugs alogn the way. We've now hooked up documentKey retention.
This turned out to be a bit bigger than I originally thought because there was a bunch of bullshit that wasn't properly accounted for and the AI did definitely do some slop. Anyways, I think we've landed somewhere that's really good, the default persistance works really nicely now with the homepage stuff, and doesn't require any hacky bullshit
Also hook it up in the agent ui
About to be rewritten
Getting the plumbing in place for the new API
Alright, final docs, test fixes and some last minute API tweaks. I think we got this refactor in the books
Also make sure cancellation vs save is properly exemplified and works properly in the playground.
This simplifies a lot of stuff around annotations getting changed despite content changes removing them and then reverting back to the original.
A `bug` was found if the internal component editor lifecycle methods are use improperly. This would go against how the component was designed to be used, so I've gone ahead and intentionally marked them as internal and alligned their naming conventions to include __
amadeus
force-pushed
the
amadeus/remove-editor-persistance
branch
from
August 28, 2026 07:37
bb5f503 to
4fd50fd
Compare
necolas
approved these changes
Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Preamble
Another fairly beefy PR in the editing stack. Similar to the previous branches, I don't expect every line to get a deep review, but I do want to explain the changes at a high level.
This builds on the edit completion work from #1089. That branch gave components a clear accept/reject boundary, and now the big remaining piece to fix from the start of our stack -- refactoring state persistance to not be based on cacheKey, and be less magical and more flexible.
The old persistence system grew out of a narrower requirement: keep some editor state when switching between files. It eventually became responsible for a mix of documents, history, selections, scroll positions, worker cache keys, IndexedDB, and component lifecycle behavior. I think this ended up being a bit less flexible and piggy-backed on some concerns around how Worker Pool caching worked that wasn't necessarily compatible with these requirements.
This PR replaces that machinery with explicit edit-session retention and state transfer. It also gives applications a supported way to inspect, move, restore, or clear an editing session without treating internal editor state as a JSON storage format.
The problem
Before this branch, editor persistence was enabled with
persistStateand wasimplicitly identified by
FileContents.cacheKey. That caused a few issue:Editor, while selections and scroll state could be sent to a separate in-memory, IndexedDB, or custom storage adapter.editorOptionsobject.The result was some state belonged to the application, some to the component, some to the live editor, and some to a best-effort storage layer.
The new retention model
The main change is a new
EditStateManager, which retains complete editing sessions using an application-providededitStateKey.The key gives an editing session a stable identity outside any one component or
Editorinstance. When that editor goes away, the manager can hold onto the session-wide state needed to continue later. A new editor using the same key can pick up the draft, undo/redo history, selections, and relevant component state without the application rebuilding those pieces itself.This makes retention deliberate instead of tying it to
FileContents.cacheKeyor aneditorOptionsobject. Applications decide which sessions receive keys, how many inactive sessions can be retained, and when retained state should be inspected, partially cleared, or removed entirely.Most applications only need to provide a stable key and let the manager handle the session lifecycle. Applications with more specific persistence requirements can use
EditStateManagerdirectly, or capture state from an editor and provide it asinitialStatewhen constructing another editor.The manager is intentionally an in-memory system. Applications that need state to survive a reload still own that durable storage boundary, which is covered later in this description.
Keyed edit-session retention
The old
persistStateswitch andpersistStateStorageoption are gone. An application now opts into retention by assigning a stableeditStateKey:Vanilla editors receive the same identity at construction:
When the session becomes inactive, its document, history, selections, and eligible viewport state move into
EditStateManager. A later editor with the same document kind and key can resume the session.The document kind is part of the identity. File and FileDiff sessions use separate namespaces, and every
Editoris now constructed for exactly one of those kinds:This prevents a file document from being attached to a FileDiff, or a retained diff session from being interpreted as a plain file.
Retention is synchronous and limited to the current in memory session. Each document-kind namespace keeps up to 100 inactive retained sessions by default. Active sessions do not count toward that limit and cannot be evicted or cleared out from under their editor.
The manager exposes a small application-facing API for inspecting and clearing state:
Save and Cancel are separate from state retention. Returning
'accept'keeps the edited contents in the component, while returning'reject'restores the original contents. Neither choice deletes the keyed session fromEditStateManager. If the sameeditStateKeyis used again, the editor resumes that session and its history. CallEditStateManager.clear()after editing ends when the next edit should start fresh.State capture and transfer
Editors now expose two different state APIs:
getViewState()returns a defensive copy of selections and editor-owned viewport offsets.setViewState()applies that copy to an attached editor.getEditState()returns the complete session at its latest lifecycle checkpoint. It remains available while rendering is recycled and duringonEditComplete, which lets an owner transfer the final state while it is still readable from the editor:The editor adopts
initialState; it does not clone it. Partial initial state is also supported. Missing fields are completed from the component on first attachment, which allows an application to restore only a document, file metadata, or view state when that is all it owns.The change and completion events now carry the relevant editor instead of embedding another state snapshot into every event. Consumers can pull the exact level of state they need:
FileDiff state
A FileDiff session has more state than the editable new-side document. It also needs the old-side baseline and hunk information used to interpret that document.
The complete FileDiff
EditStatetherefore includes adiffSession. An edited document and its undo/redo history must travel with the matching diff session. Restoring the document without that metadata could combine new-side text with an unrelated old file and produce an invalid diff.Partial diffs still need complete file contents before they can be edited. When those files are not initially present,
loadDiffFilescan hydrate them asynchronously before the editing session is started.Viewport ownership
The previous editor state walked the DOM to find a scroll container. That made vertical scroll restoration ambiguous for components inside a larger page, virtualizer, or CodeView.
Viewport ownership is now explicit:
ownsVerticalViewport: trueis supplied when the editor is created.This keeps item-local state with the item without making one editor responsible for moving an entire page or virtualized list when it resumes.
React and
CodeViewThe React
EditProviderand vanillaCodeViewfactory signatures now pass the document kind and edit-state key through to editor construction:React no longer caches editors by
editorOptionsobject identity. A factory creates a fresh editor for a new session, whileeditStateKeyprovides explicit continuity when that session should resume. Changing the provider, options, or key does not replace an active editor; those values apply when the next session is created.File,FileDiff,MultiFileDiff, andPatchDiffall forwardeditStateKey.CodeViewusesgetEditStateKey(item)so each item can derive a stable key from application identity:Collapse and virtualization continue to recycle the same editor without ending the edit session. Removing an item or turning edit mode off completes the session. A later editor can resume it only when that item has an
editStateKey.Completion and teardown
This branch tightens the lifecycle introduced in
#1089so the editor owns the entire completion sequence.The disposer returned by
editor.edit(component)remains the normal way to finish a session. It detaches the editor and completes the component with the correct install behavior.Cleanup reasons now have distinct behavior:
A changed session still publishes
onEditCompleteduring discard so the application can observe or persist the final value, but an accepted return value is ignored because the component is being removed. Recycle keeps the private document and history alive for collapse and virtualization.Unchanged, annotation-only, selection-only, and fully undone sessions still do not call
onEditComplete. Save and Cancel controls therefore reset their own intent when editing starts and when a completion action is chosen, rather than depending on a completion callback that may not run. The docs examples and playground now follow that rule consistently.Durable persistence
The old IndexedDB and custom
IStateStorageadapters have been removed. They could serialize selections and viewport offsets, but they could not serialize a complete live session safely.Durable storage is now deliberately application-owned:
FileContentsfromonEditChangeor an explicit save point.editor.getViewState()when selections or viewport state should survive a reload.TextDocumentand pass it throughinitialStatewhen loading the draft again.Complete
EditStateshould not be serialized. It contains a live document, history entries, diff metadata, and potentially application annotation values that are not JSON-safe. Durable restoration intentionally starts a fresh undo timeline.EditStateManagerretains complete sessions in memory, while the application decides how and where durable file data is stored.API cleanup
The public names now distinguish view state from complete edit state:
The editor constructor, React provider, and CodeView factory changes are breaking because the document kind and key are now explicit creation-time inputs. The migration guide linked above has the direct before/after mappings.