Files
2026-05-27 23:29:53 +01:00

4.1 KiB

Tasks: enterprise-react-file-upload

Change ID: enterprise-react-file-upload Package: @suaveplan/react-file-upload Target path: react/media/react-file-upload Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/mime, @suaveplan/react-translate


0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Scaffold

  • Create directory tree. Write package.json at 0.1.0, tsconfig.json, vitest.config.ts, scripts.
  • Dependencies: @suaveplan/error workspace:^, @suaveplan/mime workspace:^, @suaveplan/react-translate workspace:^, zod catalog:forms, @opentelemetry/api catalog:observability, tslib catalog:build.

2. Error codes and types

  • Define FileUploadError; codes: FILE_UPLOAD_VALIDATION_FAILED, FILE_UPLOAD_CHUNK_FAILED, FILE_UPLOAD_NETWORK_ERROR.
  • Define FileState, ChunkState, UploadOptions, DropzoneOptions, UploadStatus.

3. Upload engine

  • createFileState(file: File): FileState.
  • splitFileIntoChunks(file, chunkSize): Blob[].
  • validateFile(file, options) — checks size, MIME via @suaveplan/mime matchesMimePattern. Throws FileUploadError(FILE_UPLOAD_VALIDATION_FAILED) on failure.
  • matchesMimePattern(file, pattern) — delegates to @suaveplan/mime.
  • createPreviewUrl(file): string, revokePreviewUrl(url): void — URL.createObjectURL / revokeObjectURL.
  • formatBytes(bytes): string, calculateProgress(chunks): number, isImageFile(file): boolean.
  • Write upload-engine.test.ts (≤500 lines): all functions. { timeout: 5000 }.
  • Write upload-engine.md.

4. Hooks

useDropzone

  • HTML5 drag events + click-to-browse; onDrop(files: File[]), isDragging, rootProps, inputProps.
  • A11y: role="button", aria-label, keyboard Enter/Space opens file picker.
  • Write use-dropzone.test.tsx + md.

useFileUpload

  • Manages files: FileState[], chunked upload (configurable concurrency, chunkSize), upload(files), cancel(id), retry(id), remove(id). Injectable uploadFn(chunk, metadata).
  • Throws FileUploadError(FILE_UPLOAD_CHUNK_FAILED) on chunk failure; auto-retries up to maxRetries.
  • OTel file_upload.complete span on each file completion.
  • Write use-file-upload.test.ts (≤500 lines): upload, progress, cancel, retry. { timeout: 10000 }.
  • Write use-file-upload.md.

5. Components

Dropzone

  • Uses useDropzone; renders drag-target area with i18n copy.
  • Write test + stories (default, dragging, with-accepted-types, playground) + md.

FileList

  • Renders FileState[] with name, size, progress, cancel/retry buttons.
  • Write test + stories + md.

ProgressBar

  • Single-file progress bar component.
  • Write test + stories + md.

FileUpload (composite)

  • Composes Dropzone + FileList + ProgressBar.
  • Write test + stories (default, uploading, complete, error, playground) + md.

6. E2E

  • e2e/file-upload.spec.ts — Playwright:
    • Use page.setInputFiles to upload a file → progress bar appears.
    • Progress reaches 100% → complete state shown.
    • Click cancel during upload → file marked cancelled.

7. Verify and publish

  • bunx turbo lint typecheck test --filter=@suaveplan/react-file-upload — green.
  • bunx turbo test:coverage --filter=@suaveplan/react-file-upload — 100%.
  • bun scripts/publish.ts --package @suaveplan/react-file-upload.
  • Archive change folder.