fix(desktop): optimistic rewind + checkpoint boundary corrections#4272
Merged
Conversation
- UndoRewindBanner: shows above composer after non-fork rewind, displaying turns rolled back and files changed, with two-click confirm undo button - Silent fork before rewind: creates a backup tab that undo switches to, fully restoring the pre-rewind session - rewindSignal prop: Transcript scrolls to the last user message after rewind so the user sees exactly where they rewound to - CSS: .undo-rewind styles matching the composer area - Locales: undoRewind keys in en/zh/zh-TW
CheckpointHasBoundary previously only checked whether the cpBound[turn] key existed. After compaction the key remains but its value is stale (boundary > len(Messages)), so the UI showed the rewind button as enabled despite the backend refusing the operation with 'conversation rewind unavailable for turn N: the conversation was compacted past this point'. Now CheckpointHasBoundary also validates boundary <= len(Messages), so compacted turns get CanConversation=false and the frontend button is correctly disabled.
Frontend: - Optimistic rewind: immediate UI truncation, undo via restore, deferred Go Rewind on actual send. Composer refill with rewound-to prompt. Code-only rewind skips truncation. - displayItems memo: switches between full and truncated items based on rewindState. - send wrapper: commits any pending rewind before sending. Go backend: - CheckpointHasBoundary: also validate boundary <= len(Messages), so compacted turns correctly show CanConversation=false. - List(): nil cur.Paths so in-progress file snapshots don't pollute CanCode backward-propagation. - RestoreCode: only emit notice when files actually changed (skip confusing '0 file(s) restored, 0 removed').
actionMeta('code') showed checkpoint.files.length (the current turn's
own files), but RestoreCode(fromTurn) restores all files from turn
fromTurn onward. Now CheckpointsForTab propagates the cumulative
unique file set backwards so the UI shows the true number of files
that would be affected.
Follow-on cleanup to the optimistic rewind in this PR: - On a deferred-rewind failure, leave the optimistic state cleared (full transcript restored) instead of re-truncating to a view the Go side no longer matches; still skip the send. - Defer the dock/project refresh for scope=both until the rewind actually commits, so the file dock no longer flashes the un-rewound state. - Clear the refilled composer when the user undoes the rewind. - Sort CheckpointsForTab's cumulative Files (Go map order is nondeterministic). - Drop the unused onDismiss prop from UndoRewindBanner.
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.
Lands #4269 (by @CVEngineer66) and tightens a few rough edges in the optimistic-rewind flow. Closes #4269.
What #4269 fixes (commits 1–4, @CVEngineer66)
cpBound[turn]key can survive while its boundary points past the truncated log. Now validatesboundary <= len(Messages)— exactly the guardRewinditself applies — so the UI button state matches what a rewind will accept.Pathsare nilled), while the turn still shows as a rewind point.Rewindis deferred until the next send. Undo restores the full items with no Go call.Follow-on tightening (commit 5)
CheckpointsForTab's cumulative list (Go map order is unordered).onDismissprop fromUndoRewindBanner.Validation
go build ./...(desktop + core),go vet,go test ./internal/checkpoint/ ./internal/control/pass.tsc --noEmit(afterwails generate module) +check:csspass.