4.1 KiB
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 statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin 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.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Scaffold
- Create directory tree. Write
package.jsonat0.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/mimematchesMimePattern. ThrowsFileUploadError(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 (configurableconcurrency,chunkSize),upload(files),cancel(id),retry(id),remove(id). InjectableuploadFn(chunk, metadata). - Throws
FileUploadError(FILE_UPLOAD_CHUNK_FAILED)on chunk failure; auto-retries up tomaxRetries. - OTel
file_upload.completespan 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.setInputFilesto upload a file → progress bar appears. - Progress reaches 100% → complete state shown.
- Click cancel during upload → file marked cancelled.
- Use
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.