feat(dashboard): Phase 6 — resumable large-upload UX (T6.1 + T6.2)#109
Conversation
WalkthroughThis PR introduces large file resumable upload support to the Textrawl dashboard. The implementation adds a complete GCS-style chunked upload flow in Sequence DiagramsequenceDiagram
participant User
participant UploadPage
participant initUpload
participant putResumable
participant completeUpload
participant pollUploadStatus
participant Server
User->>UploadPage: Select large file
UploadPage->>initUpload: Create upload session
initUpload->>Server: POST /upload/init
Server-->>initUpload: uploadId, resumableUri
initUpload-->>UploadPage: InitUploadResponse
UploadPage->>putResumable: Upload file chunks
putResumable->>Server: PUT with Content-Range header
Server-->>putResumable: 308 Resume Incomplete + Range
putResumable->>putResumable: Parse committed offset, upload next chunk
putResumable->>Server: PUT next chunk
Server-->>putResumable: 200 OK
putResumable-->>UploadPage: Upload complete, progress updates
UploadPage->>completeUpload: Finalize upload
completeUpload->>Server: POST /upload/complete
Server-->>completeUpload: statusUrl
UploadPage->>pollUploadStatus: Wait for processing
pollUploadStatus->>Server: GET /upload/status (polling)
Server-->>pollUploadStatus: Processing progress
pollUploadStatus-->>UploadPage: onProcessingUpdate callback
pollUploadStatus->>Server: GET /upload/status (terminal)
Server-->>pollUploadStatus: Final status (completed/partial)
pollUploadStatus-->>UploadPage: UploadStatusResponse
UploadPage-->>User: Display final status
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Drive large files through the server's resumable upload contract instead of the single-shot POST with fake progress. - lib/api.ts: resumable client — initUpload, putResumable (256 KiB-aligned chunks, honors 308 + Range, re-probes committed offset after transient failures, throws on a malformed Range rather than skipping bytes; no bearer header on the GCS PUT), completeUpload, getUploadStatus, cancelUpload, pollUploadStatus, and a resumableUpload orchestrator. UploadError carries the server's stable error code. - app/upload/page.tsx: threshold switch (UPLOAD_THRESHOLD_MB) selects direct vs resumable; real progress (bytes uploading, entries processing) replaces the fake 30->100; pending/uploading/processing/complete/partial/error states with per-file Retry and Cancel; keeps the no-server guard; reads nested error.message so failures never render as [object Object]. - Minimal vitest harness for the dashboard workspace + unit tests for the client flow (chunking/308/resume/4xx, polling, cancel, orchestrator). - .env.example documents NEXT_PUBLIC_UPLOAD_THRESHOLD_MB. Honest accept-list/copy, ZIP MIME normalization, and friendly error mapping follow in T6.2.
Replace the over-promising accept list and bare error strings with the parent plan's §8a support matrix and a structured code->text mapping. - lib/api.ts: SUPPORTED_UPLOAD_EXTENSIONS / UPLOAD_ACCEPT_ATTR / SUPPORTED_UPLOAD_COPY as the single source of truth for the picker accept attribute and help copy (txt/md/pdf/docx/csv/xlsx/json/html/htm/zip; images, audio, MBOX, EML removed). normalizeUploadContentType maps ZIP to application/zip by extension (covers application/x-zip-compressed and ''). - lib/api.ts: UPLOAD_ERROR_MESSAGES + describeUploadError (maps stable server codes to friendly text, treats fetch TypeError as a connectivity failure) and friendlyUploadCode. uploadErrorFromResponse is exported so the direct path maps 413/UNSUPPORTED_TYPE too. No more [object Object] or bare "Load failed". - app/upload/page.tsx: picker sets accept + the §8a copy; all error sites and the terminal failed/partial branches render friendly text. - Tests: accept-list matches §8a and excludes images/audio/mbox/eml; ZIP MIME normalization; every stable code maps to readable text.
749b528 to
153c09c
Compare
Phase 6 · T6.1 — Resumable large-upload client + threshold switch + real progress
Makes the dashboard drive the server's resumable upload contract for large files instead of the single-shot
POSTwith fake progress. Dashboard-only; no server changes.Stacked on
feat/processor-registry(Phase 5) — retarget tomainonce Phase 5 merges.What changed
lib/api.ts— resumable client:initUpload,putResumable(256 KiB-aligned chunks, honors308 Resume Incomplete+Range, re-probes the committed offset after transient failures, throws on a malformedRangerather than skipping bytes; no bearer header on the GCS PUT),completeUpload,getUploadStatus,cancelUpload,pollUploadStatus, and aresumableUploadorchestrator.UploadErrorcarries the server's stable errorcode.app/upload/page.tsx: threshold switch (UPLOAD_THRESHOLD_MB) picks direct vs resumable; real progress (bytes while uploading,entriesProcessed/entriesTotalwhile processing) replaces the fake30→100;pending → uploading → processing → complete | partial | errorwith per-file Retry and Cancel; keeps the no-server guard; reads nestederror.messageso failures never render as[object Object]..env.example: documentsNEXT_PUBLIC_UPLOAD_THRESHOLD_MB(default 20, matching serverMAX_SINGLE_FILE_SIZE_MB).Deferred to T6.2 (PR B)
Honest accept-list + copy, ZIP MIME normalization, and the friendly error code→text mapping.
Notes / flags
/upload/initschema doesn't accept tags, so resumable uploads don't carry them (server-side gap, not Phase 6's scope).PUT/Content-Rangefrom the dashboard origin and exposing theRangeheader (T3.3), plus Phase 4 prod activation. Code is built to that contract; unit tests don't depend on it.Verification
typecheck,lint,next buildclean.pnpm verify:fast: 249 tests + security/docs/tool-sync green.