From 9d2b7a7d7a298b56d640ef0eb8930995e7fc966a Mon Sep 17 00:00:00 2001 From: Aaron Gabriel Date: Sat, 18 Apr 2026 16:02:04 -0600 Subject: [PATCH] Add attachment upload: drop, paste, picker with progress + defer-link on /new Drag & drop, paste-from-clipboard, and a picker button upload files to the vault storage endpoint, then insert the right markdown (![alt](url) for images, [filename](url) otherwise). On /notes/:id/edit attachments link to the note immediately; on /new they stage and link after create to avoid orphan storage blobs on cancel. XHR backs the upload so we can show per-file progress and cancel mid-flight. Inline images in the preview go through VaultImage, which blob-fetches past the auth-gated storage endpoint. Size cap 100 MB and extension allowlist (wav/mp3/m4a/ogg/webm/png/jpg/jpeg/ gif/webp) are enforced client-side before any network request. PDF + mp4 tracked upstream in parachute-vault#127. Attachment sidebar is read-only this PR; remove button is sequenced to #8.5 after the vault DELETE endpoint lands. Co-Authored-By: Claude Opus 4.7 --- src/app/routes/NoteEditor.test.tsx | 189 +++++++++++++++++- src/app/routes/NoteEditor.tsx | 102 +++++++++- src/app/routes/NoteNew.test.tsx | 255 +++++++++++++++++++++++- src/app/routes/NoteNew.tsx | 98 ++++++++- src/components/AttachmentDropZone.tsx | 77 +++++++ src/components/AttachmentPicker.tsx | 42 ++++ src/components/AttachmentUploadList.tsx | 76 +++++++ src/components/CodeMirrorEditor.tsx | 57 +++++- src/components/MarkdownView.tsx | 5 + src/components/VaultImage.tsx | 60 ++++++ src/components/useAttachmentUploader.ts | 148 ++++++++++++++ src/lib/vault/client.test.ts | 192 +++++++++++++++++- src/lib/vault/client.ts | 122 +++++++++++- src/lib/vault/queries.ts | 45 ++++- src/lib/vault/types.ts | 4 + 15 files changed, 1441 insertions(+), 31 deletions(-) create mode 100644 src/components/AttachmentDropZone.tsx create mode 100644 src/components/AttachmentPicker.tsx create mode 100644 src/components/AttachmentUploadList.tsx create mode 100644 src/components/VaultImage.tsx create mode 100644 src/components/useAttachmentUploader.ts diff --git a/src/app/routes/NoteEditor.test.tsx b/src/app/routes/NoteEditor.test.tsx index 7c749bf..71b0510 100644 --- a/src/app/routes/NoteEditor.test.tsx +++ b/src/app/routes/NoteEditor.test.tsx @@ -1,4 +1,5 @@ import { NoteEditor } from "@/app/routes/NoteEditor"; +import { useToastStore } from "@/lib/toast/store"; import { useVaultStore } from "@/lib/vault/store"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; @@ -8,14 +9,102 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; // Swap CodeMirror out for a plain textarea so tests can drive onChange without // wrangling CM6 inside jsdom. -vi.mock("@/components/CodeMirrorEditor", () => ({ - CodeMirrorEditor: ({ - value, - onChange, - }: { value: string; onChange(next: string): void; onSave?(): void; onCancel?(): void }) => ( -