diff --git a/packages/app/e2e/onboarding/home-suggestion-chips.spec.ts b/packages/app/e2e/onboarding/home-suggestion-chips.spec.ts index 2f9aeadd1..cc4c500e9 100644 --- a/packages/app/e2e/onboarding/home-suggestion-chips.spec.ts +++ b/packages/app/e2e/onboarding/home-suggestion-chips.spec.ts @@ -85,9 +85,9 @@ test("@smoke composer placeholder is the static home string", async ({ page, pro const label = await editor.getAttribute("aria-label") // i18n source: packages/app/src/i18n/{zh,en}.ts → prompt.placeholder.home if (locale === "zh") { - expect(label).toBe("输入你的任务,或 @ 引用文件") + expect(label).toBe("输入你的任务,@ 引用文件,/ 唤起命令") } else { - expect(label).toBe("Type your task, or @ to mention files") + expect(label).toBe("Type your task, @ to mention files, / for commands") } }) diff --git a/packages/app/e2e/snap/prompt-placeholder.snap.ts b/packages/app/e2e/snap/prompt-placeholder.snap.ts new file mode 100644 index 000000000..3d057de8c --- /dev/null +++ b/packages/app/e2e/snap/prompt-placeholder.snap.ts @@ -0,0 +1,70 @@ +import { test } from "../fixtures" +import { promptSelector } from "../selectors" +import { composeGrid, snapOutputPath, type Shot } from "./_compose" + +test.use({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2 }) + +async function snapComposer(page: import("@playwright/test").Page, name: string): Promise { + const prompt = page.locator(promptSelector) + await prompt.waitFor({ state: "visible", timeout: 30_000 }) + const placeholder = page.locator('[data-component="prompt-placeholder"]') + await placeholder.waitFor({ state: "visible", timeout: 30_000 }) + return { name, buf: await prompt.screenshot() } +} + +// Locale-independent right-panel toggle. The shared `openRightPanel` helper +// looks up the button by English aria name and would not find it after the +// snap switches the app to zh. +async function ensureRightPanelOpen(page: import("@playwright/test").Page) { + const toggle = page.locator('[aria-controls="right-panel"]').first() + await toggle.waitFor({ state: "visible", timeout: 30_000 }) + const expanded = await toggle.getAttribute("aria-expanded") + if (expanded === "true") return + await toggle.click() + await page.locator('#right-panel').waitFor({ state: "visible", timeout: 5_000 }) +} + +// 768px matches the Electron window minWidth from packages/desktop-electron/src/main/windows.ts. +// The placeholder element has `truncate whitespace-nowrap`, so the narrow shot is the only +// way to catch the `/ for commands` hint being clipped at the smallest supported window. +// The "squeezed" row also opens the right panel so the prompt width is realistically narrow +// (window minWidth - sidebar - right panel) instead of just window minWidth. +const NARROW_WIDTH = 768 + +test("prompt-placeholder", async ({ page, project }) => { + test.setTimeout(180_000) + + await project.open() + const enShot = await snapComposer(page, "en-1440") + + await page.setViewportSize({ width: NARROW_WIDTH, height: 900 }) + const enNarrowShot = await snapComposer(page, "en-768") + + await ensureRightPanelOpen(page) + const enSqueezedShot = await snapComposer(page, "en-768+rightpanel") + + // Client-side locale is sourced from localStorage. Set it, reload, re-snap all widths. + // Also clear persisted layout state so the right panel returns to its default closed + // state for the zh baseline/narrow shots — opening it for the en squeezed shot writes + // `rightPanel.opened: true` into `pawwork.global.dat:layout`, which survives reload. + await page.evaluate(() => { + localStorage.setItem("pawwork.global.dat:language", JSON.stringify({ locale: "zh" })) + localStorage.removeItem("pawwork.global.dat:layout") + }) + await page.setViewportSize({ width: 1440, height: 900 }) + await page.reload() + const zhShot = await snapComposer(page, "zh-1440") + + await page.setViewportSize({ width: NARROW_WIDTH, height: 900 }) + const zhNarrowShot = await snapComposer(page, "zh-768") + + await ensureRightPanelOpen(page) + const zhSqueezedShot = await snapComposer(page, "zh-768+rightpanel") + + const out = snapOutputPath("prompt-placeholder") + await composeGrid( + [enShot, enNarrowShot, enSqueezedShot, zhShot, zhNarrowShot, zhSqueezedShot], + out, + ) + process.stdout.write(`\n[snap] prompt-placeholder grid -> ${out}\n\n`) +}) diff --git a/packages/app/src/components/prompt-input/placeholder.test.ts b/packages/app/src/components/prompt-input/placeholder.test.ts index f4feb9f34..ecc6c99de 100644 --- a/packages/app/src/components/prompt-input/placeholder.test.ts +++ b/packages/app/src/components/prompt-input/placeholder.test.ts @@ -1,4 +1,6 @@ import { describe, expect, test } from "bun:test" +import { dict as en } from "@/i18n/en" +import { dict as zh } from "@/i18n/zh" import { promptPlaceholder } from "./placeholder" describe("promptPlaceholder", () => { @@ -17,3 +19,17 @@ describe("promptPlaceholder", () => { expect(promptPlaceholder({ mode: "normal", commentCount: 0, t })).toBe("prompt.placeholder.home") }) }) + +describe("prompt.placeholder.home copy", () => { + test("hints at both @ for files and / for commands in English", () => { + const copy = en["prompt.placeholder.home"] + expect(copy).toContain("@") + expect(copy).toContain("/") + }) + + test("hints at both @ for files and / for commands in Chinese", () => { + const copy = zh["prompt.placeholder.home"] + expect(copy).toContain("@") + expect(copy).toContain("/") + }) +}) diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 21f42ab91..eca26d7d2 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -258,7 +258,7 @@ export const dict = { "variant.max": "Max", "prompt.placeholder.shell": "Enter shell command...", - "prompt.placeholder.home": "Type your task, or @ to mention files", + "prompt.placeholder.home": "Type your task, @ to mention files, / for commands", "prompt.placeholder.summarizeComments": "Summarize comments…", "prompt.placeholder.summarizeComment": "Summarize comment…", "prompt.mode.shell": "Shell", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 25a3cedaf..3ab60cdd0 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -276,7 +276,7 @@ export const dict = { "variant.max": "最高", "prompt.placeholder.shell": "输入 shell 命令...", - "prompt.placeholder.home": "输入你的任务,或 @ 引用文件", + "prompt.placeholder.home": "输入你的任务,@ 引用文件,/ 唤起命令", "prompt.placeholder.summarizeComments": "总结评论…", "prompt.placeholder.summarizeComment": "总结该评论…", "prompt.mode.shell": "Shell",