Skip to content

feat(chat): implement edit message functionality#1136

Merged
benoitf merged 1 commit intokortex-hub:mainfrom
fbricon:edit-button-implem
Mar 20, 2026
Merged

feat(chat): implement edit message functionality#1136
benoitf merged 1 commit intokortex-hub:mainfrom
fbricon:edit-button-implem

Conversation

@fbricon
Copy link
Copy Markdown
Contributor

@fbricon fbricon commented Mar 19, 2026

This PR provides support for editing chat messages. In the original Vercel Chatbot UI, the message was edited in place (Claude Desktop does it too), but I prefer Ollama client's approach, where editing happens in a single place, the input box at the bottom, providing a consistent UI (includes all existing and future buttons).

Clicking the edit button on a user message populates the input box with the message text, shows a "Press ESC to cancel editing" hint, and grays out all messages after the edited one. Submitting the edit deletes trailing messages from the database and regenerates the AI response. Just like in Ollama client.

  • Add EditState context for shared editing state across components
  • Add deleteTrailingMessages IPC endpoint and preload exposure
  • Update multimodal-input to handle edit mode (ESC cancel, submit edit)
  • Update preview-message to gray out messages and hide edit buttons
    during editing

Includes the fix from #1134 edit: was merged in main first

See it in action:

edit-message.mp4

Admittedly, there's one thing Claude Desktop does better, which is, instead of deleting trailing messages, it just forks the conversation and allows the user to switch between different branches. But it's a more complex approach that'll certainly require database schema changes. Not impossible to do but I think the current approach is a good starting point. We can certainly implement conversation forking in a subsequent PR, if needed. cc @slemeur

Fixes #1081

@fbricon fbricon requested a review from a team as a code owner March 19, 2026 10:13
@fbricon fbricon requested review from MarsKubeX and benoitf and removed request for a team March 19, 2026 10:13
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds in-chat message editing: a new EditState context, UI changes to enter/cancel/submit edits (grays later messages), preload + main IPC to delete trailing generated messages, input handling to replace the edited message and trigger regeneration, and Playwright page-object updates + tests for edit flows.

Changes

Cohort / File(s) Summary
Main IPC / Preload
packages/main/src/chat/chat-manager.ts, packages/preload/src/index.ts
New IPC channel inference:deleteTrailingMessages handled in main; preload exposes inferenceDeleteTrailingMessages(id): Promise<undefined). Review main-side error propagation and preload API surface.
EditState Hook
packages/renderer/src/lib/chat/hooks/edit-state.svelte.ts
Added exported EditState class with context helpers, editingMessage state, start/cancel methods, and isAfterEditingMessage(messages, message).
Chat Root
packages/renderer/src/lib/chat/components/chat.svelte
Instantiates EditState into Svelte context via EditState.toContext() at chat root.
Message List & Preview
packages/renderer/src/lib/chat/components/messages.svelte, packages/renderer/src/lib/chat/components/messages/preview-message.svelte
PreviewMessage now receives full messages[]; preview uses EditState.fromContext() to compute isGrayed, applies opacity-40 pointer-events-none to later messages, removes local edit-mode state, and updates edit button behavior.
Input / Multimodal
packages/renderer/src/lib/chat/components/multimodal-input.svelte
Integrates EditState: save/restore input when editing, focus textarea, ESC to cancel, on submit delete trailing messages via IPC, replace edited message parts, and call chatClient.regenerate().
Tests & Page Object
tests/playwright/src/model/pages/chat-page.ts, tests/playwright/src/specs/provider-specs/chat-smoke.spec.ts
Added locators and helpers for edit UI and two serial Playwright tests covering edit+cancel and edit+regenerate flows. Review test timing/flakiness around regeneration and IPC effects.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Chat UI
    participant EditState
    participant ChatClient
    participant IPC as Main/ChatManager

    User->>UI: Click "Edit message"
    UI->>EditState: startEditing(message)
    EditState-->>UI: editingMessage set
    UI->>UI: populate textarea, save previous input, gray later messages

    alt Submit edited message
        User->>UI: Submit edited text
        UI->>IPC: invoke inference:deleteTrailingMessages(messageId)
        IPC-->>IPC: delete trailing generated messages
        UI->>ChatClient: replace edited message parts
        UI->>ChatClient: regenerate()
        ChatClient->>IPC: request model regeneration
        UI->>EditState: cancelEditing()
    else Cancel editing (ESC)
        User->>UI: Press ESC
        UI->>EditState: cancelEditing()
        UI->>UI: restore textarea, remove graying
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly describes the main feature being implemented—edit message functionality—matching the primary change across all modified files.
Description check ✅ Passed The description is directly related to the changeset, explaining the edit message feature, implementation approach, design decisions, and linking to the fixed issue.
Linked Issues check ✅ Passed The pull request implements the required functionality to resolve issue #1081: edit message now works correctly with a dedicated UI pattern (edit in input box), eliminating the runtime error and message disappearance bug.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing edit message functionality as specified in issue #1081; no unrelated or out-of-scope modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/renderer/src/lib/chat/components/multimodal-input.svelte`:
- Around line 88-108: The block handling editState.editingMessage clears input
and cancels editing before calling await
window.inferenceDeleteTrailingMessages(editingMessage.id), which can fail and
leave the UI inconsistent; wrap the inferenceDeleteTrailingMessages call and the
subsequent update/regenerate logic in a try/catch, and on catch restore
editState.editingMessage (or call editState.startEditing with the original
editingMessage), restore the input via setInput(savedInput) and savedInput
variable, and surface the error (e.g., rethrow or log) so the user can recover
and the UI remains consistent; ensure chatClient.messages update and
chatClient.regenerate only run on success.

