Skip to content

feat(dashboard): Phase 6 — resumable large-upload UX (T6.1 + T6.2)#109

Merged
jeffgreendesign merged 2 commits into
mainfrom
feat/dashboard-resumable-upload
Jun 12, 2026
Merged

feat(dashboard): Phase 6 — resumable large-upload UX (T6.1 + T6.2)#109
jeffgreendesign merged 2 commits into
mainfrom
feat/dashboard-resumable-upload

Conversation

@jeffgreendesign

Copy link
Copy Markdown
Owner

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 POST with fake progress. Dashboard-only; no server changes.

Stacked on feat/processor-registry (Phase 5) — retarget to main once Phase 5 merges.

What changed

  • lib/api.ts — resumable client: initUpload, putResumable (256 KiB-aligned chunks, honors 308 Resume Incomplete + Range, re-probes the 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) picks direct vs resumable; real progress (bytes while uploading, entriesProcessed/entriesTotal while processing) replaces the fake 30→100; pending → uploading → processing → complete | partial | error with per-file Retry and Cancel; keeps the no-server guard; reads nested error.message so failures never render as [object Object].
  • Tests: minimal vitest harness for the dashboard workspace + 16 unit tests (chunking / 308 / resume / malformed-Range / 4xx, polling, cancel, orchestrator, threshold parsing).
  • .env.example: documents NEXT_PUBLIC_UPLOAD_THRESHOLD_MB (default 20, matching server MAX_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

  • Tags ride only the direct path — the server's /upload/init schema doesn't accept tags, so resumable uploads don't carry them (server-side gap, not Phase 6's scope).
  • Live E2E needs maintainer infra: GCS bucket CORS allowing PUT/Content-Range from the dashboard origin and exposing the Range header (T3.3), plus Phase 4 prod activation. Code is built to that contract; unit tests don't depend on it.

Verification

  • Dashboard: 16 vitest tests pass; typecheck, lint, next build clean.
  • Root pnpm verify:fast: 249 tests + security/docs/tool-sync green.

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dashboard Building Building Preview, Comment Jun 12, 2026 5:14pm
textrawl Ready Ready Preview, Comment Jun 12, 2026 5:14pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR introduces large file resumable upload support to the Textrawl dashboard. The implementation adds a complete GCS-style chunked upload flow in dashboard/lib/api.ts with configurable file size thresholds, per-chunk Content-Range headers, 308 Resume Incomplete handling, and server-side processing polling. The upload page (dashboard/app/upload/page.tsx) now distinguishes small (direct POST) and large (resumable) files, tracks byte-level and processing progress separately, and displays partial/error states with retry and cancel controls. A comprehensive Vitest test suite validates threshold configuration, session initialization, multi-chunk resumption, error mapping, and end-to-end orchestration. Environment variables, test scripts, and Vitest configuration complete the setup.

Sequence Diagram

sequenceDiagram
  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
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is comprehensive with clear sections on changes, deferred work, notes, and verification, but deviates from the template structure (uses custom sections instead of template headings).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately describes the main change: implementing resumable large-upload UX for the dashboard with explicit phase and task references (Phase 6 T6.1 + T6.2).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dashboard-resumable-upload

Comment @coderabbitai help to get the list of available commands and usage tips.

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.
@jeffgreendesign jeffgreendesign force-pushed the feat/dashboard-resumable-upload branch from 749b528 to 153c09c Compare June 12, 2026 17:14
@jeffgreendesign jeffgreendesign changed the base branch from feat/processor-registry to main June 12, 2026 17:14
@jeffgreendesign jeffgreendesign changed the title feat(dashboard): resumable large-upload flow (Phase 6 T6.1) feat(dashboard): Phase 6 — resumable large-upload UX (T6.1 + T6.2) Jun 12, 2026
@jeffgreendesign jeffgreendesign merged commit baf1c48 into main Jun 12, 2026
5 of 7 checks passed
@jeffgreendesign jeffgreendesign deleted the feat/dashboard-resumable-upload branch June 12, 2026 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant