diff --git a/packages/elements/__tests__/prompt-input.test.tsx b/packages/elements/__tests__/prompt-input.test.tsx
index d961a3ba..035e4203 100644
--- a/packages/elements/__tests__/prompt-input.test.tsx
+++ b/packages/elements/__tests__/prompt-input.test.tsx
@@ -1054,6 +1054,114 @@ describe("promptInputProvider", () => {
expect(screen.getByTestId("count")).toHaveTextContent("1");
});
+
+ it("keeps attachment references stable while typing", async () => {
+ setupPromptInputTests();
+ const { PromptInputProvider } = await import("../src/prompt-input");
+ const user = userEvent.setup();
+ const onSubmit = vi.fn();
+ const file = new File(["test"], "test.txt", { type: "text/plain" });
+
+ const fileRefs: AttachmentData[] = [];
+ const AttachmentConsumer = () => {
+ const attachments = usePromptInputAttachments();
+ if (attachments.files.length > 0) {
+ fileRefs.push(attachments.files[0]);
+ }
+ return (
+
+ );
+ };
+
+ render(
+
+
+
+
+
+
+
+
+ );
+
+ await user.click(screen.getByTestId("add-file"));
+ expect(fileRefs.length).toBeGreaterThan(0);
+
+ await user.type(screen.getByRole("textbox"), "hello");
+
+ expect(new Set(fileRefs).size).toBe(1);
+ });
+
+ it("does not re-render memoized attachment rows on text input", async () => {
+ setupPromptInputTests();
+ const { PromptInputProvider } = await import("../src/prompt-input");
+ const user = userEvent.setup();
+ const onSubmit = vi.fn();
+ const file = new File(["test"], "test.txt", { type: "text/plain" });
+ const otherFile = new File(["other"], "other.txt", {
+ type: "text/plain",
+ });
+ const chipRenders: string[] = [];
+
+ const Chip = React.memo(({ data }: { data: AttachmentData }) => {
+ chipRenders.push(data.id);
+ return {data.filename};
+ });
+ Chip.displayName = "Chip";
+
+ const ChipList = () => {
+ const attachments = usePromptInputAttachments();
+ return (
+ <>
+
+
+ {attachments.files.map((f) => (
+
+ ))}
+ >
+ );
+ };
+
+ render(
+
+
+
+
+
+
+
+
+ );
+
+ await user.click(screen.getByTestId("add-file"));
+ const rendersAfterAdd = chipRenders.length;
+ expect(rendersAfterAdd).toBeGreaterThan(0);
+
+ await user.type(screen.getByRole("textbox"), "hello");
+ expect(chipRenders.length).toBe(rendersAfterAdd);
+
+ await user.click(screen.getByTestId("add-other-file"));
+ expect(chipRenders.length).toBeGreaterThan(rendersAfterAdd);
+ expect(screen.getAllByTestId(/^chip-/)).toHaveLength(2);
+ });
});
describe("file validation", () => {
diff --git a/packages/elements/src/prompt-input.tsx b/packages/elements/src/prompt-input.tsx
index 412c846d..0c384e83 100644
--- a/packages/elements/src/prompt-input.tsx
+++ b/packages/elements/src/prompt-input.tsx
@@ -816,7 +816,7 @@ export const PromptInput = ({
add,
clear: clearAttachments,
fileInputRef: inputRef,
- files: files.map((item) => ({ ...item, id: item.id })),
+ files,
openFileDialog,
remove,
}),