Skip to content

fix: restore missing revert actions and chat layout after resize#4157

Open
1-minute-to-midnight wants to merge 13 commits into
pingdotgg:mainfrom
1-minute-to-midnight:agent/fix-revert-and-timeline-resize
Open

fix: restore missing revert actions and chat layout after resize#4157
1-minute-to-midnight wants to merge 13 commits into
pingdotgg:mainfrom
1-minute-to-midnight:agent/fix-revert-and-timeline-resize

Conversation

@1-minute-to-midnight

@1-minute-to-midnight 1-minute-to-midnight commented Jul 19, 2026

Copy link
Copy Markdown

Summary

This fixes two user-facing issues:

  1. The revert feature was unavailable in most conversation threads, even when hovering over a user message.
  2. When another desktop window temporarily made T3 Code narrower, the conversation became squished and stayed that way after the window was closed.

Revert was unavailable in most threads

The revert icon would appear in some conversations but be completely absent in many others. This made the feature feel inconsistent and often impossible to use.

Revert action missing from a user message

The fix keeps the revert action available on user messages and makes checkpoint matching work across current, resumed, imported, and older threads. While a turn is still running, the action remains visible but disabled with an explanation.

Reverting to a message now restores the conversation to that point and restores the corresponding workspace files. This also works in folders that are not Git repositories: T3 stores its own snapshots outside the workspace and does not create a .git directory in the user's project.

Implementation details:

  • fall back from an exact assistant-message checkpoint match to stable turn IDs and conversation order when needed
  • use a T3-managed shadow repository under the application state directory for non-Git workspace snapshots
  • align the server and client projections so the selected user message remains while its response and every later message are removed
  • prevent a non-Git revert from incorrectly reaching GitVcsDriver and failing with exit code 128
Additional revert failure screenshots

A non-Git revert incorrectly failing in GitVcsDriver

A later user message remaining after reverting to hello

Resizing the window could permanently squish the conversation

Opening an external desktop window, such as KDE Konsole beside T3 Code, temporarily narrowed the app as expected. After closing that window, the T3 layout expanded again but the conversation rows remained collapsed into one-character-wide strips. Switching to another thread and back was previously required to reset them.

Conversation rows remaining squished after the window expands

The virtualized message list was retaining cached row measurements from the narrow layout. The fix refreshes the list after resizing settles, covering element resize notifications, desktop window and visual viewport changes, and compositor transitions that do not reliably emit the same event sequence.

Validation

  • 112 focused tests passed across seven affected test files
  • targeted formatting and lint checks passed
  • targeted type checks passed for t3, @t3tools/web, and @t3tools/client-runtime
  • isolated browser verification confirmed that reverting to hello removes a file created by the next turn and removes every subsequent message
  • isolated narrow/restore verification confirmed that conversation rows recover their correct width
  • rebuilt AppImage reached backend ready and main window created in an isolated smoke test
  • the reporter manually confirmed both fixes in the packaged AppImage

Development note

ChatGPT 5.6 Sol was used to debug these issues, implement the fixes, and run the focused verification. The packaged result was then manually tested and confirmed by the reporter.

Note

Restore missing revert actions and fix chat layout after viewport resize

  • Rewrites message retention on thread.reverted to truncate at the Nth user message boundary (by index) rather than by checkpoint-derived turn IDs, preserving uncheckpointed predecessor messages and always retaining system messages across the server projector, client reducer, and orchestration pipeline.
  • Adds deriveRevertTurnCountByUserMessageId in ChatView.logic.ts to map user messages to revert indices by matching checkpoints via assistantMessageId or turnId fallback, including non-streaming assistant messages.
  • Makes the revert button always visible in MessagesTimeline.tsx with context-aware disabled state and tooltip, instead of being hidden when unavailable.
  • Fixes MessagesTimeline remounting LegendList after viewport width changes settle (debounced 120ms) to reset cached measurements and correct layout after resize.
  • Extends CheckpointStore to support non-Git workspaces via a per-workspace shadow bare Git repository, and fixes checkpoint restore in unborn repositories in GitVcsDriver.
  • Risk: revert now fails closed for threads where a summarized filesystem checkpoint cannot be restored — conversation history is not rewound and a checkpoint.revert.failed activity is recorded.

Macroscope summarized 1bafb7b.


Note

