diff --git a/web/src/components/AttachmentLibrary/AttachmentMediaGrid.tsx b/web/src/components/AttachmentLibrary/AttachmentMediaGrid.tsx
index 37cf475e..6c7463cc 100644
--- a/web/src/components/AttachmentLibrary/AttachmentMediaGrid.tsx
+++ b/web/src/components/AttachmentLibrary/AttachmentMediaGrid.tsx
@@ -4,7 +4,7 @@ import { Badge } from "@/components/ui/badge";
import VideoPoster from "@/components/VideoPoster";
import type { AttachmentLibraryMediaItem, AttachmentLibraryMonthGroup } from "@/hooks/useAttachmentLibrary";
import { useTranslate } from "@/utils/i18n";
-import { AttachmentMetadataLine, AttachmentOpenButton } from "./AttachmentLibraryPrimitives";
+import { AttachmentMetadataLine, AttachmentOpenButton, AttachmentSourceChip } from "./AttachmentLibraryPrimitives";
interface AttachmentMediaGridProps {
groups: AttachmentLibraryMonthGroup[];
@@ -60,12 +60,12 @@ const AttachmentMediaCard = ({ item, onPreview }: { item: AttachmentLibraryMedia
)}
+
diff --git a/web/src/hooks/useAttachmentLibrary.ts b/web/src/hooks/useAttachmentLibrary.ts
index e475b93b..a9679c59 100644
--- a/web/src/hooks/useAttachmentLibrary.ts
+++ b/web/src/hooks/useAttachmentLibrary.ts
@@ -37,6 +37,7 @@ export interface AttachmentLibraryMediaItem extends AttachmentVisualItem {
createdAt?: Date;
createdLabel: string;
fileTypeLabel: string;
+ memoName?: string;
}
export interface AttachmentLibraryMonthGroup {
@@ -96,6 +97,7 @@ const toLibraryMediaItem = (item: AttachmentVisualItem, locale: string, livePhot
createdAt,
createdLabel: formatCreatedAt(createdAt, locale),
fileTypeLabel: item.kind === "motion" ? livePhotoLabel : fileTypeLabel,
+ memoName: primaryAttachment.memo,
};
};
diff --git a/web/tests/attachment-library-source-link.test.tsx b/web/tests/attachment-library-source-link.test.tsx
new file mode 100644
index 00000000..2f961969
--- /dev/null
+++ b/web/tests/attachment-library-source-link.test.tsx
@@ -0,0 +1,65 @@
+import { create } from "@bufbuild/protobuf";
+import { fireEvent, render, screen } from "@testing-library/react";
+import { MemoryRouter } from "react-router-dom";
+import { describe, expect, it, vi } from "vitest";
+import AttachmentMediaGrid from "@/components/AttachmentLibrary/AttachmentMediaGrid";
+import type { AttachmentLibraryMonthGroup } from "@/hooks/useAttachmentLibrary";
+import { AttachmentSchema } from "@/types/proto/api/v1/attachment_service_pb";
+
+vi.mock("@/utils/i18n", () => ({ useTranslate: () => (key: string) => key }));
+
+describe("", () => {
+ it("links a media attachment to its source memo without replacing the direct file action", () => {
+ const attachment = create(AttachmentSchema, {
+ name: "attachments/photo",
+ filename: "photo.jpg",
+ type: "image/jpeg",
+ memo: "memos/source-memo",
+ });
+ const groups: AttachmentLibraryMonthGroup[] = [
+ {
+ key: "2026-07",
+ label: "Jul 2026",
+ items: [
+ {
+ id: attachment.name,
+ kind: "image",
+ filename: attachment.filename,
+ posterUrl: "/file/attachments/photo/photo.jpg?thumbnail=1",
+ sourceUrl: "/file/attachments/photo/photo.jpg",
+ attachmentNames: [attachment.name],
+ attachments: [attachment],
+ previewItem: {
+ id: attachment.name,
+ kind: "image",
+ filename: attachment.filename,
+ sourceUrl: "/file/attachments/photo/photo.jpg",
+ },
+ mimeType: attachment.type,
+ primaryAttachment: attachment,
+ createdLabel: "Jul 29, 2026",
+ fileTypeLabel: "JPEG",
+ memoName: attachment.memo,
+ },
+ ],
+ },
+ ];
+ const onPreview = vi.fn();
+
+ render(
+
+
+ ,
+ );
+
+ const memoLink = screen.getByRole("link", { name: "attachment-library.labels.memo" });
+ expect(memoLink).toHaveAttribute("href", "/memos/source-memo");
+
+ const fileLink = screen.getByRole("link", { name: "attachment-library.actions.open" });
+ expect(fileLink).toHaveAttribute("href", "/file/attachments/photo/photo.jpg");
+ expect(fileLink).toHaveAttribute("target", "_blank");
+
+ fireEvent.click(memoLink);
+ expect(onPreview).not.toHaveBeenCalled();
+ });
+});