Skip to content

feat: implement message editing and thread rollback functionality with UI support#592

Draft
zip700 wants to merge 6 commits intoDimillian:mainfrom
zip700:issue-511-edit-regenerate-user-messages
Draft

feat: implement message editing and thread rollback functionality with UI support#592
zip700 wants to merge 6 commits intoDimillian:mainfrom
zip700:issue-511-edit-regenerate-user-messages

Conversation

@zip700
Copy link
Copy Markdown

@zip700 zip700 commented Apr 3, 2026

PR Architecture Summary: Edit and Regenerate Message History (Issue #511)

*Disclaimer: This Pull Request and summary were structurally generated and organized by an AI Assistant.


High-Level Objective

This PR implements the "Edit and Regenerate" feature for message history, allowing users to modify an existing message mid-thread and seamlessly rewind the conversational context to branch off from that point.

Explicit Architectural Choices

  1. State Isolation (useMessageEdit.ts)
    Instead of ballooning useMainAppLayoutSurfaces or useThreads with transient UI state for hovering, editing, and prompt validations, we created a dedicated hook useMessageEdit.ts. This encapsulates text mutation, image retention, UI transition states (idle, editing, confirming, regenerating), and validation guards natively.

  2. Frontend Reducer Action (truncateThreadItems)
    Truncation of orphaned thread items (any downstream context succeeding the edited turn ID) was explicitly wired into useThreadsReducer.ts. The UI optimistically and cleanly prunes these items leveraging afterItemId immediately following a successful network rollback, prior to sending the new generation request. It utilizes the existing thread hierarchy invariants safely.

  3. Backend / Daemon Parity
    Following backend routing rules, the new rollback_thread command traverses all necessary stacks cleanly:

    • Added rollback_thread_core in src-tauri/src/shared/codex_core.rs.
    • Wired the tauri endpoint using the frontend IPC standard src/services/tauri.ts.
    • Covered JSON-RPC proxy endpoints for the headless Daemon in src-tauri/src/bin/codex_monitor_daemon/rpc/codex.rs.
    • Handles the remote_backend remote switch logic properly within src-tauri/src/codex/mod.rs.
  4. UI/UX Aesthetics & Design System Reuse
    Rather than introducing a global top-level modal for editing logic, the message edit menu and confirmation warning inject conditionally via MessageRows.tsx. All CSS builds purely upon existing tokenized primitives (--surface-accent, --cm-surface-panel-strong, etc.) avoiding drift and duplicated components as outlined in the AGENTS.md guidelines.

Areas of Consideration / Potential Concerns

  • Concurrent Execution / Double Submit Guards:
    The useMessageEdit disables interactable elements via an isRegenerating state guard. In addition, the controller (useThreadMessaging.ts) double-checks threadStatusById[threadId]?.isProcessing to prevent an incoming rollback while a background thought is actively polling.
  • Rollback Failures:
    Currently, if rollbackThreadService(...) throws an error on the Rust backend, the subsequent truncateThreadItems action is bypassed, and the UI will push a standard generic Error Message into the chat stream via pushThreadErrorMessage. This prevents local desync where the UI wipes out items but the database retains them, guaranteeing server authority.
  • Image handling over edits:
    Images present in previous prompts are automatically extracted, buffered in EditState, and re-attached dynamically into the new regenerated payload without requiring the user to manually re-upload them.
  • Legacy "any" Cleanups:
    Addressed implicit/explicit typing where applicable. For example, rollback_thread and archive_thread IPC services return strictly typed invoke<void> instead of invoke<any>.

@zip700
Copy link
Copy Markdown
Author

zip700 commented Apr 3, 2026

@codex review

Copy link
Copy Markdown

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

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: 330794611a

ℹ️ 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".

@zip700
Copy link
Copy Markdown
Author

zip700 commented Apr 3, 2026

@codex review

Copy link
Copy Markdown

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

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: b481d27d8f

ℹ️ 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".

import { asNumber, asString } from "./threadItems.shared";

function extractTurnId(record: Record<string, unknown>) {
return asString(record.id ?? record.turnId ?? record.turn_id).trim();
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 Prefer turnId fields over item id when extracting turn ids

extractTurnId currently reads record.id before record.turnId/record.turn_id, so payloads that include both identifiers will store the message item id as turnId. editAndRegenerateMessage then trusts that non-empty value and skips the refresh fallback, which can send rollback_thread the wrong identifier and fail rollback (or rewind the wrong turn) when item ids and turn ids differ.

Useful? React with 👍 / 👎.

editText={isEditingThis ? editText : undefined}
isConfirming={isEditingThis ? isConfirmingEdit : undefined}
isRegenerating={isEditingThis ? isRegeneratingEdit : undefined}
onStartEdit={isThinking ? undefined : onStartEdit}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Disable edit entry while regenerate is already in flight

Messages only gates onStartEdit on isThinking, but not on isRegeneratingEdit. During rollback/regenerate, only the active row receives the regenerating flag, so other user messages can still enter edit mode; this races with the first executeRegenerate completion path (which clears edit state) and can drop the newly opened edit session.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

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

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: 4c4451246b

ℹ️ 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 +86 to +88
const trimmed = editState.editText.trim();
if (!trimmed) {
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Permit regenerate for image-only edited messages

executeRegenerate hard-blocks whenever the edited text trims to empty, but startEdit preserves attached images and sendMessageToThread already allows sends where text is empty and images are present. This means editing/regenerating a user prompt that only contains images (or intentionally clearing text while keeping images) can never proceed, so image-based turns cannot be corrected via the new edit flow.

Useful? React with 👍 / 👎.

@zip700 zip700 force-pushed the issue-511-edit-regenerate-user-messages branch from 4c44512 to 5fe9c04 Compare April 3, 2026 22:01
Copy link
Copy Markdown

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

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: 5fe9c049e2

ℹ️ 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 +150 to +151
await refreshThread(workspace.id, threadId);
return readTurnId();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Await refreshed items before re-reading rollback turn ID

The fallback path in resolveRollbackTurnId calls refreshThread(...) and immediately re-reads via getItemsForThread, but in useThreads that getter is backed by itemsByThreadRef.current (updated on render), so this second read can still see pre-refresh items and return null. In the exact scenario this fallback is meant to handle (messages missing turnId), edit/regenerate can fail with "Failed to determine the turn for the edited message" even after a successful refresh.

Useful? React with 👍 / 👎.

@zip700 zip700 marked this pull request as draft April 3, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant