From 277fa3b27cea382dc62bb45c795f106549f46bfa Mon Sep 17 00:00:00 2001 From: Aaron Trowbridge Date: Thu, 10 Sep 2026 10:42:59 -0400 Subject: [PATCH] fix(app): restore the 3-arg promptDesignPlaceholder translate callback (mirror of amicode #929/#964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provenance: amicode#964 — the fork→overlay sync (ff7b69c8) regressed amicode#929's fix (59b447e7) because the fork (this repo, the sync source) never carried it: prompt-input-v2.tsx called promptDesignPlaceholder with 2 args while the helper amicode materializes (upstream v1.18.29) takes 3 and calls the translate callback in the non-shell branch — every non-shell composer render threw minified 'n is not a function'. Aaron hit it live post-cutover 2026-09-10; the amicode overlay is fixed (harmoniqs/amicode 964-reconciliation). Until the sync tooling carries an amicode-fixes-are-canonical rule, the fork must carry what the overlay carries: - prompt-input-v2.tsx: the 3-arg call with the language.t adapter (the overlay's exact hunk) - prompt-input/placeholder.ts: aligned to the upstream v1.18.29 3-arg contract — the translate callback replaces the hard-coded 'Ask Amico anything...' brand line; the ui locales already carry 'ui.promptInput.placeholder.normal', and the overlay/deployed app has used the translated string since the v1.18.29 re-base - placeholder.test.ts: bun tests pinning the 3-arg contract (shell verbatim, normal translated, the regressed 2-arg call throws 'is not a function') Verified: bun test placeholder.test.ts 7 pass; bun run typecheck (tsgo -b) clean in packages/app. --- .../app/src/components/prompt-input-v2.tsx | 11 +++++- .../prompt-input/placeholder.test.ts | 34 ++++++++++++++++++- .../components/prompt-input/placeholder.ts | 15 ++++++-- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/packages/app/src/components/prompt-input-v2.tsx b/packages/app/src/components/prompt-input-v2.tsx index be6ae49206..9a279a62e8 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 d4caead0d2..cb0575bb26 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 d4ffda06b4..f5f19ae140 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: "@" }) }