In `@tests/playwright/src/model/pages/chat-page.ts`:
- Around line 321-339: The helper verifyMessagesGrayedAfterIndex currently
matches the edited message by innerHTML which can incorrectly match duplicates;
change the lookup of editedGlobalIndex to compare DOM node identity between
editedMessage and items in allMessages (e.g., use a node identity check via
elementHandle.evaluate(...) with node.isSameNode or Playwright's node
comparison) instead of comparing innerHTML, and if no match is found throw a
clear error rather than leaving editedGlobalIndex as -1 so the trailing-opacity
assertions don't run from index 0; update references to allMessages,
userMessages, editedMessage, and editedGlobalIndex accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6d136b0c-14b3-42cd-b407-6d517d476956

📥 Commits

Reviewing files that changed from the base of the PR and between e7669bf and c48a709.

📒 Files selected for processing (9)
  • packages/main/src/chat/chat-manager.ts
  • packages/preload/src/index.ts
  • packages/renderer/src/lib/chat/components/chat.svelte
  • packages/renderer/src/lib/chat/components/messages.svelte
  • packages/renderer/src/lib/chat/components/messages/preview-message.svelte
  • packages/renderer/src/lib/chat/components/multimodal-input.svelte
  • packages/renderer/src/lib/chat/hooks/edit-state.svelte.ts
  • tests/playwright/src/model/pages/chat-page.ts
  • tests/playwright/src/specs/provider-specs/chat-smoke.spec.ts

