feat(desktop): add hover actions for Mastra user messages#2117
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds user-message edit/resend UX and optimistic restart flow: new editor, draft extraction, UI state, message-list props; server TRPC mutation and runtime helper to clone/switch threads and resend edited payload; title-generation improvements and zod input for restart. Changes
Sequence DiagramsequenceDiagram
participant User as User
participant UI as ChatMastraInterface
participant List as ChatMastraMessageList
participant Editor as UserMessageEditor
participant Server as ChatMastraService
participant Runtime as Runtime
participant Store as Memory Store
User->>UI: Click Edit / Resend on message
UI->>UI: set editing state (editingUserMessageId / pendingRestartUserMessage)
UI->>List: render with editing props
List->>Editor: show initialDraft
User->>Editor: edit content/files, press Send
Editor->>UI: onSubmitEditedUserMessage(payload)
UI->>UI: create optimistic message, mark submitting
UI->>Server: call restartFromMessage mutation
Server->>Runtime: restartRuntimeFromUserMessage(input)
Runtime->>Store: getRuntimeMemoryStore()
Runtime->>Runtime: clone thread up to target message
Runtime->>Runtime: switch thread / optionally switch model
Runtime->>Runtime: sendMessage(edited payload)
Runtime-->>Server: result
Server-->>UI: mutation result
UI->>List: clear editing state, update visibleMessages
List-->>User: display restarted/updated conversation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsx">
<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsx:226">
P2: The action bar is hidden with opacity only, so invisible buttons remain interactable and can trigger unintended resend/edit/copy clicks. Disable pointer events while hidden and re-enable on hover/focus.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| return null; | ||
| })} | ||
| {showActions ? ( | ||
| <div className="opacity-0 transition-opacity group-hover/msg:opacity-100 group-focus-within/msg:opacity-100"> |
There was a problem hiding this comment.
P2: The action bar is hidden with opacity only, so invisible buttons remain interactable and can trigger unintended resend/edit/copy clicks. Disable pointer events while hidden and re-enable on hover/focus.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsx, line 226:
<comment>The action bar is hidden with opacity only, so invisible buttons remain interactable and can trigger unintended resend/edit/copy clicks. Disable pointer events while hidden and re-enable on hover/focus.</comment>
<file context>
@@ -259,6 +222,43 @@ export function UserMessage({
return null;
})}
+ {showActions ? (
+ <div className="opacity-0 transition-opacity group-hover/msg:opacity-100 group-focus-within/msg:opacity-100">
+ <MessageActions className="rounded-lg border border-border bg-background/95 p-1 shadow-sm backdrop-blur-xs">
+ <MessageAction
</file context>
| <div className="opacity-0 transition-opacity group-hover/msg:opacity-100 group-focus-within/msg:opacity-100"> | |
| <div className="pointer-events-none opacity-0 transition-opacity group-hover/msg:pointer-events-auto group-hover/msg:opacity-100 group-focus-within/msg:pointer-events-auto group-focus-within/msg:opacity-100"> |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsx (1)
41-45: Compute the draft lazily instead of on every render.Line 43 eagerly rebuilds the resend/edit draft for every user-message render, including inline-image data-URL materialization. In longer threads, that makes unrelated rerenders proportional to attachment size. Prefer deriving
showActionsfrommessage.contentand only callinggetUserMessageDraft()inside the edit/resend handlers, or at least memoize it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsx` around lines 41 - 45, The component currently calls getUserMessageDraft(message) on every render which eagerly materializes inline-image data-URLs and causes heavy rerenders; remove the eager draft calculation and either (A) compute draft lazily by calling getUserMessageDraft(...) only inside the edit/resend handlers (e.g., onEdit / onResend) where draft is needed, or (B) memoize it with useMemo so it only recalculates when message.content or message.attachments change; also derive showActions from message.content (not from draft) so rendering decisions don't trigger draft materialization. Ensure you update any handlers that referenced the draft variable to obtain it from the lazy call or memoized value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsx`:
- Around line 41-45: The component currently calls getUserMessageDraft(message)
on every render which eagerly materializes inline-image data-URLs and causes
heavy rerenders; remove the eager draft calculation and either (A) compute draft
lazily by calling getUserMessageDraft(...) only inside the edit/resend handlers
(e.g., onEdit / onResend) where draft is needed, or (B) memoize it with useMemo
so it only recalculates when message.content or message.attachments change; also
derive showActions from message.content (not from draft) so rendering decisions
don't trigger draft materialization. Ensure you update any handlers that
referenced the draft variable to obtain it from the lazy call or memoized value.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b368d64f-fa82-4bfd-870e-bd89ee06e3df
📒 Files selected for processing (8)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/ChatMastraInterface.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/ChatMastraMessageList.test.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/ChatMastraMessageList.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/ChatMastraMessageList.types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/utils/getUserMessageDraft/getUserMessageDraft.test.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/utils/getUserMessageDraft/getUserMessageDraft.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/utils/getUserMessageDraft/index.ts
🚀 Preview Deployment🔗 Preview Links
Preview updates automatically with new commits |
There was a problem hiding this comment.
3 issues found across 16 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/ChatMastraInterface.tsx">
<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/ChatMastraInterface.tsx:837">
P1: `restartFromUserMessage` rethrows after handling the error, which causes unhandled promise rejections in the edit flow because the editor submits with `void onSubmit(...)` and no catch.
(Based on your team's feedback about handling async calls with proper await and catch.) [FEEDBACK_USED]</violation>
</file>
<file name="packages/chat-mastra/src/server/trpc/utils/runtime/title-generation.ts">
<violation number="1" location="packages/chat-mastra/src/server/trpc/utils/runtime/title-generation.ts:32">
P1: Duplicate title-generation utility: This file is an exact byte-for-byte copy of `packages/chat/src/host/title-generation/title-generation.ts`</violation>
</file>
<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsx">
<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsx:28">
P2: Depending on the full `initialDraft` object in the effect causes the editor state to reset on any parent re-render because a new draft object is created each render. That can wipe in-progress edits while chat updates. Consider memoizing the draft in `UserMessage` or keying the reset effect to a stable identifier (like the message id/content) so it only runs when the edited message actually changes.
(Based on your team's feedback about narrowing React effect dependencies.) [FEEDBACK_USED]</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| @@ -0,0 +1,71 @@ | |||
| type TitleModel = unknown; | |||
There was a problem hiding this comment.
P1: Duplicate title-generation utility: This file is an exact byte-for-byte copy of packages/chat/src/host/title-generation/title-generation.ts
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/chat-mastra/src/server/trpc/utils/runtime/title-generation.ts, line 32:
<comment>Duplicate title-generation utility: This file is an exact byte-for-byte copy of `packages/chat/src/host/title-generation/title-generation.ts`</comment>
<file context>
@@ -0,0 +1,71 @@
+ tracingContext?: Record<string, unknown>;
+ };
+
+export async function generateTitleFromMessage(
+ params: GenerateTitleFromMessageParams,
+): Promise<string | null> {
</file context>
There was a problem hiding this comment.
🧹 Nitpick comments (5)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsx (1)
145-165: Consider extracting a type guard for message part handling.The inline type casting to
rawPartis verbose but necessary due to message content type variations. For improved readability, consider extracting this into a dedicated type guard or helper function.♻️ Optional refactor to extract type guard
// Add near the top of the file or in a utils file interface RawMessagePart { type?: string; data?: string; filename?: string; mediaType?: string; mimeType?: string; } function isFilePart(part: MastraMessagePart): part is MastraMessagePart & RawMessagePart { const rawPart = part as RawMessagePart; return part.type === "image" || rawPart.type === "file"; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsx` around lines 145 - 165, The message part handling in UserMessage.tsx currently casts parts inline and checks types repeatedly; extract a type guard helper (e.g., isFilePart) that narrows MastraMessagePart to a shape with optional fields (data, filename, mediaType, mimeType) and use it in the map callback to replace the rawPart casting and the conditional (part.type !== "image" && rawPart.type !== "file"), then read data and mediaType safely from the narrowed type before returning null when data is missing.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsx (2)
25-28: Consider stabilizing the initialDraft dependency.The
useEffectdepends on theinitialDraftobject reference. If the parent component doesn't memoize this prop, the effect will re-run unnecessarily on every parent render, resetting user edits.Verify that the parent component (
UserMessage.tsx) provides a stable reference forinitialDraft, or consider comparing individual properties:♻️ Alternative: compare by value instead of reference
+import { useRef } from "react"; + export function UserMessageEditor({ initialDraft, // ... }: UserMessageEditorProps) { + const prevDraftRef = useRef(initialDraft); const [text, setText] = useState(initialDraft.text); const [files, setFiles] = useState<FileUIPart[]>(initialDraft.files); useEffect(() => { + // Only reset if the draft actually changed (by message ID or content) + if (prevDraftRef.current === initialDraft) return; + prevDraftRef.current = initialDraft; setText(initialDraft.text); setFiles(initialDraft.files); }, [initialDraft]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsx` around lines 25 - 28, The effect in UserMessageEditor.tsx uses the whole initialDraft object as a dependency causing unnecessary resets; either have the parent (UserMessage.tsx) memoize initialDraft so its reference is stable, or change the useEffect dependency to specific values (initialDraft.text and initialDraft.files) and only call setText/setFiles when those values actually change; locate the useEffect that calls setText and setFiles and update the dependency strategy accordingly to prevent spurious re-renders.
46-97: Extract duplicated payload construction logic.The submission payload construction is duplicated between the
onKeyDownhandler (lines 53-65) and the Save buttononClick(lines 84-96). This violates DRY and risks inconsistency if one is updated without the other.♻️ Proposed fix to extract submission handler
export function UserMessageEditor({ initialDraft, isSubmitting, onCancel, onSubmit, }: UserMessageEditorProps) { const [text, setText] = useState(initialDraft.text); const [files, setFiles] = useState<FileUIPart[]>(initialDraft.files); useEffect(() => { setText(initialDraft.text); setFiles(initialDraft.files); }, [initialDraft]); const canSubmit = Boolean(text.trim() || files.length > 0); + const handleSubmit = useCallback(() => { + if (!canSubmit || isSubmitting) return; + void onSubmit({ + content: text, + ...(files.length > 0 + ? { + files: files.map((file) => ({ + data: file.url, + mediaType: file.mediaType, + filename: file.filename, + uploaded: false as const, + })), + } + : {}), + }); + }, [canSubmit, files, isSubmitting, onSubmit, text]); return ( <div className="flex w-full max-w-[85%] flex-col gap-3 rounded-2xl border border-border bg-background px-3 py-3 shadow-sm"> {/* ... attachments ... */} <textarea value={text} onChange={(event) => setText(event.currentTarget.value)} onKeyDown={(event) => { if (event.key !== "Enter" || event.shiftKey) return; event.preventDefault(); - if (!canSubmit || isSubmitting) return; - void onSubmit({ - content: text, - ...(files.length > 0 - ? { - files: files.map((file) => ({ - data: file.url, - mediaType: file.mediaType, - filename: file.filename, - uploaded: false as const, - })), - } - : {}), - }); + handleSubmit(); }} placeholder="Edit message..." className="..." /> <div className="flex justify-end gap-2"> <Button ... >Cancel</Button> <Button type="button" size="sm" - onClick={() => - void onSubmit({ - content: text, - ...(files.length > 0 - ? { - files: files.map((file) => ({ - data: file.url, - mediaType: file.mediaType, - filename: file.filename, - uploaded: false as const, - })), - } - : {}), - }) - } + onClick={handleSubmit} disabled={!canSubmit || isSubmitting} > {/* ... button content ... */} </Button> </div> </div> ); }Note: Add
useCallbackto the imports from React.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsx` around lines 46 - 97, The submission payload construction is duplicated in the textarea onKeyDown handler and the Save Button onClick; extract this into a shared useCallback (e.g., handleSubmit or buildSubmitPayload) inside the UserMessageEditor component and call it from both places. Implement useCallback import from React, move the payload logic (content: text and conditional files.map(...) producing {data, mediaType, filename, uploaded: false}) into that function, have it perform the same canSubmit/isSubmitting check or return null if not allowed, and then call void onSubmit(handleSubmit()) from both the onKeyDown handler (after event.preventDefault()) and the Save button onClick to avoid duplication and keep behavior identical.packages/chat-mastra/src/server/trpc/service.ts (1)
251-274: Consider returning a value for consistency withsendMessagemutation.The
sendMessagemutation at line 248 returnsruntime.harness.sendMessage(input.payload), butrestartFromMessagereturns nothing (void). While the underlyingrestartRuntimeFromUserMessageisPromise<void>, the inconsistency could cause issues if callers expect a return value similar tosendMessage.If no return value is intentional, this is fine. However, if the frontend needs confirmation or metadata about the restart operation, consider returning a status object.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chat-mastra/src/server/trpc/service.ts` around lines 251 - 274, The restartFromMessage mutation currently performs actions but returns nothing while the nearby sendMessage mutation returns runtime.harness.sendMessage(...), creating an inconsistency; update restartFromMessage (the procedure named restartFromMessage in service.ts) to return a useful value—either await and return the Promise from runtime.harness.sendMessage(...) if applicable, or return a simple status object (e.g., { ok: true, messageId: input.messageId }) after awaiting restartRuntimeFromUserMessage(runtime, ...); ensure you still call getOrCreateRuntime, onUserPromptSubmit, restartRuntimeFromUserMessage, and fire generateAndSetTitle but make restartFromMessage return the chosen value to match sendMessage’s behavior.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/ChatMastraInterface.tsx (1)
957-962: **Note:isEditSubmittingreflects broader "awaiting assistant" state.**The propisEditSubmittingis set toisAwaitingAssistant, which represents any assistant activity (running, submitted, or streaming), not specifically whether an edit is being submitted. This works correctly for the intended UX (disable edit actions while assistant is responding), but the naming could be more precise.This is a minor naming consideration - the behavior is correct.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/ChatMastraInterface.tsx` around lines 957 - 962, The prop name isEditSubmitting is misleading because it receives the broader isAwaitingAssistant state; update the prop to a clearer name (e.g., isEditDisabled or isEditBlocked) where it's passed in this component (the JSX passing isEditSubmitting={isAwaitingAssistant}) and update the corresponding prop/interface and all consumers (e.g., ChatMastraInterface and its props/type definition) to use the new name; alternatively, if you prefer to keep the prop name, add a short inline comment next to the prop assignment explaining that isAwaitingAssistant intentionally disables edit actions while the assistant is active to avoid confusion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/ChatMastraInterface.tsx`:
- Around line 957-962: The prop name isEditSubmitting is misleading because it
receives the broader isAwaitingAssistant state; update the prop to a clearer
name (e.g., isEditDisabled or isEditBlocked) where it's passed in this component
(the JSX passing isEditSubmitting={isAwaitingAssistant}) and update the
corresponding prop/interface and all consumers (e.g., ChatMastraInterface and
its props/type definition) to use the new name; alternatively, if you prefer to
keep the prop name, add a short inline comment next to the prop assignment
explaining that isAwaitingAssistant intentionally disables edit actions while
the assistant is active to avoid confusion.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsx`:
- Around line 25-28: The effect in UserMessageEditor.tsx uses the whole
initialDraft object as a dependency causing unnecessary resets; either have the
parent (UserMessage.tsx) memoize initialDraft so its reference is stable, or
change the useEffect dependency to specific values (initialDraft.text and
initialDraft.files) and only call setText/setFiles when those values actually
change; locate the useEffect that calls setText and setFiles and update the
dependency strategy accordingly to prevent spurious re-renders.
- Around line 46-97: The submission payload construction is duplicated in the
textarea onKeyDown handler and the Save Button onClick; extract this into a
shared useCallback (e.g., handleSubmit or buildSubmitPayload) inside the
UserMessageEditor component and call it from both places. Implement useCallback
import from React, move the payload logic (content: text and conditional
files.map(...) producing {data, mediaType, filename, uploaded: false}) into that
function, have it perform the same canSubmit/isSubmitting check or return null
if not allowed, and then call void onSubmit(handleSubmit()) from both the
onKeyDown handler (after event.preventDefault()) and the Save button onClick to
avoid duplication and keep behavior identical.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsx`:
- Around line 145-165: The message part handling in UserMessage.tsx currently
casts parts inline and checks types repeatedly; extract a type guard helper
(e.g., isFilePart) that narrows MastraMessagePart to a shape with optional
fields (data, filename, mediaType, mimeType) and use it in the map callback to
replace the rawPart casting and the conditional (part.type !== "image" &&
rawPart.type !== "file"), then read data and mediaType safely from the narrowed
type before returning null when data is missing.
In `@packages/chat-mastra/src/server/trpc/service.ts`:
- Around line 251-274: The restartFromMessage mutation currently performs
actions but returns nothing while the nearby sendMessage mutation returns
runtime.harness.sendMessage(...), creating an inconsistency; update
restartFromMessage (the procedure named restartFromMessage in service.ts) to
return a useful value—either await and return the Promise from
runtime.harness.sendMessage(...) if applicable, or return a simple status object
(e.g., { ok: true, messageId: input.messageId }) after awaiting
restartRuntimeFromUserMessage(runtime, ...); ensure you still call
getOrCreateRuntime, onUserPromptSubmit, restartRuntimeFromUserMessage, and fire
generateAndSetTitle but make restartFromMessage return the chosen value to match
sendMessage’s behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 43d2d9dd-8095-40ac-84d2-81118fbb00af
📒 Files selected for processing (16)
apps/desktop/src/lib/trpc/routers/workspaces/utils/ai-name.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/ChatMastraInterface.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/ChatMastraMessageList.test.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/ChatMastraMessageList.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/ChatMastraMessageList.types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/UserMessage.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/useOrderedSections/useOrderedSections.tsxpackages/chat-mastra/src/server/trpc/service.tspackages/chat-mastra/src/server/trpc/utils/runtime/index.tspackages/chat-mastra/src/server/trpc/utils/runtime/runtime.test.tspackages/chat-mastra/src/server/trpc/utils/runtime/runtime.tspackages/chat-mastra/src/server/trpc/utils/runtime/title-generation.tspackages/chat-mastra/src/server/trpc/zod.tspackages/chat/src/host/title-generation/title-generation.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsx (1)
21-27: Minor inconsistency:filesnot synced wheninitialDraftchanges.
textis synced viauseEffectwheninitialDraft.textchanges, butfiles(line 23) is assigned once and never updated. If the component can receive a newinitialDraftwithout remounting, attachments would become stale.For consistency, either sync both or derive both without effects:
Option: Derive both using useMemo
- const [text, setText] = useState(initialDraft.text); - const inputRef = useRef<HTMLInputElement>(null); - const files = initialDraft.files; - - useEffect(() => { - setText(initialDraft.text); - }, [initialDraft.text]); + const [text, setText] = useState(initialDraft.text); + const inputRef = useRef<HTMLInputElement>(null); + + useEffect(() => { + setText(initialDraft.text); + }, [initialDraft.text]); + + const files = initialDraft.files;If
initialDraftidentity is stable per edit session (component remounts for new message), the current approach is acceptable.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsx` around lines 21 - 27, The component currently sets files = initialDraft.files once, causing attachments to become stale when initialDraft updates; update the component so files is kept in sync with initialDraft by either (A) making files a state variable (e.g., files + setFiles) and adding a useEffect alongside the existing one to call setFiles(initialDraft.files) when initialDraft.files changes, or (B) derive both text and files from initialDraft via useMemo (e.g., memoize {text, files} from initialDraft) and remove the separate useEffect/state mismatch; target the UserMessageEditor component and the symbols text, files, initialDraft, useEffect, and useState when applying the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsx`:
- Around line 21-27: The component currently sets files = initialDraft.files
once, causing attachments to become stale when initialDraft updates; update the
component so files is kept in sync with initialDraft by either (A) making files
a state variable (e.g., files + setFiles) and adding a useEffect alongside the
existing one to call setFiles(initialDraft.files) when initialDraft.files
changes, or (B) derive both text and files from initialDraft via useMemo (e.g.,
memoize {text, files} from initialDraft) and remove the separate useEffect/state
mismatch; target the UserMessageEditor component and the symbols text, files,
initialDraft, useEffect, and useState when applying the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 78b7bb2a-ef7b-482a-8535-2765992d2750
📒 Files selected for processing (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/ChatMastraInterface.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/components/ChatMastraMessageList/components/UserMessage/components/UserMessageEditor/UserMessageEditor.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/ChatMastraInterface.tsx
Summary
Testing
Summary by cubic
Adds hover actions to Mastra user messages—Resend, Edit, Copy—and a restart-from-message flow with a stable overlay that keeps message order while streaming. Also adds server/runtime support and a small UI polish to make actions clear and accessible.
New Features
Refactors
Written for commit 71723f0. Summary will update on new commits.
Summary by CodeRabbit