Skip to content

Commit cfb23b7

Browse files
authored
refactor(attachments): drop nanoid and use the blob URL as the id (CHAT-31) (#95)
1 parent e16ad77 commit cfb23b7

8 files changed

Lines changed: 42 additions & 29 deletions

File tree

‎.changeset/drop-nanoid.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@intentface/chat": patch
3+
---
4+
5+
Drop the `nanoid` dependency. The default attachment `convert` now uses the file's blob URL as its id, so `@floating-ui/dom` is the package's only runtime dependency. Ids are still unique per item, including the same file added twice; nothing in the package parses them, and a custom `convert` can still supply its own.

‎bun.lock‎

Lines changed: 2 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎content/docs/quick-start.mdx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ appearance. This page takes you from an empty React 19 app to a working chat.
1313
<InstallationBlock packageName="@intentface/chat" />
1414

1515
`react` and `react-dom` (v19+) are the only peer dependencies. Nothing else
16-
ships with it beyond two small runtime deps — `@floating-ui/dom` for anchored
17-
positioning and `nanoid` for attachment ids. No styling, no editor framework, no
18-
animation library.
16+
ships with it beyond one small runtime dep — `@floating-ui/dom` for anchored
17+
positioning. No styling, no editor framework, no animation library.
1918

2019
Each primitive is a separate entry point:
2120

‎package.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"fumadocs-mdx": "15.0.12",
3232
"lucide-react": "^0.563.0",
3333
"motion": "^12.42.2",
34-
"nanoid": "^5.1.16",
3534
"next": "16.2.12",
3635
"next-themes": "^0.4.6",
3736
"react": "19.2.3",

‎packages/chat/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ bun add @intentface/chat
5858
```
5959

6060
`react` and `react-dom` (v19+) are peer dependencies. Nothing else ships with
61-
it beyond two small runtime deps (`@floating-ui/dom` for anchored positioning,
62-
`nanoid` for attachment ids) — no editor framework, no animation library.
61+
it beyond one small runtime dep (`@floating-ui/dom` for anchored positioning) —
62+
no editor framework, no animation library.
6363

6464
## What's inside
6565

‎packages/chat/package.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,7 @@
100100
"prepack": "bun run build"
101101
},
102102
"dependencies": {
103-
"@floating-ui/dom": "^1.7.6",
104-
"nanoid": "^5.1.11"
103+
"@floating-ui/dom": "^1.7.6"
105104
},
106105
"peerDependencies": {
107106
"react": "^19.0.0",

‎packages/chat/src/attachments/attachments.tsx‎

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
"use client";
22

33
// Headless attachments: generic structural parts, accept matching, and the
4-
// platform-default ingestion (blob URL + generated id — the browser-native way
5-
// to reference a picked File; override via Composer.Attachments' create/destroy
6-
// for upload-to-storage etc.). Policy (accepted types, counts, sizes), media
7-
// taxonomy, send serialization, and copy are the consumer's. Card visuals
8-
// belong to the styled layer.
4+
// platform-default ingestion (a blob URL that doubles as the id — the
5+
// browser-native way to reference a picked File; override via
6+
// Composer.Attachments' create/destroy for upload-to-storage etc.). Policy
7+
// (accepted types, counts, sizes), media taxonomy, send serialization, and copy
8+
// are the consumer's. Card visuals belong to the styled layer.
99

10-
import { nanoid } from "nanoid";
1110
import { type ReactNode, useEffect, useState } from "react";
1211
import { createPortal } from "react-dom";
1312
import type { PrimitiveProps } from "../internal/primitive-props";
@@ -37,13 +36,12 @@ export type AttachmentErrorCode = "accept" | "max_file_size" | "max_files";
3736
// item referencing it by blob URL. Overridable per composer (create/destroy).
3837
// ---------------------------------------------------------------------------
3938

40-
export const toAttachmentItem = (file: File): AttachmentItem => ({
41-
id: nanoid(),
42-
url: URL.createObjectURL(file),
43-
filename: file.name,
44-
mediaType: file.type,
45-
fileSize: file.size,
46-
});
39+
// Every createObjectURL call mints a fresh `blob:<origin>/<uuid>`, so the URL
40+
// is also a unique id — even for the same File added twice.
41+
export const toAttachmentItem = (file: File): AttachmentItem => {
42+
const url = URL.createObjectURL(file);
43+
return { id: url, url, filename: file.name, mediaType: file.type, fileSize: file.size };
44+
};
4745

4846
export const revokeAttachmentUrl = (item: Pick<AttachmentItem, "url">) => {
4947
if (item.url?.startsWith("blob:")) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { revokeAllAttachmentUrls, toAttachmentItem } from "../src/attachments/attachments";
3+
4+
describe("toAttachmentItem", () => {
5+
test("uses the blob URL as the id", () => {
6+
const item = toAttachmentItem(new File(["x"], "a.png", { type: "image/png" }));
7+
expect(item.url.startsWith("blob:")).toBe(true);
8+
expect(item.id).toBe(item.url);
9+
revokeAllAttachmentUrls([item]);
10+
});
11+
12+
test("the same file added twice gets distinct ids", () => {
13+
const file = new File(["x"], "a.png", { type: "image/png" });
14+
const first = toAttachmentItem(file);
15+
const second = toAttachmentItem(file);
16+
expect(first.id).not.toBe(second.id);
17+
revokeAllAttachmentUrls([first, second]);
18+
});
19+
});

0 commit comments

Comments
 (0)