diff --git a/packages/app/src/components/prompt-input-v2.tsx b/packages/app/src/components/prompt-input-v2.tsx index be6ae4920..9a279a62e 100644 --- a/packages/app/src/components/prompt-input-v2.tsx +++ b/packages/app/src/components/prompt-input-v2.tsx @@ -145,7 +145,16 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps): t: (key, params) => language.t(key as Parameters[0], params as never), }), ) - const designPlaceholder = () => promptDesignPlaceholder(mode(), placeholder()) + // The design placeholder takes the translate callback: the helper calls it + // in the non-shell branch, so the 2-arg form passes undefined there — + // minified "n is not a function" on every non-shell composer render. + // #929 (59b447e7) fixed this amicode-side; the fork→overlay sync ff7b69c8 + // regressed it back to the 2-arg call. Mirrored to the sync source per + // harmoniqs/amicode#964 so the next sync brings the fix, not the regression. + const designPlaceholder = () => + promptDesignPlaceholder(mode(), placeholder(), (key, params) => + language.t(key as Parameters[0], params as never), + ) const historyComments = () => { const byID = new Map(comments.all().map((item) => [`${item.file}\n${item.id}`, item] as const)) diff --git a/packages/app/src/components/prompt-input/placeholder.test.ts b/packages/app/src/components/prompt-input/placeholder.test.ts index d4caead0d..cb0575bb2 100644 --- a/packages/app/src/components/prompt-input/placeholder.test.ts +++ b/packages/app/src/components/prompt-input/placeholder.test.ts @@ -1,5 +1,37 @@ import { describe, expect, test } from "bun:test" -import { promptPlaceholder } from "./placeholder" +import { promptDesignPlaceholder, promptPlaceholder } from "./placeholder" + +// harmoniqs/amicode#964 — the design placeholder takes the translate callback +// (the #929 contract, mirrored to the sync source). A 2-arg call passes +// undefined as the translate callback, which the helper calls in the +// non-shell branch: the "n is not a function" failure Aaron hit live +// post-cutover 2026-09-10. +describe("promptDesignPlaceholder", () => { + const t = (key: string, params?: Record) => { + let out = key + for (const [k, v] of Object.entries(params ?? {})) out = out.split(`{{${k}}}`).join(v) + return out + } + + test("returns the shell placeholder verbatim in shell mode", () => { + expect(promptDesignPlaceholder("shell", "git status", t)).toBe("git status") + }) + + test("translates through the callback in normal mode", () => { + expect(promptDesignPlaceholder("normal", "fallback", t)).toBe( + "ui.promptInput.placeholder.normal", + ) + }) + + test("the translate callback is REQUIRED — the regressed 2-arg call throws", () => { + expect(() => + (promptDesignPlaceholder as unknown as (mode: "normal", placeholder: string) => string)( + "normal", + "fallback", + ), + ).toThrow(/is not a function/) + }) +}) describe("promptPlaceholder", () => { const t = (key: string, params?: Record) => `${key}${params?.example ? `:${params.example}` : ""}` diff --git a/packages/app/src/components/prompt-input/placeholder.ts b/packages/app/src/components/prompt-input/placeholder.ts index d4ffda06b..f5f19ae14 100644 --- a/packages/app/src/components/prompt-input/placeholder.ts +++ b/packages/app/src/components/prompt-input/placeholder.ts @@ -14,8 +14,17 @@ export function promptPlaceholder(input: PromptPlaceholderInput) { return input.t("prompt.placeholder.normal", { example: input.example }) } -export function promptDesignPlaceholder(mode: PromptPlaceholderInput["mode"], placeholder: string) { +// Mirrored from amicode's overlay (upstream v1.18.29 contract): the design +// placeholder takes the translate callback and calls it in the non-shell +// branch — a 2-arg call passes undefined there and throws "n is not a +// function" on every non-shell composer render. The amicode hard-coded brand +// line ("Ask Amico anything, ...") is superseded by the translated key the +// ui locales already carry. See harmoniqs/amicode#929/#964. +export function promptDesignPlaceholder( + mode: PromptPlaceholderInput["mode"], + placeholder: string, + t: PromptPlaceholderInput["t"], +) { if (mode === "shell") return placeholder - // amicode: brand the composer prompt - return "Ask Amico anything, / for commands, @ for context..." + return t("ui.promptInput.placeholder.normal", { slash: "/", at: "@" }) }