From 2403a39e1a61155b48ba4d48b8e7322350c9e4a6 Mon Sep 17 00:00:00 2001 From: Clay Delk Date: Wed, 29 Apr 2026 21:48:57 -0400 Subject: [PATCH] fix: copy and content improvements in goose2 (#8886) Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com> --- .../src/features/agents/ui/AgentsView.tsx | 6 ++- .../chat/lib/chatInputPlaceholder.test.ts | 45 +++++++++++++++++++ .../features/chat/lib/chatInputPlaceholder.ts | 15 +++++++ .../src/features/chat/lib/newChat.test.ts | 12 +++-- .../features/chat/lib/sessionTitle.test.ts | 6 +++ .../src/features/chat/lib/sessionTitle.ts | 13 +++++- .../features/chat/stores/chatSessionStore.ts | 7 ++- ui/goose2/src/features/chat/ui/ChatInput.tsx | 10 ++++- .../chat/ui/__tests__/ChatInput.test.tsx | 4 +- .../chat/ui/__tests__/FilesList.test.tsx | 4 +- .../src/features/home/ui/HomeScreen.test.tsx | 4 +- .../src/features/projects/ui/ProjectsView.tsx | 6 ++- .../ui/__tests__/CreateProjectDialog.test.tsx | 4 +- .../settings/ui/AppearanceSettings.tsx | 3 -- .../features/settings/ui/DoctorCheckRow.tsx | 14 +++--- .../features/settings/ui/DoctorSettings.tsx | 3 -- .../features/settings/ui/ModelProviderRow.tsx | 1 + .../settings/ui/ProvidersSettings.tsx | 3 -- .../features/settings/ui/SettingsModal.tsx | 6 ++- .../__tests__/modelProviderHelpers.test.tsx | 34 ++++++++++++++ .../settings/ui/modelProviderHelpers.tsx | 14 ++++++ .../ui/__tests__/SidebarChatRow.test.tsx | 2 +- .../src/features/skills/ui/SkillsDialogs.tsx | 4 +- .../ui/__tests__/CreateSkillDialog.test.tsx | 4 +- .../skills/ui/__tests__/SkillsView.test.tsx | 6 ++- .../src/shared/i18n/locales/en/agents.json | 4 +- .../src/shared/i18n/locales/en/chat.json | 21 ++++----- .../src/shared/i18n/locales/en/common.json | 8 ++-- .../src/shared/i18n/locales/en/projects.json | 18 ++++---- .../src/shared/i18n/locales/en/sessions.json | 8 ++-- .../src/shared/i18n/locales/en/settings.json | 38 ++++++++-------- .../src/shared/i18n/locales/en/sidebar.json | 6 +-- .../src/shared/i18n/locales/en/skills.json | 16 +++---- .../src/shared/i18n/locales/es/agents.json | 4 +- .../src/shared/i18n/locales/es/chat.json | 13 +++--- .../src/shared/i18n/locales/es/projects.json | 4 +- .../src/shared/i18n/locales/es/sessions.json | 4 +- .../src/shared/i18n/locales/es/settings.json | 28 ++++++------ .../src/shared/i18n/locales/es/sidebar.json | 4 +- .../src/shared/i18n/locales/es/skills.json | 4 +- ui/goose2/tests/e2e/personas.spec.ts | 18 +++++--- ui/goose2/tests/e2e/skills.spec.ts | 4 +- ui/goose2/tests/e2e/smoke.spec.ts | 2 +- 43 files changed, 289 insertions(+), 145 deletions(-) create mode 100644 ui/goose2/src/features/chat/lib/chatInputPlaceholder.test.ts create mode 100644 ui/goose2/src/features/settings/ui/__tests__/modelProviderHelpers.test.tsx diff --git a/ui/goose2/src/features/agents/ui/AgentsView.tsx b/ui/goose2/src/features/agents/ui/AgentsView.tsx index f4894248..9b0f47b1 100644 --- a/ui/goose2/src/features/agents/ui/AgentsView.tsx +++ b/ui/goose2/src/features/agents/ui/AgentsView.tsx @@ -299,7 +299,11 @@ export function AgentsView() { > - {t("view.deleteTitle")} + + {t("view.deleteTitle", { + name: deletingPersona?.displayName ?? "", + })} + {t("view.deleteDescription", { name: deletingPersona?.displayName ?? "", diff --git a/ui/goose2/src/features/chat/lib/chatInputPlaceholder.test.ts b/ui/goose2/src/features/chat/lib/chatInputPlaceholder.test.ts new file mode 100644 index 00000000..24137e7c --- /dev/null +++ b/ui/goose2/src/features/chat/lib/chatInputPlaceholder.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; +import { + getChatInputAgentLabel, + getChatInputPlaceholder, +} from "./chatInputPlaceholder"; + +const t = (key: string, options?: { agent: string }) => + options?.agent ? `${key}:${options.agent}` : key; + +describe("getChatInputAgentLabel", () => { + it("uses the active persona display name when present", () => { + expect(getChatInputAgentLabel("Reviewer", "Goose")).toBe("Reviewer"); + }); + + it("falls back to the provider display name", () => { + expect(getChatInputAgentLabel(undefined, "Goose")).toBe("Goose"); + }); + + it("preserves explicit persona names with the default suffix", () => { + expect(getChatInputAgentLabel("Ops (Default)", "Goose (Default)")).toBe( + "Ops (Default)", + ); + }); + + it("removes the default suffix from provider fallback labels", () => { + expect(getChatInputAgentLabel(undefined, "Goose (Default)")).toBe("Goose"); + }); +}); + +describe("getChatInputPlaceholder", () => { + it("uses the agent label in the default placeholder", () => { + expect(getChatInputPlaceholder(t, "Goose", false, false)).toBe( + "input.placeholder:Goose", + ); + }); + + it("uses voice status placeholders while recording or transcribing", () => { + expect(getChatInputPlaceholder(t, "Goose", true, false)).toBe( + "toolbar.voiceInputRecording", + ); + expect(getChatInputPlaceholder(t, "Goose", false, true)).toBe( + "toolbar.voiceInputTranscribing", + ); + }); +}); diff --git a/ui/goose2/src/features/chat/lib/chatInputPlaceholder.ts b/ui/goose2/src/features/chat/lib/chatInputPlaceholder.ts index 2deb66c8..5568031f 100644 --- a/ui/goose2/src/features/chat/lib/chatInputPlaceholder.ts +++ b/ui/goose2/src/features/chat/lib/chatInputPlaceholder.ts @@ -1,3 +1,18 @@ +const DEFAULT_LABEL_SUFFIX = " (Default)"; + +export function getChatInputAgentLabel( + personaDisplayName: string | undefined, + providerDisplayName: string, +): string { + if (personaDisplayName) { + return personaDisplayName; + } + + return providerDisplayName.endsWith(DEFAULT_LABEL_SUFFIX) + ? providerDisplayName.slice(0, -DEFAULT_LABEL_SUFFIX.length) + : providerDisplayName; +} + export function getChatInputPlaceholder( t: (key: string, options?: { agent: string }) => string, agent: string, diff --git a/ui/goose2/src/features/chat/lib/newChat.test.ts b/ui/goose2/src/features/chat/lib/newChat.test.ts index 202b44e7..1073cd22 100644 --- a/ui/goose2/src/features/chat/lib/newChat.test.ts +++ b/ui/goose2/src/features/chat/lib/newChat.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import type { Message } from "@/shared/types/messages"; import { findExistingDraft } from "./newChat"; import type { ChatSession } from "../stores/chatSessionStore"; @@ -8,7 +9,7 @@ function makeSession( ): ChatSession { return { id, - title: "New Chat", + title: "New chat", createdAt: "2026-04-01T00:00:00.000Z", updatedAt: "2026-04-01T00:00:00.000Z", messageCount: 0, @@ -30,7 +31,7 @@ describe("findExistingDraft", () => { draftsBySession: { "alpha-draft": "alpha draft" }, messagesBySession: {}, request: { - title: "New Chat", + title: "New chat", projectId: "alpha", }, }), @@ -111,7 +112,12 @@ describe("findExistingDraft", () => { draftsBySession: {}, messagesBySession: { "alpha-session": [ - { id: "msg-1", role: "user", content: "hello" } as any, + { + id: "msg-1", + role: "user", + created: 1, + content: [{ type: "text", text: "hello" }], + } satisfies Message, ], }, request: { diff --git a/ui/goose2/src/features/chat/lib/sessionTitle.test.ts b/ui/goose2/src/features/chat/lib/sessionTitle.test.ts index 62cbcc54..788eddb5 100644 --- a/ui/goose2/src/features/chat/lib/sessionTitle.test.ts +++ b/ui/goose2/src/features/chat/lib/sessionTitle.test.ts @@ -4,6 +4,7 @@ import { getDisplaySessionTitle, getEditableSessionTitle, getSessionTitleFromDraft, + isDefaultChatTitle, isSessionTitleUnchanged, } from "./sessionTitle"; @@ -17,6 +18,11 @@ describe("sessionTitle", () => { ); }); + it("treats the ACP title-case default title as the default title", () => { + expect(isDefaultChatTitle("New Chat")).toBe(true); + expect(getDisplaySessionTitle("New Chat", "Nuevo chat")).toBe("Nuevo chat"); + }); + it("treats the localized default title as unchanged while the sentinel is still internal", () => { expect( isSessionTitleUnchanged("Nuevo chat", DEFAULT_CHAT_TITLE, "Nuevo chat"), diff --git a/ui/goose2/src/features/chat/lib/sessionTitle.ts b/ui/goose2/src/features/chat/lib/sessionTitle.ts index de99e0f0..88030e52 100644 --- a/ui/goose2/src/features/chat/lib/sessionTitle.ts +++ b/ui/goose2/src/features/chat/lib/sessionTitle.ts @@ -1,9 +1,10 @@ import type { ChatAttachmentDraft } from "@/shared/types/messages"; -export const DEFAULT_CHAT_TITLE = "New Chat"; +export const DEFAULT_CHAT_TITLE = "New chat"; +const ACP_DEFAULT_CHAT_TITLE = "New Chat"; export function isDefaultChatTitle(title: string): boolean { - return title === DEFAULT_CHAT_TITLE; + return title === DEFAULT_CHAT_TITLE || title === ACP_DEFAULT_CHAT_TITLE; } function attachmentKindLabel(kind: ChatAttachmentDraft["kind"], count: number) { @@ -17,6 +18,14 @@ function attachmentKindLabel(kind: ChatAttachmentDraft["kind"], count: number) { } } +// The goose ACP backend uses "New Chat" (title case) as its default — normalize to ours. +export function normalizeAcpTitle( + title: string | null | undefined, +): string | undefined { + if (!title) return undefined; + return title === ACP_DEFAULT_CHAT_TITLE ? DEFAULT_CHAT_TITLE : title; +} + export function getSessionTitleFromDraft( text: string, attachments?: ChatAttachmentDraft[], diff --git a/ui/goose2/src/features/chat/stores/chatSessionStore.ts b/ui/goose2/src/features/chat/stores/chatSessionStore.ts index de7d37d5..1fa40c6e 100644 --- a/ui/goose2/src/features/chat/stores/chatSessionStore.ts +++ b/ui/goose2/src/features/chat/stores/chatSessionStore.ts @@ -5,7 +5,10 @@ import { type AcpSessionInfo, } from "@/shared/api/acp"; import type { Session } from "@/shared/types/chat"; -import { DEFAULT_CHAT_TITLE } from "@/features/chat/lib/sessionTitle"; +import { + DEFAULT_CHAT_TITLE, + normalizeAcpTitle, +} from "@/features/chat/lib/sessionTitle"; import { archiveSession as acpArchiveSession, unarchiveSession as acpUnarchiveSession, @@ -97,7 +100,7 @@ function acpSessionToChatSession(session: AcpSessionInfo): ChatSession { return { id: session.sessionId, acpSessionId: session.sessionId, - title: session.title ?? "Untitled", + title: normalizeAcpTitle(session.title) ?? "Untitled", projectId: session.projectId ?? undefined, providerId: session.providerId ?? undefined, personaId: session.personaId ?? undefined, diff --git a/ui/goose2/src/features/chat/ui/ChatInput.tsx b/ui/goose2/src/features/chat/ui/ChatInput.tsx index 51feb03f..26abd164 100644 --- a/ui/goose2/src/features/chat/ui/ChatInput.tsx +++ b/ui/goose2/src/features/chat/ui/ChatInput.tsx @@ -5,7 +5,10 @@ import { attachmentSnapshotsMatch, skillDraftSnapshotsMatch, } from "../lib/chatInputSnapshots"; -import { getChatInputPlaceholder } from "../lib/chatInputPlaceholder"; +import { + getChatInputAgentLabel, + getChatInputPlaceholder, +} from "../lib/chatInputPlaceholder"; import { cn } from "@/shared/lib/cn"; import { Badge } from "@/shared/ui/badge"; import { Popover, PopoverAnchor } from "@/shared/ui/popover"; @@ -330,7 +333,10 @@ export function ChatInput({ const providerDisplayName = providers.find((provider) => provider.id === selectedProvider)?.label ?? formatProviderLabel(selectedProvider); - const agentDisplayName = activePersona?.displayName ?? providerDisplayName; + const agentDisplayName = getChatInputAgentLabel( + activePersona?.displayName, + providerDisplayName, + ); const resolvedCurrentModel = useMemo(() => { if (currentModel) { return currentModel; diff --git a/ui/goose2/src/features/chat/ui/__tests__/ChatInput.test.tsx b/ui/goose2/src/features/chat/ui/__tests__/ChatInput.test.tsx index 9ca9f8b2..9933a480 100644 --- a/ui/goose2/src/features/chat/ui/__tests__/ChatInput.test.tsx +++ b/ui/goose2/src/features/chat/ui/__tests__/ChatInput.test.tsx @@ -94,9 +94,7 @@ describe("ChatInput", () => { it("renders with default placeholder", () => { render(); expect( - screen.getByPlaceholderText( - "Message Goose, @ to mention agents or skills", - ), + screen.getByPlaceholderText("Chat with Goose or @ mention an agent"), ).toBeInTheDocument(); }); diff --git a/ui/goose2/src/features/chat/ui/__tests__/FilesList.test.tsx b/ui/goose2/src/features/chat/ui/__tests__/FilesList.test.tsx index a0c6ba77..8106ae2f 100644 --- a/ui/goose2/src/features/chat/ui/__tests__/FilesList.test.tsx +++ b/ui/goose2/src/features/chat/ui/__tests__/FilesList.test.tsx @@ -39,9 +39,7 @@ describe("FilesList", () => { render(); expect( - screen.getByText( - "Project files are unavailable until a project with working directories is assigned.", - ), + screen.getByText("Files will show here after you assign a project."), ).toBeInTheDocument(); }); diff --git a/ui/goose2/src/features/home/ui/HomeScreen.test.tsx b/ui/goose2/src/features/home/ui/HomeScreen.test.tsx index 14390446..b50bd5ce 100644 --- a/ui/goose2/src/features/home/ui/HomeScreen.test.tsx +++ b/ui/goose2/src/features/home/ui/HomeScreen.test.tsx @@ -183,9 +183,7 @@ describe("HomeScreen", () => { it("renders the chat input placeholder with default agent name when no persona selected", () => { renderHome(); expect( - screen.getByPlaceholderText( - "Message Goose, @ to mention agents or skills", - ), + screen.getByPlaceholderText("Chat with Goose or @ mention an agent"), ).toBeInTheDocument(); }); diff --git a/ui/goose2/src/features/projects/ui/ProjectsView.tsx b/ui/goose2/src/features/projects/ui/ProjectsView.tsx index debe5e8f..4796f37e 100644 --- a/ui/goose2/src/features/projects/ui/ProjectsView.tsx +++ b/ui/goose2/src/features/projects/ui/ProjectsView.tsx @@ -279,7 +279,11 @@ export function ProjectsView({ onStartChat }: ProjectsViewProps) { > - {t("view.deleteTitle")} + + {t("view.deleteTitle", { + name: deletingProject?.name ?? "", + })} + {t("view.deleteDescription", { name: deletingProject?.name ?? "", diff --git a/ui/goose2/src/features/projects/ui/__tests__/CreateProjectDialog.test.tsx b/ui/goose2/src/features/projects/ui/__tests__/CreateProjectDialog.test.tsx index 17b72059..89156b86 100644 --- a/ui/goose2/src/features/projects/ui/__tests__/CreateProjectDialog.test.tsx +++ b/ui/goose2/src/features/projects/ui/__tests__/CreateProjectDialog.test.tsx @@ -155,13 +155,13 @@ describe("CreateProjectDialog", () => { />, ); - expect(screen.getByText("Edit Project")).toBeInTheDocument(); + expect(screen.getByText("Edit project")).toBeInTheDocument(); }); it("shows New Project title without editingProject", () => { render(); - expect(screen.getByText("New Project")).toBeInTheDocument(); + expect(screen.getByText("New project")).toBeInTheDocument(); }); it("populates the prompt editor with working dirs and prompt text", () => { diff --git a/ui/goose2/src/features/settings/ui/AppearanceSettings.tsx b/ui/goose2/src/features/settings/ui/AppearanceSettings.tsx index a98111be..8424ffc6 100644 --- a/ui/goose2/src/features/settings/ui/AppearanceSettings.tsx +++ b/ui/goose2/src/features/settings/ui/AppearanceSettings.tsx @@ -60,9 +60,6 @@ export function AppearanceSettings() {

{t("appearance.title")}

-

- {t("appearance.description")} -

diff --git a/ui/goose2/src/features/settings/ui/DoctorCheckRow.tsx b/ui/goose2/src/features/settings/ui/DoctorCheckRow.tsx index 4489c9e4..fbcd62a9 100644 --- a/ui/goose2/src/features/settings/ui/DoctorCheckRow.tsx +++ b/ui/goose2/src/features/settings/ui/DoctorCheckRow.tsx @@ -124,21 +124,19 @@ export function DoctorCheckRow({ check, onFixed }: DoctorCheckRowProps) { {t("settings:doctor.runFix")} - - {check.fixCommand} + + {t("settings:doctor.runFixDescription")} + + {check.fixCommand} + {fixError &&

{fixError}

} {t("common:actions.cancel")} -