Conversation
f7e3502 to
6a5d19f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a5d19ff3d
ℹ️ 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".
6a5d19f to
5374dfb
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5374dfbc92
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3fc723c0c8
ℹ️ 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".
| await sendMessage( | ||
| textContent.text, | ||
| text, | ||
| targetPersonaId | ||
| ? { id: targetPersonaId, name: targetPersonaName } | ||
| : undefined, |
There was a problem hiding this comment.
Route retry sends through persona/provider switching logic
This retry path calls sendMessage directly with only targetPersonaId, but sendMessage still selects the provider from providerOverride (the current session provider). If a user retries an older message after switching providers, the resend can go to the wrong backend even though the persona metadata is preserved. ChatView.handleSend already contains the provider-switch flow for persona changes, so bypassing it here can change model/provider behavior for retries.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Follow-up. P2 — routing retry through persona/provider switching. Not blocking merge.
| } else if (e.key === "Enter" && !e.shiftKey) { | ||
| e.preventDefault(); | ||
| handleEditSave(); |
There was a problem hiding this comment.
Ignore Enter during IME composition in inline edit
Pressing Enter in this handler always saves (unless Shift is held), but there is no composition guard (isComposing). For CJK/IME users, Enter is used to confirm a candidate, so this can unexpectedly submit the edit and truncate/re-send conversation history mid-composition. The Enter branch should be skipped while composition is active.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 87bfe5a82 — added !e.nativeEvent.isComposing guard to the Enter key handler in the inline edit handleEditKeyDown callback so CJK/IME composition is not interrupted.
I think this PR doesn't do that exactly (based on some quick pairing with an LLM). The goose backend seems to delete the session history if you edit a message so the user sees the session as if it was rewound and that message was changed, but the LLM sees the full history. So if you edit a message the LLM thinks this is the history: I'm fine for us to merge this in as the behaviour, but let's make that clearer in the PR description. I'm not sure there is a better way at the moment without the proper ACP truncate+fork feature that seems to be coming soon. Also, it seems all user messages are editable, so the difference between what the user sees and what the LLM thinks the history is could be very different. Thinking about it a bit more: is it worth having the feature if it doesnt actually revise the LLM's history? Is there a benefit? |
- Add retryMessage(messageId) to useChat — truncates from the target message onward and resends the preceding user message. Works for both user and assistant messages. - Add editMessage(messageId) to useChat — truncates from the target user message onward and populates the chat input draft with the original text for revision. - Wire onRetryMessage and onEditMessage from ChatView through MessageTimeline to MessageBubble. - Remove role gate so retry icon shows on both user and assistant messages (edit remains user-only).
ChatInput initialized local text via useState(initialValue) which only reads the prop on mount. Subsequent changes (e.g. cancel edit then re-edit) were ignored. Added useEffect to re-sync setTextRaw when initialValue changes. Uses setTextRaw (not setText) to avoid redundant onDraftChange callback since the store already holds the correct value.
The focus effect only ran on mount. Now keyed on editingMessageId so the textarea receives focus whenever the user clicks edit.
Pivot from bottom-ChatInput editing to inline editing. When clicking edit, the message bubble transforms into a textarea with Save and Cancel buttons. Enter saves, Escape cancels. - MessageBubble: add isEditing mode with inline textarea, auto-resize, focus, and keyboard shortcuts (Enter=save, Escape=cancel) - MessageTimeline: pass editingMessageId, onSaveEdit, onCancelEdit - ChatView: add handleSaveEdit (truncate + send), wire to timeline - ChatInput: remove edit indicator bar, editingMessageId prop, and edit-mode focus effect
When editing, the container breaks out of the 80% user bubble constraint to span the full timeline width with a subtle bg-muted/40 background. Hides the user avatar during edit. Adds hint text indicating editing will start a new conversation.
1. handleSaveEdit now forces chatState to idle via store and defers sendMessage via pendingEditSend ref + useEffect, avoiding the stale closure where sendMessage would bail on old chatState. 2. Split focus into a separate useEffect keyed only on isEditing so it doesn't fire on every keystroke and reset cursor position. 3. Removed dead setDraft call in editMessage — inline edit reads text directly from the message, not the draft store. 4. Removed unused edit.label and edit.cancel i18n keys.
User messages render as rounded bg-muted bubbles on the content div only — action buttons and timestamp sit outside. Max width capped at 640px. Avatar removed. Outer group/gap-3 hover structure preserved.
…tions Swap the fragile CSS group-hover opacity pattern for Radix HoverCard which handles hover intent, open/close delays, and portal rendering. Actions appear below the bubble on hover via stripped HoverCardContent (no border, shadow, padding, or background). Removes group class.
Force-override base HoverCardContent styles (shadow, border, bg, animations, rounded, padding) with !important. Add mb-6 to user content div so the portal actions float in the reserved gap.
Remove !animate-none override so fade and slide transitions from the base HoverCardContent are preserved. All other chrome overrides (shadow, border, bg, padding, rounded) remain.
8 new tests: inline textarea pre-fill, save/cancel clicks, Enter/Escape keys, disabled save on empty text, edit hint visibility, retry click. Updated existing hover test to trigger HoverCard via userEvent.hover. All 49 tests green (25 MessageBubble + 24 ChatInput).
If editIndex is -1 (message deleted between click and save), bail early instead of sending to untruncated history. Clears edit state.
…cale 1. Added variant='bare' to HoverCardContent — strips all chrome. MessageBubble uses it instead of six !important overrides. 2. Removed duplicate height calc from ChatInput handleInput — useLayoutEffect([text]) already covers it. 3. Edit state (editText, editTextareaRef, effects) gated on role==='user' via canEdit flag. 4. Added edit.hint to Spanish locale.
…pped Replace py-4 with pt-4 pb-12 on the timeline inner container to give the last message enough room for HoverCard actions to render.
Bare variant now z-40 instead of z-50. Popovers and dropdowns at z-50 will always paint on top of message action hovercards.
Removed size=sm and h-7/px-3/text-xs overrides from Save/Cancel. Hint text now left-aligned, buttons right-anchored via ml-auto.
Replace px-4 py-2.5 with p-3 for consistent 12px padding on all sides.
…dead code Bug 1: retryMessage no longer truncates history on image-only messages. Content is validated BEFORE truncation — if there's nothing to re-send (no text, no images, no attachments), the operation bails cleanly. Bug 2: retryMessage and handleSaveEdit now forward attachments and persona from the original message. Added rebuildAttachmentDrafts() to reconstruct ChatAttachmentDraft[] from stored message content (ImageContent blocks) and metadata.attachments (file/directory refs). Dead code: Removed MessageBranch components (~210 lines) from message.tsx and messageBranch i18n keys from en/es common.json. Unused forking artifacts. Tests: +6 new tests covering image-only retry safety, attachment preservation, assistant edit-button exclusion, Shift+Enter multiline, and chatStore editingMessageId set/clear/cleanup. 430 tests green.
…rrow TS union Blocker 1: Narrow ChatAttachmentDraft union in attachments.test.ts before asserting base64 — fixes TS compilation error that broke CI. Blocker 2: Unify pendingEditSend + deferredSend into a single pendingSend ref with a reason discriminator. Edit path now uses flushSync to commit state synchronously and calls sendMessage immediately — no timing gap, no race condition. Persona path keeps the deferred effect with the unified ref. Blocker 3: Re-add !isUser guard on retry button in MessageBubble so retry only shows on assistant messages (edit covers user messages). Bonus: TODO comment on rebuildAttachmentDrafts dedup heuristic explaining the assumption that all images produce both a content block and metadata entry.
…o providerId) The ACP architecture rewrite on main removed the providerId parameter from acpSendMessage. Four retry/edit tests still passed the old "goose" providerId argument — remove it so assertions match the new 3-arg signature: acpSendMessage(sessionId, prompt, options).
…before_message_id Uses timestamp bridge: truncate thread_messages by message_id to get the created_timestamp, then truncate messages table by that timestamp. This handles the different ID schemes (tmsg_ vs msg_) across tables.
3fc723c to
a5d12c2
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5d12c2e7d
ℹ️ 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".
Thread truncation metadata from edit/retry UI through the WebSocket API: - acpApi.ts: prompt() accepts optional _meta parameter - acp.ts: acpSendMessage passes truncateBeforeMessageId via _meta - useChat.ts: sendMessage accepts and forwards truncation param, retryMessage passes the user message ID for backend truncation - ChatView.tsx: handleSaveEdit passes truncation ID, uses deferred pattern for edit-during-streaming (BLOCKER 2), removes flushSync hack and dead 'edit' reason from pendingSend Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39c6149bf9
ℹ️ 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".
…ncation
P0 fix: the frontend generated client-side UUIDs for messages while the
backend independently generated tmsg_{uuid} IDs. When edit/retry sent
the client UUID as truncate_before_message_id, the backend lookup found
nothing and never truncated.
Fix:
- Frontend sends messageId on PromptRequest (ACP unstable_message_id)
- Server uses args.message_id as the thread message ID instead of
generating a new one, so both sides share the same ID
- Replay chunks now carry message_id so loaded sessions also have
correct IDs for future edit/retry operations
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: def6cca68a
ℹ️ 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".
Five test cases covering: - truncate target + subsequent messages - truncate from first message clears all - nonexistent message ID is a no-op - truncate last message only - truncation is scoped to the target thread Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c5aa09f4fb
ℹ️ 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".
- Add isComposing guard to Enter key handler in MessageBubble inline edit so CJK/IME users don't accidentally save mid-composition - Dismiss queued follow-up messages before forcing idle state in handleSaveEdit to prevent useMessageQueue from auto-sending against the truncated timeline Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Addressing PR commentsmatt2e's comment (LLM sees full history)Fixed. Commits Codex bot comments — triageAlready fixed in this PR:
Acknowledged, follow-up:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 87bfe5a822
ℹ️ 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".
Three follow-ups from PR review: 1. Atomic truncation: new ThreadManager.truncate_and_append() wraps truncate + append in a single BEGIN IMMEDIATE transaction so a crash can't leave a half-truncated conversation. 2. Precise session truncation: changed truncate_conversation to use autoincrement row IDs for ordering instead of second-resolution timestamps, eliminating the risk of over-deleting messages that share the same second. 3. Snapshot+restore on failure: retryMessage and handleSaveEdit now snapshot the message array before optimistic truncation and restore it if sendMessage throws, preventing a broken UI state on network errors. Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 103865b8f1
ℹ️ 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".
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
PR Comment SweepHuman reviewer (matt2e)LLM sees full history on edit — ✅ Fixed. Option A commits ( Latest Codex review (
|
| # | Comment | Status |
|---|---|---|
| P1 | Route edit resend through persona/provider switch flow | Follow-up — edge case where edit bypasses handlePersonaChange. Only matters if user switches provider AND edits an old message. Narrow window, no data loss. |
| P2 | Ignore Escape during IME composition | ✅ Fixed in c7e96edc4 — isComposing guard on Escape, matching the Enter guard. |
Earlier Codex reviews (already addressed)
| Comment | Status |
|---|---|
| Use backend message IDs for truncation | ✅ Fixed in def6cca68 — frontend sends messageId on PromptRequest |
| Propagate truncate marker when retrying | ✅ Fixed in 39c6149bf — retryMessage passes truncateMessageId |
| Avoid dropping edit resend after stopStreaming | ✅ Fixed in 39c6149bf — deferred pendingEditSend ref pattern |
| Keep unsent draft when cancelling edit | ✅ Fixed in 143ab8676 — cancelEdit no longer calls clearDraft |
| Make truncation atomic | ✅ Fixed in 103865b8f — truncate_and_append() single transaction |
| Precise session truncation boundary | ✅ Fixed in 103865b8f — autoincrement ID ordering |
| Restore history on send failure | ✅ Fixed in 103865b8f — snapshot+restore in retry and edit |
| Clear queued sends before forcing idle | ✅ Fixed in 87bfe5a82 — dismissQueuedMessage before setChatState |
| IME Enter guard | ✅ Fixed in 87bfe5a82 — isComposing check |
| Rehydrate pathless file attachments | ✅ Fixed in cbf6732ee |
| Deduplicate image attachments | Follow-up — edge case with mixed image+file attachments |
| Skip image metadata when rebuilding | Follow-up — hasImageDrafts heuristic has a TODO tracking it |
All PR comments addressedmatt2e's comment (LLM sees full history)Fixed. This was accurate when the comment was written, but the Option A commits (
The LLM now sees the same truncated history as the user. The truncation + new message append is atomic (single Codex review comments (18 total)
Additional fix from this sweep
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c7e96edc40
ℹ️ 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".
| const attachmentDrafts = rebuildAttachmentDrafts(userMessage); | ||
| const hasContent = text.trim() || attachmentDrafts.length > 0; | ||
|
|
There was a problem hiding this comment.
Preserve replay metadata before retrying loaded messages
retryMessage reconstructs resend context entirely from the in-memory userMessage, but replayed history messages are built from chunk events that do not include the original attachment/persona metadata. After reopening a session, retry/edit can therefore resend only text and fall back to the currently selected persona/provider instead of the original target context. Please restore attachment/persona metadata during replay (or disable retry/edit when that metadata is unavailable) so loaded-history retries are faithful.
Useful? React with 👍 / 👎.
| pendingEditSend.current = { | ||
| text, | ||
| truncateMessageId: messageId, | ||
| persona, | ||
| attachments, |
There was a problem hiding this comment.
Persist deferred edit resend across component teardown
In the busy-state edit path, the code truncates local history and then parks the resend payload in the component-local pendingEditSend ref. If the chat view unmounts before the idle effect drains that ref (for example, user navigates away right after save), the pending resend is lost and the conversation stays truncated without the edited replacement message. The deferred resend state should live in shared/session state or be dispatched in a teardown-safe queue.
Useful? React with 👍 / 👎.
Pass userMessage.id from the local Zustand store through to acpSendMessage so the backend stores the same UUID. Without this, the frontend and backend have different IDs for the same user message, causing truncate_before_message_id lookups to find nothing — edit/retry truncation silently fails and messages zombie back on session reload. Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: becf37f2a9
ℹ️ 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".
| if let Some((row_id, created_ts)) = row { | ||
| // 2. Delete from thread_messages |
There was a problem hiding this comment.
Reject truncate requests when target message is missing
truncate_and_append only deletes history when the lookup finds a matching row, but it still appends the new message unconditionally afterward. If the client sends a stale/unknown truncate_message_id (for example after replay/id drift), retry/edit becomes append-only on the backend while the UI has already truncated locally, so deleted turns reappear after reload and model context is wrong. This path should return an error (or skip append) when the truncate target is not found.
Useful? React with 👍 / 👎.
| "DELETE FROM messages WHERE session_id = ? AND id >= \ | ||
| (SELECT id FROM messages WHERE session_id = ? AND created_timestamp >= ? ORDER BY id ASC LIMIT 1)", | ||
| ) |
There was a problem hiding this comment.
Use exact message mapping for session truncate boundary
Fresh evidence: the session-side delete boundary is still chosen with created_timestamp >= ? and the first matching row id, which cannot distinguish multiple messages created in the same second. In that case, truncating from a later message can delete earlier same-second messages that should be kept. The boundary needs a stable per-message mapping/tie-breaker (not timestamp-only bridging) to avoid over-truncation.
Useful? React with 👍 / 👎.
Category: new-feature
User Impact: Users can now edit sent messages in-place and retry from any point in a conversation, with all attachments, images, and persona overrides preserved through the re-send.
Problem: The Goose 2 desktop app had no way to correct a sent message or retry a failed exchange. Users who made a typo or wanted to refine their prompt had to start a new conversation.
Solution: Adds client-side inline edit and per-message retry using truncate-and-resend. Edit mode renders a textarea over the original message bubble with Save/Cancel controls and keyboard shortcuts (Enter to save, Escape to cancel, Shift+Enter for newline). Retry identifies the originating user message, reconstructs its full context (text, images, file attachments, persona), and re-sends after truncation. A Radix HoverCard replaces the old CSS group-hover for the action toolbar, fixing z-index and animation issues. Session forking support can be layered on as a follow-up.
File changes
ui/goose2/src/features/chat/hooks/useChat.ts
Added
retryMessage,editMessage, andcancelEditto the chat hook.retryMessagevalidates content and reconstructs attachment drafts before truncating history, then forwards text, persona, and attachments tosendMessage.editMessageenters non-destructive edit mode; truncation only happens on save.ui/goose2/src/features/chat/hooks/tests/useChat.test.ts
Added 14 hook-level tests: 5 for
retryMessage(truncation, persona preservation, assistant-to-user lookup, streaming/thinking guards), 3 foreditMessage(state, streaming guard, assistant-role guard), 1 forcancelEdit, 3 for attachment preservation (image-only, file metadata, mixed content), and 2 forretryLastMessagedelegation.ui/goose2/src/features/chat/lib/attachments.ts
Added
rebuildAttachmentDrafts()— reconstructsChatAttachmentDraft[]from a stored message'sImageContentblocks andmetadata.attachments, enabling retry and edit to preserve the original message's full attachment context.ui/goose2/src/features/chat/stores/chatStore.ts
Added
editingMessageIdBySessionstate andsetEditingMessageIdaction. Editing state is scoped per-session and cleaned up on session cleanup.ui/goose2/src/features/chat/stores/tests/chatStore.test.ts
Added tests for
setEditingMessageIdset, clear, and session cleanup.ui/goose2/src/features/chat/ui/ChatView.tsx
Added
handleSaveEdit— stops streaming if active, reads persona and attachments from the original message before truncation, then defers the re-send via a ref until chat state returns to idle. WiresonRetryMessage,onEditMessage,onSaveEdit,onCancelEdit, andeditingMessageIdtoMessageTimeline.ui/goose2/src/features/chat/ui/ChatInput.tsx
Syncs textarea text state when
initialValueprop changes (needed for edit mode to populate the input). AddedinitialValuesync effect.ui/goose2/src/features/chat/ui/MessageBubble.tsx
Replaced CSS group-hover action toolbar with Radix HoverCard. Added inline edit UI: textarea overlay with Save/Cancel buttons, keyboard handling (Enter/Escape/Shift+Enter), empty-text guard. User bubbles get muted background and max-width constraint.
ui/goose2/src/features/chat/ui/MessageTimeline.tsx
Passes
onRetryMessage(assistant messages),onEditMessage(user messages),editingMessageId,onSaveEdit, andonCancelEditthrough toMessageBubble.ui/goose2/src/features/chat/ui/tests/ChatInput.test.tsx
Added test for
initialValuesync — verifies textarea updates when the prop changes.ui/goose2/src/features/chat/ui/tests/MessageBubble.test.tsx
Added 10 tests: inline edit keyboard shortcuts (Enter saves, Escape cancels, Shift+Enter allows newline), empty-text guard, edit/cancel button rendering, assistant edit-button exclusion, user edit-button presence, and HoverCard hover behavior.
ui/goose2/src/shared/i18n/locales/en/chat.json
Added
edit.hint,edit.textareaAriaLabelkeys for the inline edit UI.ui/goose2/src/shared/i18n/locales/en/common.json
Removed dead
messageBranchi18n keys (unused forking infrastructure).ui/goose2/src/shared/i18n/locales/es/chat.json
Added Spanish translations for
edit.hint,edit.textareaAriaLabel.ui/goose2/src/shared/i18n/locales/es/common.json
Removed dead
messageBranchi18n keys (unused forking infrastructure).ui/goose2/src/shared/ui/ai-elements/message.tsx
Removed
MessageBranchand 5 sub-components (~210 lines of dead code). Cleaned 11 unused imports.ui/goose2/src/shared/ui/hover-card.tsx
Added
barevariant to HoverCard — no background, border, or shadow — used by the message action toolbar.Reproduction Steps
Demo
Screen.Recording.2026-04-14.at.12.47.56.PM.mov