Spec: SDR 3.1.2 — "The editor shall support image uploading for diagrams, tables, and other visual learning content." Doc tech stack: "Would need a way to store images (maybe with Vercel blob). Suggestion: Cloudflare R2 + embeddings."
Current state
note-editor.tsx:240 falls back to uploadImageToDataUrl(file) when no uploadImage handler is passed:
const resolvedFile = uploadImage
? await uploadImage(file)
: await uploadImageToDataUrl(file);
So images are base64-inlined into note.content jsonb. Consequences:
- Row size balloons — a handful of screenshots can push a note into megabytes
- Every note load transfers the full image payload, with no caching or CDN
- Base64 inflates bytes by ~33% on top of that
- No thumbnailing, no resizing, no dedupe
This degrades quietly as usage grows and works directly against SDR 4.3 (scalability) and 4.2 (performance).
Scope
- Pick a blob store (Vercel Blob is the lowest-friction given Vercel deployment; Cloudflare R2 is cheaper at volume and is what the tech-stack doc suggests) and wire an authenticated upload route.
- Store
{ url, width, height, size } in the block, not bytes.
- Enforce mime allowlist and size cap server-side; scope objects per user.
- Generate a resized display variant; keep the original.
- Lifecycle: deleting a note should not orphan its blobs forever — decide on a cleanup path.
- Migration for existing notes with inlined data URLs, or an explicit decision to leave them as-is.
Acceptance criteria
Spec: SDR 3.1.2 — "The editor shall support image uploading for diagrams, tables, and other visual learning content." Doc tech stack: "Would need a way to store images (maybe with Vercel blob). Suggestion: Cloudflare R2 + embeddings."
Current state
note-editor.tsx:240 falls back to
uploadImageToDataUrl(file)when nouploadImagehandler is passed:So images are base64-inlined into
note.contentjsonb. Consequences:This degrades quietly as usage grows and works directly against SDR 4.3 (scalability) and 4.2 (performance).
Scope
{ url, width, height, size }in the block, not bytes.Acceptance criteria
note.contentsize stays flat as images are added