High Risk
Changes checkpoint capture/restore, revert semantics, and Git detection across server and client; incorrect behavior could corrupt workspace files or desync conversation from disk state.

Overview
Restores revert-to-message across more thread types and fixes a chat layout bug after the window narrows and widens again.

Checkpoints and revert: CheckpointStore now uses a T3-managed shadow Git repo under app state when the workspace does not own a Git root (non-Git folders, nested projects). isGitRepository only returns true for the canonical repo root. CheckpointReactor no longer skips checkpoint work for non-Git cwd; revert fails closed on missing filesystem snapshots when checkpoint summaries exist, but still allows conversation-only rollback for legacy threads. Revert retention on thread.reverted is rewritten to cut at the Nth user message (conversation index), not checkpoint turn IDs—server projector, projection pipeline, and client threadReducer stay aligned.

UI: deriveRevertTurnCountByUserMessageId centralizes revert targets (checkpoints, turn IDs, conversation order). Revert stays visible on user messages (disabled until the turn can revert). MessagesTimeline remounts the virtualized list after viewport width changes so rows do not stay one-character wide.

Reviewed by Cursor Bugbot for commit 1bafb7b. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9d25f514-9618-4ee4-a08d-d6e376f83de5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Jul 19, 2026
Comment thread apps/server/src/orchestration/Layers/ProjectionPipeline.ts Outdated
Comment thread apps/server/src/vcs/GitVcsDriver.ts
Comment thread apps/server/src/checkpointing/CheckpointStore.ts
@1-minute-to-midnight
1-minute-to-midnight marked this pull request as ready for review July 19, 2026 17:07
Comment thread apps/server/src/vcs/GitVcsDriver.ts
Comment thread apps/server/src/orchestration/projector.ts
Comment thread apps/server/src/orchestration/projector.ts Outdated
Comment thread apps/server/src/checkpointing/CheckpointStore.ts
@macroscopeapp

macroscopeapp Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR introduces a new shadow checkpoint system and rewrites conversation revert logic across server and client. Multiple high-severity unresolved review comments identify potential bugs in rollback counting and atomicity of workspace/conversation state, warranting careful human review.

You can customize Macroscope's approvability policy. Learn more.

Comment thread apps/server/src/orchestration/Layers/ProjectionPipeline.ts
Comment thread apps/server/src/checkpointing/CheckpointStore.ts Outdated
Comment thread apps/server/src/orchestration/Layers/CheckpointReactor.ts Outdated
Comment thread apps/server/src/checkpointing/CheckpointStore.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The Effect service conventions require catching statically known tagged failures with Effect.catchTags({ ... }) — including when handling only a single tag — and explicitly prohibit Effect.catchTag. This PR introduces two new catchTag call sites. Pre-existing catchTag usages elsewhere in the repository are out of scope for this review.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/checkpointing/CheckpointStore.ts Outdated
Comment thread apps/server/src/orchestration/Layers/CheckpointReactor.ts Outdated
Comment thread apps/server/src/orchestration/Layers/CheckpointReactor.ts Outdated
Comment thread apps/server/src/checkpointing/CheckpointStore.ts
Comment thread apps/server/src/orchestration/Layers/CheckpointReactor.ts
Comment thread apps/server/src/orchestration/Layers/CheckpointReactor.ts
Comment thread apps/server/src/orchestration/Layers/CheckpointReactor.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One Effect error-convention violation: raw git stderr is copied into VcsProcessExitError.detail, which is interpolated into the caller-visible message. Prefer a bounded, normalized detail and capture the raw output only as stderrLength, matching the canonical VcsProcessExitError.fromProcessExit factory.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/checkpointing/CheckpointStore.ts Outdated
Comment thread apps/server/src/checkpointing/CheckpointStore.ts Outdated
Comment thread apps/server/src/checkpointing/CheckpointStore.ts Outdated
Resolve symbolic workspace aliases before choosing between the owned Git repository and the shadow checkpoint store. This keeps /var and /private/var representations from splitting checkpoint state on macOS.