Comment on lines +88 to +108
if (editState.editingMessage) {
const editingMessage = editState.editingMessage;
setInput('');
savedInput = '';
editState.cancelEditing();

await window.inferenceDeleteTrailingMessages(editingMessage.id);

const index = chatClient.messages.findIndex(m => m.id === editingMessage.id);
if (index !== -1) {
const updatedMessage = {
...editingMessage,
parts: [{ type: 'text' as const, text }],
};
chatClient.messages = [...chatClient.messages.slice(0, index), updatedMessage];
}

resetHeight();
await chatClient.regenerate();
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.

⚠️ Potential issue | 🟡 Minor

Missing error handling could leave the UI in an inconsistent state.

If inferenceDeleteTrailingMessages fails (e.g., database error, message already deleted), the editing state is already cancelled and the input cleared (lines 90-92), leaving the user with no recovery path. Consider wrapping this in try/catch to restore the editing state on failure.

🛡️ Proposed fix to add error handling
   if (editState.editingMessage) {
     const editingMessage = editState.editingMessage;
+    const originalInput = input;
     setInput('');
     savedInput = '';
     editState.cancelEditing();
 
-    await window.inferenceDeleteTrailingMessages(editingMessage.id);
+    try {
+      await window.inferenceDeleteTrailingMessages(editingMessage.id);
+    } catch (error) {
+      // Restore editing state on failure
+      editState.startEditing(editingMessage);
+      setInput(originalInput);
+      toast.error('Failed to update message. Please try again.');
+      return;
+    }
 
     const index = chatClient.messages.findIndex(m => m.id === editingMessage.id);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderer/src/lib/chat/components/multimodal-input.svelte` around
lines 88 - 108, The block handling editState.editingMessage clears input and
cancels editing before calling await
window.inferenceDeleteTrailingMessages(editingMessage.id), which can fail and
leave the UI inconsistent; wrap the inferenceDeleteTrailingMessages call and the
subsequent update/regenerate logic in a try/catch, and on catch restore
editState.editingMessage (or call editState.startEditing with the original
editingMessage), restore the input via setInput(savedInput) and savedInput
variable, and surface the error (e.g., rethrow or log) so the user can recover
and the UI remains consistent; ensure chatClient.messages update and
chatClient.regenerate only run on success.

Comment on lines +321 to +339
async verifyMessagesGrayedAfterIndex(userMessageIndex: number): Promise<void> {
const allMessages = await this.conversationMessages.all();
const userMessages = await this.userConversationMessages.all();
const editedMessage = userMessages[userMessageIndex];

// Find the position of the edited user message in all messages
let editedGlobalIndex = -1;
for (let i = 0; i < allMessages.length; i++) {
const el = allMessages[i];
if ((await el.innerHTML()) === (await editedMessage.innerHTML())) {
editedGlobalIndex = i;
break;
}
}

// Messages after the edited one should have reduced opacity
for (let i = editedGlobalIndex + 1; i < allMessages.length; i++) {
await expect(allMessages[i]).toHaveClass(/opacity-40/);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Match the edited message by DOM order, not innerHTML().

If two user messages render the same markup, Line 330 will match the first duplicate, so this helper can assert the wrong trailing messages. If nothing matches, editedGlobalIndex stays -1 and the loop starts from 0, which makes the failure misleading.

🛠️ Proposed fix
  async verifyMessagesGrayedAfterIndex(userMessageIndex: number): Promise<void> {
    const allMessages = await this.conversationMessages.all();
-    const userMessages = await this.userConversationMessages.all();
-    const editedMessage = userMessages[userMessageIndex];
-
-    // Find the position of the edited user message in all messages
-    let editedGlobalIndex = -1;
-    for (let i = 0; i < allMessages.length; i++) {
-      const el = allMessages[i];
-      if ((await el.innerHTML()) === (await editedMessage.innerHTML())) {
-        editedGlobalIndex = i;
-        break;
-      }
-    }
+    const editedGlobalIndex = await this.conversationMessages.evaluateAll((nodes, targetUserIndex) => {
+      let currentUserIndex = -1;
+
+      return nodes.findIndex(node => {
+        if ((node as HTMLElement).dataset.role !== 'user') {
+          return false;
+        }
+
+        currentUserIndex += 1;
+        return currentUserIndex === targetUserIndex;
+      });
+    }, userMessageIndex);
+    expect(editedGlobalIndex).toBeGreaterThanOrEqual(0);
 
     // Messages after the edited one should have reduced opacity
     for (let i = editedGlobalIndex + 1; i < allMessages.length; i++) {
       await expect(allMessages[i]).toHaveClass(/opacity-40/);
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async verifyMessagesGrayedAfterIndex(userMessageIndex: number): Promise<void> {
const allMessages = await this.conversationMessages.all();
const userMessages = await this.userConversationMessages.all();
const editedMessage = userMessages[userMessageIndex];
// Find the position of the edited user message in all messages
let editedGlobalIndex = -1;
for (let i = 0; i < allMessages.length; i++) {
const el = allMessages[i];
if ((await el.innerHTML()) === (await editedMessage.innerHTML())) {
editedGlobalIndex = i;
break;
}
}
// Messages after the edited one should have reduced opacity
for (let i = editedGlobalIndex + 1; i < allMessages.length; i++) {
await expect(allMessages[i]).toHaveClass(/opacity-40/);
}
async verifyMessagesGrayedAfterIndex(userMessageIndex: number): Promise<void> {
const allMessages = await this.conversationMessages.all();
const editedGlobalIndex = await this.conversationMessages.evaluateAll((nodes, targetUserIndex) => {
let currentUserIndex = -1;
return nodes.findIndex(node => {
if ((node as HTMLElement).dataset.role !== 'user') {
return false;
}
currentUserIndex += 1;
return currentUserIndex === targetUserIndex;
});
}, userMessageIndex);
expect(editedGlobalIndex).toBeGreaterThanOrEqual(0);
// Messages after the edited one should have reduced opacity
for (let i = editedGlobalIndex + 1; i < allMessages.length; i++) {
await expect(allMessages[i]).toHaveClass(/opacity-40/);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/playwright/src/model/pages/chat-page.ts` around lines 321 - 339, The
helper verifyMessagesGrayedAfterIndex currently matches the edited message by
innerHTML which can incorrectly match duplicates; change the lookup of
editedGlobalIndex to compare DOM node identity between editedMessage and items
in allMessages (e.g., use a node identity check via elementHandle.evaluate(...)
with node.isSameNode or Playwright's node comparison) instead of comparing
innerHTML, and if no match is found throw a clear error rather than leaving
editedGlobalIndex as -1 so the trailing-opacity assertions don't run from index
0; update references to allMessages, userMessages, editedMessage, and
editedGlobalIndex accordingly.

Copy link
Copy Markdown
Contributor

@MarsKubeX MarsKubeX left a comment

Choose a reason for hiding this comment

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

LGTM.

@benoitf
Copy link
Copy Markdown
Contributor

benoitf commented Mar 19, 2026

I've restarted the failing job

@benoitf benoitf force-pushed the edit-button-implem branch from c48a709 to a2c6c4e Compare March 19, 2026 17:23
@benoitf
Copy link
Copy Markdown
Contributor

benoitf commented Mar 19, 2026

clicked on the rebase button as the other pr was merged ( commit included in this pr)

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
tests/playwright/src/model/pages/chat-page.ts (1)

321-339: ⚠️ Potential issue | 🟡 Minor

Use deterministic message indexing instead of innerHTML() matching.

At Line 330, matching by innerHTML() can misidentify the edited message when content repeats. If no match is found, editedGlobalIndex remains -1, and Line 337 starts assertions from index 0, producing misleading failures.

🛠️ Suggested fix
   async verifyMessagesGrayedAfterIndex(userMessageIndex: number): Promise<void> {
     const allMessages = await this.conversationMessages.all();
-    const userMessages = await this.userConversationMessages.all();
-    const editedMessage = userMessages[userMessageIndex];
-
-    // Find the position of the edited user message in all messages
-    let editedGlobalIndex = -1;
-    for (let i = 0; i < allMessages.length; i++) {
-      const el = allMessages[i];
-      if ((await el.innerHTML()) === (await editedMessage.innerHTML())) {
-        editedGlobalIndex = i;
-        break;
-      }
-    }
+    const editedGlobalIndex = await this.conversationMessages.evaluateAll((nodes, targetUserIndex) => {
+      let currentUserIndex = -1;
+      return nodes.findIndex(node => {
+        if ((node as HTMLElement).dataset.role !== 'user') return false;
+        currentUserIndex += 1;
+        return currentUserIndex === targetUserIndex;
+      });
+    }, userMessageIndex);
+    expect(editedGlobalIndex).toBeGreaterThanOrEqual(0);
 
     // Messages after the edited one should have reduced opacity
     for (let i = editedGlobalIndex + 1; i < allMessages.length; i++) {
       await expect(allMessages[i]).toHaveClass(/opacity-40/);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/playwright/src/model/pages/chat-page.ts` around lines 321 - 339, The
test uses innerHTML to match elements which can misidentify duplicates and leave
editedGlobalIndex at -1; modify verifyMessagesGrayedAfterIndex to locate the
edited message deterministically by comparing node identity between arrays (use
ElementHandle.evaluate with node.isSameNode or similar) to set
editedGlobalIndex, add a guard that throws/fails if editedGlobalIndex remains
-1, then proceed with the opacity assertions on conversationMessages; reference
function verifyMessagesGrayedAfterIndex, variables conversationMessages,
userConversationMessages, and editedGlobalIndex.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@tests/playwright/src/model/pages/chat-page.ts`:
- Around line 321-339: The test uses innerHTML to match elements which can
misidentify duplicates and leave editedGlobalIndex at -1; modify
verifyMessagesGrayedAfterIndex to locate the edited message deterministically by
comparing node identity between arrays (use ElementHandle.evaluate with
node.isSameNode or similar) to set editedGlobalIndex, add a guard that
throws/fails if editedGlobalIndex remains -1, then proceed with the opacity
assertions on conversationMessages; reference function
verifyMessagesGrayedAfterIndex, variables conversationMessages,
userConversationMessages, and editedGlobalIndex.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 08a57fe6-fc71-4c44-8a00-21a7b1f272ff

📥 Commits

Reviewing files that changed from the base of the PR and between c48a709 and a2c6c4e.

📒 Files selected for processing (9)
  • packages/main/src/chat/chat-manager.ts
  • packages/preload/src/index.ts
  • packages/renderer/src/lib/chat/components/chat.svelte
  • packages/renderer/src/lib/chat/components/messages.svelte
  • packages/renderer/src/lib/chat/components/messages/preview-message.svelte
  • packages/renderer/src/lib/chat/components/multimodal-input.svelte
  • packages/renderer/src/lib/chat/hooks/edit-state.svelte.ts
  • tests/playwright/src/model/pages/chat-page.ts
  • tests/playwright/src/specs/provider-specs/chat-smoke.spec.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/renderer/src/lib/chat/components/chat.svelte
  • tests/playwright/src/specs/provider-specs/chat-smoke.spec.ts
  • packages/renderer/src/lib/chat/hooks/edit-state.svelte.ts
  • packages/renderer/src/lib/chat/components/multimodal-input.svelte
  • packages/main/src/chat/chat-manager.ts

@benoitf benoitf enabled auto-merge (rebase) March 19, 2026 17:31
@benoitf
Copy link
Copy Markdown
Contributor

benoitf commented Mar 20, 2026

I don't know if broken e2e are related to this change

@fbricon
Copy link
Copy Markdown
Contributor Author

fbricon commented Mar 20, 2026

I don't think so, CHAT-05 is failing, the following tests are not run including the ones from this PR.

@fbricon fbricon force-pushed the edit-button-implem branch from a2c6c4e to 60bb271 Compare March 20, 2026 10:05
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/preload/src/index.ts (1)

1321-1323: Use messageId instead of generic id for bridge clarity.

At Line 1321, naming the argument messageId would reduce accidental misuse (e.g., passing chatId) and makes intent explicit at call sites.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/preload/src/index.ts` around lines 1321 - 1323, Rename the exposed
bridge function parameter from the generic id to messageId for clarity: in the
contextBridge.exposeInMainWorld call for 'inferenceDeleteTrailingMessages'
replace the parameter name id with messageId and update the internal call to
ipcInvoke('inference:deleteTrailingMessages', messageId) (retain the string type
and Promise<undefined> return); ensure the symbol
'inferenceDeleteTrailingMessages' and its ipcInvoke usage are the only places
changed so callers see the clearer parameter name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/main/src/chat/chat-manager.ts`:
- Around line 154-159: The deleteTrailingMessages method currently performs an
irreversible deletion via this.chatQueries.deleteTrailingMessages({ id }) which
risks losing history if subsequent regeneration fails; change the flow so that
deletion is not executed as a standalone operation — either (a) move this logic
into a single main-process "edit" IPC handler that performs delete + save +
regenerate within one transactional operation, or (b) implement a two-phase
approach in chat-manager where deleteTrailingMessages only marks messages as
"to_be_deleted" (or creates a backup copy) and the IPC handler then performs the
actual delete only after regenerate/save succeeds; update callers to invoke the
new atomic handler (e.g., an editChatAndRegenerate method) instead of calling
deleteTrailingMessages directly to ensure rollback/consistency.

---

Nitpick comments:
In `@packages/preload/src/index.ts`:
- Around line 1321-1323: Rename the exposed bridge function parameter from the
generic id to messageId for clarity: in the contextBridge.exposeInMainWorld call
for 'inferenceDeleteTrailingMessages' replace the parameter name id with
messageId and update the internal call to
ipcInvoke('inference:deleteTrailingMessages', messageId) (retain the string type
and Promise<undefined> return); ensure the symbol
'inferenceDeleteTrailingMessages' and its ipcInvoke usage are the only places
changed so callers see the clearer parameter name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6d166cff-7c27-4682-8aa6-c8c238d0ab0e

📥 Commits

Reviewing files that changed from the base of the PR and between a2c6c4e and 60bb271.

📒 Files selected for processing (9)
  • packages/main/src/chat/chat-manager.ts
  • packages/preload/src/index.ts
  • packages/renderer/src/lib/chat/components/chat.svelte
  • packages/renderer/src/lib/chat/components/messages.svelte
  • packages/renderer/src/lib/chat/components/messages/preview-message.svelte
  • packages/renderer/src/lib/chat/components/multimodal-input.svelte
  • packages/renderer/src/lib/chat/hooks/edit-state.svelte.ts
  • tests/playwright/src/model/pages/chat-page.ts
  • tests/playwright/src/specs/provider-specs/chat-smoke.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/renderer/src/lib/chat/hooks/edit-state.svelte.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/renderer/src/lib/chat/components/messages.svelte
  • packages/renderer/src/lib/chat/components/multimodal-input.svelte
  • tests/playwright/src/specs/provider-specs/chat-smoke.spec.ts
  • packages/renderer/src/lib/chat/components/messages/preview-message.svelte
  • tests/playwright/src/model/pages/chat-page.ts

@fbricon
Copy link
Copy Markdown
Contributor Author

fbricon commented Mar 20, 2026

I don't think so, CHAT-05 is failing, the following tests are not run including the ones from this PR.

My bad it's a new test:

✘ 22 [Ollama-Provider] › tests/playwright/src/specs/provider-specs/chat-smoke.spec.ts:264:3 › Chat page navigation › [CHAT-12] Edit button enters editing mode and ESC cancels it @smoke (3.1m)

It passes locally, so I need to figure it out.

@jeffmaury jeffmaury self-requested a review March 20, 2026 10:46
@fbricon fbricon force-pushed the edit-button-implem branch from 60bb271 to 6087007 Compare March 20, 2026 11:28
@fbricon fbricon disabled auto-merge March 20, 2026 12:34
@fbricon fbricon force-pushed the edit-button-implem branch from 6087007 to a687c61 Compare March 20, 2026 12:34
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/renderer/src/lib/chat/hooks/edit-state.svelte.ts`:
- Around line 21-25: In isAfterEditingMessage, guard against missing lookups:
after computing editIndex and messageIndex (from messages.findIndex in
isAfterEditingMessage), return false if either index is -1 before performing the
comparison; this ensures you don't treat unrelated messages as "after" when one
or both IDs aren't found.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a01d06ef-75df-4bf4-8353-9b7208fdf94f

📥 Commits

Reviewing files that changed from the base of the PR and between 6087007 and a687c61.

📒 Files selected for processing (9)
  • packages/main/src/chat/chat-manager.ts
  • packages/preload/src/index.ts
  • packages/renderer/src/lib/chat/components/chat.svelte
  • packages/renderer/src/lib/chat/components/messages.svelte
  • packages/renderer/src/lib/chat/components/messages/preview-message.svelte
  • packages/renderer/src/lib/chat/components/multimodal-input.svelte
  • packages/renderer/src/lib/chat/hooks/edit-state.svelte.ts
  • tests/playwright/src/model/pages/chat-page.ts
  • tests/playwright/src/specs/provider-specs/chat-smoke.spec.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/renderer/src/lib/chat/components/chat.svelte
  • tests/playwright/src/model/pages/chat-page.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/main/src/chat/chat-manager.ts
  • packages/renderer/src/lib/chat/components/messages.svelte
  • tests/playwright/src/specs/provider-specs/chat-smoke.spec.ts
  • packages/preload/src/index.ts
  • packages/renderer/src/lib/chat/components/multimodal-input.svelte

Comment on lines +21 to +25
isAfterEditingMessage(messages: UIMessage[], message: UIMessage): boolean {
if (!this.editingMessage) return false;
const editIndex = messages.findIndex(m => m.id === this.editingMessage!.id);
const messageIndex = messages.findIndex(m => m.id === message.id);
return messageIndex > editIndex;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard missing-index cases before comparing message order.

If either lookup returns -1, the current comparison can incorrectly mark messages as “after,” which can disable/grayout unrelated messages. Return false when either index is missing.

Proposed fix
   isAfterEditingMessage(messages: UIMessage[], message: UIMessage): boolean {
     if (!this.editingMessage) return false;
     const editIndex = messages.findIndex(m => m.id === this.editingMessage!.id);
     const messageIndex = messages.findIndex(m => m.id === message.id);
+    if (editIndex === -1 || messageIndex === -1) return false;
     return messageIndex > editIndex;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isAfterEditingMessage(messages: UIMessage[], message: UIMessage): boolean {
if (!this.editingMessage) return false;
const editIndex = messages.findIndex(m => m.id === this.editingMessage!.id);
const messageIndex = messages.findIndex(m => m.id === message.id);
return messageIndex > editIndex;
isAfterEditingMessage(messages: UIMessage[], message: UIMessage): boolean {
if (!this.editingMessage) return false;
const editIndex = messages.findIndex(m => m.id === this.editingMessage!.id);
const messageIndex = messages.findIndex(m => m.id === message.id);
if (editIndex === -1 || messageIndex === -1) return false;
return messageIndex > editIndex;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderer/src/lib/chat/hooks/edit-state.svelte.ts` around lines 21 -
25, In isAfterEditingMessage, guard against missing lookups: after computing
editIndex and messageIndex (from messages.findIndex in isAfterEditingMessage),
return false if either index is -1 before performing the comparison; this
ensures you don't treat unrelated messages as "after" when one or both IDs
aren't found.

@fbricon fbricon force-pushed the edit-button-implem branch 2 times, most recently from 64ecaa7 to 6116246 Compare March 20, 2026 13:29
Clicking the edit button on a user message populates the input box with
the message text, shows a "Press ESC to cancel editing" hint, and grays
out all messages after the edited one. Submitting the edit deletes
trailing messages from the database and regenerates the AI response.

- Add EditState context for shared editing state across components
- Add deleteTrailingMessages IPC endpoint and preload exposure
- Update multimodal-input to handle edit mode (ESC cancel, submit edit)
- Update preview-message to gray out messages and hide edit buttons
  during editing
- Add e2e tests for edit cancel (CHAT-12) and edit submit (CHAT-13)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fred Bricon <fbricon@gmail.com>
@fbricon fbricon force-pushed the edit-button-implem branch from 6116246 to 9d2a0fa Compare March 20, 2026 14:28
@fbricon
Copy link
Copy Markdown
Contributor Author

fbricon commented Mar 20, 2026

the e2e tests finally pass, are we good to merge?

@benoitf benoitf merged commit 2a09493 into kortex-hub:main Mar 20, 2026
15 checks passed
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.

Message disappears when clicking "Edit message"

3 participants