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 (