Co-authored-by: codex <codex@users.noreply.github.com>
@juliusmarminge
juliusmarminge force-pushed the agent/fix-revert-and-timeline-resize branch from 733a3d8 to 1bafb7b Compare July 20, 2026 16:34
Comment on lines +59 to +61
if (nextEntry.message.role === "user") {
break;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium components/ChatView.logic.ts:59

deriveRevertTurnCountByUserMessageId skips a user message that has no assistant reply yet whenever it encounters the next user message, so the earlier user message is never assigned a revertTurnCount. This happens during steering, which appends another user message to the same running turn before any assistant message exists (the code explicitly supports this). After the turn completes, the first user message's revert button stays permanently disabled even though reverting to that earlier index is valid. The inner loop should continue scanning past subsequent user messages instead of breaking, or otherwise account for steering-appended messages.

Suggested change
if (nextEntry.message.role === "user") {
break;
}
if (nextEntry.message.role === "user") {
continue;
}
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatView.logic.ts around lines 59-61:

`deriveRevertTurnCountByUserMessageId` skips a user message that has no assistant reply yet whenever it encounters the next user message, so the earlier user message is never assigned a `revertTurnCount`. This happens during steering, which appends another user message to the same running turn before any assistant message exists (the code explicitly supports this). After the turn completes, the first user message's revert button stays permanently disabled even though reverting to that earlier index is valid. The inner loop should continue scanning past subsequent user messages instead of breaking, or otherwise account for steering-appended messages.

data-timeline-layout-generation={timelineLayoutGeneration}
>
<LegendList<MessagesTimelineRow>
key={timelineLayoutGeneration}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium chat/MessagesTimeline.tsx:530

When the viewport width settles after a resize, the list remounts via key={timelineLayoutGeneration} with initialScrollAtEnd still set, so the scroll position jumps to the conversation end even if the user was reading older messages. The remount resets the LegendList to its initial scroll position instead of preserving the current offset. Consider saving the current scroll offset before remounting and restoring it afterward (for example, via initialScrollIndex or a post-mount scrollToOffset), or gate initialScrollAtEnd so it only applies on the first mount.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/chat/MessagesTimeline.tsx around line 530:

When the viewport width settles after a resize, the list remounts via `key={timelineLayoutGeneration}` with `initialScrollAtEnd` still set, so the scroll position jumps to the conversation end even if the user was reading older messages. The remount resets the `LegendList` to its initial scroll position instead of preserving the current offset. Consider saving the current scroll offset before remounting and restoring it afterward (for example, via `initialScrollIndex` or a post-mount `scrollToOffset`), or gate `initialScrollAtEnd` so it only applies on the first mount.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1bafb7b. Configure here.

return;
// Refresh the workspace entry index so the @-mention file picker reflects
// the reverted filesystem state before conversation history is rewound.
yield* workspaceEntries.refresh(sessionRuntime.value.cwd);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Provider rollback over-counts steering

High Severity

The revert logic calculates rolledBackTurns using the count of user messages, which can overstate the number of provider turns to roll back. This mismatch, especially with multiple user messages in a single provider turn, may cause the provider to truncate its history excessively, leading to an inconsistent state.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1bafb7b. Configure here.

return;
// Refresh the workspace entry index so the @-mention file picker reflects
// the reverted filesystem state before conversation history is rewound.
yield* workspaceEntries.refresh(sessionRuntime.value.cwd);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Workspace reverts before rollback

High Severity

During checkpoint revert, the workspace is restored and refreshed before provider rollback and thread.revert.complete. If rollbackConversation or later steps fail, the handler records failure but leaves files already reverted while conversation history and projections stay unchanged, diverging workspace and chat.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1bafb7b. Configure here.

continue;
}
if (turn.checkpointTurnCount === null) {
latestTurnId = turn.turnId;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Null checkpoint overwrites latest turn

Medium Severity

After thread.reverted, the SQL projection loop assigns latestTurnId for every retained turn with a null checkpointTurnCount, overwriting a higher checkpoint turn chosen earlier. Persisted latestTurnId can point at the wrong turn versus the in-memory projector and retained checkpoints.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1bafb7b. Configure here.

config.stateDir,
"checkpoints",
NodeCrypto.createHash("sha256").update(NodePath.resolve(cwd)).digest("hex"),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shadow store splits symlink paths

High Severity

Shadow checkpoint storage keys the bare repo with sha256(NodePath.resolve(cwd)) only. The same workspace reached via different path strings (symlinks, relative vs absolute) gets separate shadow repositories, so captures and reverts can miss refs and fail closed or restore the wrong snapshot.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1bafb7b. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants