diff --git a/surfaces/gui/src/components/Markdown.test.tsx b/surfaces/gui/src/components/Markdown.test.tsx index 6707e3196..b61d506ee 100644 --- a/surfaces/gui/src/components/Markdown.test.tsx +++ b/surfaces/gui/src/components/Markdown.test.tsx @@ -36,6 +36,18 @@ describe("Markdown artifact links", () => { expect(screen.getByTestId("artifact-chip").textContent).toContain("report.pdf"); }); + it("dispatches a decoded Unicode filename", () => { + const seen: string[] = []; + const listener = (e: Event) => seen.push((e as CustomEvent).detail.path); + window.addEventListener(OPEN_ARTIFACT_EVENT, listener); + + render(); + fireEvent.click(screen.getByTestId("artifact-chip")); + expect(seen).toEqual(["新闻摘要.md"]); + + window.removeEventListener(OPEN_ARTIFACT_EVENT, listener); + }); + // Seventeenth pass: the lead's one-time board mention — [Board · 5 items](board:) // renders as an inline pill that opens the drawer on its Board section. it("renders a board: link as a pill and dispatches the open-board event", () => { diff --git a/surfaces/gui/src/components/Markdown.tsx b/surfaces/gui/src/components/Markdown.tsx index e27a6627a..18710212a 100644 --- a/surfaces/gui/src/components/Markdown.tsx +++ b/surfaces/gui/src/components/Markdown.tsx @@ -31,14 +31,22 @@ function BoardChip({ label }: { label: string }) { function ArtifactChip({ path, title }: { path: string; title: string }) { const { t } = useTranslation(); - const file = path.split("/").pop() || path; + let decodedPath = path; + try { + decodedPath = decodeURIComponent(path); + } catch { + // Literal percent signs are valid in filenames even when they are not URL encoding. + } + const file = decodedPath.split(/[\\/]/).pop() || decodedPath; return (