From 5eef9496a0178f3ec5fef471613ef76cc0cdad60 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Mon, 13 Apr 2026 19:23:26 -1000 Subject: [PATCH] =?UTF-8?q?feat:=20inline=20edit=20UX=20=E2=80=94=20edit?= =?UTF-8?q?=20message=20in-place=20with=20Save/Cancel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ui/goose2/src/features/chat/ui/ChatInput.tsx | 28 +-- ui/goose2/src/features/chat/ui/ChatView.tsx | 48 ++-- .../src/features/chat/ui/MessageBubble.tsx | 220 ++++++++++++------ .../src/features/chat/ui/MessageTimeline.tsx | 9 + 4 files changed, 185 insertions(+), 120 deletions(-) diff --git a/ui/goose2/src/features/chat/ui/ChatInput.tsx b/ui/goose2/src/features/chat/ui/ChatInput.tsx index 55de6fcff5..5a85cc54ba 100644 --- a/ui/goose2/src/features/chat/ui/ChatInput.tsx +++ b/ui/goose2/src/features/chat/ui/ChatInput.tsx @@ -1,6 +1,6 @@ import { useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo } from "react"; import { open } from "@tauri-apps/plugin-dialog"; -import { Pencil, X } from "lucide-react"; +import { X } from "lucide-react"; import { useTranslation } from "react-i18next"; import type { AcpProvider } from "@/shared/api/acp"; import type { Persona } from "@/shared/types/agents"; @@ -64,10 +64,6 @@ interface ChatInputProps { }) => void; contextTokens?: number; contextLimit?: number; - /** Message ID currently being edited (non-null = edit mode active). */ - editingMessageId?: string | null; - /** Cancel edit mode — clears editing state and restores input. */ - onCancelEdit?: () => void; } export function ChatInput({ @@ -98,8 +94,6 @@ export function ChatInput({ onCreateProject, contextTokens = 0, contextLimit = 0, - editingMessageId = null, - onCancelEdit, }: ChatInputProps) { const { t } = useTranslation("chat"); const [text, setTextRaw] = useState(initialValue); @@ -196,8 +190,7 @@ export function ChatInput({ return () => observer.disconnect(); }, []); - // Focus textarea on mount and when entering edit mode - useEffect(() => textareaRef.current?.focus(), [editingMessageId]); + useEffect(() => textareaRef.current?.focus(), []); const handleSend = useCallback(() => { if (!canSend) { @@ -423,23 +416,6 @@ export function ChatInput({ )} - {editingMessageId && ( -
- - - {t("edit.label")} - - -
- )} -