fix(files,realtime): issue a revision on revert and stop resurrecting cleared cursors - #8098
Merged
Merged
Conversation
… cleared cursors Follow-ups to review findings on the v0.8.48 release PR. - POST /api/v2/files/:id/versions/:version/revert now returns the revision naming the content the file holds after the revert. A revert consumes the caller's revision, so without it chaining a second conditional write needed a metadata re-read, and the gap between the two reopened the TOCTOU window the revision exists to close. - The presence roster merge now distinguishes an absent cursor from an explicit null. The fallback is load-bearing: the server rebuilds a socket's presence record on join, so a re-join broadcasts a roster with cursor omitted. But a null is a pointer the peer cleared, and coalescing both with ?? re-pinned a ghost cursor whenever the clearing cursor-update was missed. - Corrected two File block output descriptions that named operations the block does not have: revision claimed a get operation (this block has Get Content, which emits no revision) and lineCount named insert, which is an edit mode. Also collapses the five hand-rolled copies of the revision conditional spread into workspaceFileRevisionField, beside the token it describes.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
Collaborator
Author
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 16 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
The revert response example carried a revision token encoding 2026-01-16T09:12:00.000Z while its file and version showed 2026-01-15T10:30:00Z. A revision is base64url(fileId:contentUpdatedAt), so the token has to name the record it ships with; every other example in the document already pairs them, and the one using the later timestamp overrides updatedAt to match. Echoing the old example back as expectedRevision would have named content the response never described.
Collaborator
Author
Collaborator
Author
|
@cubic-dev-ai review this PR |
Contributor
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
This branch was successfully deployed
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.
Summary
Follow-ups to the review findings on #8096. Ten findings were raised there; I investigated each one. Three were real and are fixed here, seven were not and were dismissed on the threads.
Fixed
POST /api/v2/files/:id/versions/:version/revertacceptsexpectedRevisionand invalidates it by succeeding, but was the only v2 file write that didn't return the replacement. Callers had to re-read metadata to chain a second conditional write — and the gap between the revert and that read reopens exactly the TOCTOU window the revision exists to close.cursorwith??. The fallback is load-bearing — the realtime server rebuilds a socket's presence record on join, so a re-join broadcasts a roster withcursoromitted, and blanking it would flicker peers' pointers. But an explicitnullis a pointer the peer cleared on leaving the canvas, and treating it as absent re-pinned a ghost cursor whenever the clearingcursor-updatewas missed (dropped by the visibility gate during a join, or by a rejoin that never refreshed the roster). Nowundefinedkeeps,nullclears.selectionis untouched — a cleared selection is{type:'none'}and that wire type admits no null. Pulled the merge out tosocket-presence-merge.tsso it's testable, matching the siblingsocket-join-target.ts/socket-join-controller.tspattern.revisionclaimed agetoperation — this block has Get Content, which emits no revision — andlineCountnamedinsert, which is an edit mode under Apply Edit, not an operation.Also collapses the five hand-rolled copies of the
revisionconditional spread intoworkspaceFileRevisionField, beside the token whose omission contract it encodes. That includes the privaterevisionFieldthe internal file surface had already extracted for itself.Investigated and dismissed (details on each thread in #8096)
POSTGRES_PASSWORDURI-unsafe characters — pre-existing and unchanged by the PR; only/,#,%breaknew URL(), not the@/:that were flagged, and Compose builds the DSN in YAML so the route can't encode it.output-selectlosing chip chrome — that is the change; the editor sub-block tree has 0ChipComboboxagainst 33Combobox.revisionemitted without a content timestamp —content_updated_atisNOT NULL, and the comparison uses the same coalesce as the issuer.head.version + 1is the documented implicit current version, and the read is a single-statement correlated subquery.concurrencyLimit: 1.toStringOrNullwire-shape change — byte-identical to theasStringit replaced.Type of Change
Testing
bun run check:audits47/47,bun run lint:check26/26,type-checkclean onapps/simandpackages/sim-cli.Checklist
🤖 Generated with Claude Code