feat(web): Inline upload progress#410
Conversation
Entire-Checkpoint: f8637df6d80c
Entire-Checkpoint: a8aa6e76b7df
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 058ef38e725f
Move non-standard inherited spacing values (12px, 6px, 3px) out of the main spacing scale into a read-only inherited exceptions section. Add typography advisory for 10px/11px proximity. Improve dismiss tooltip from "Dismiss" to "Dismiss error" for clarity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: f3ff7d5eb7c5
Entire-Checkpoint: 26eefa9e1c06
Entire-Checkpoint: 82929df927b4
Entire-Checkpoint: 5174187dc048
Entire-Checkpoint: be07c554de11
…d state - Replace batch-level UploadState fields (status, progress, currentFile, etc.) with per-file Map<string, PerFileUpload> tracking - Add per-file actions: addFile, updateFileProgress, setFileStatus, setFileComplete, removeFile, cancelFile, retryFile - Export PerFileUpload type for downstream component consumption - Each file gets independent cancel token, progress, and error state - Preserve PendingReplacement type and actions unchanged - Rewrite upload-error-recovery tests for per-file API (12 tests passing) - Stub UploadModal.tsx (returns null) to prevent type errors during transition
- useDropUpload: generate per-file upload IDs, call addFile/updateFileProgress/ setFileComplete per file, derive isUploading from files Map - useDropUpload: use setFileStatus for error handling with specific file ID - upload.service: replace batch startUpload/setEncrypting/setUploading with per-file addFile/updateFileProgress/setFileComplete - useFileUpload: derive batch-level fields (status, progress, currentFile, completedFiles) from per-file Map for backward compatibility - Remove all batch-level store action calls (startUpload, setSuccess, etc.) - Always use getState() for reads inside async callbacks (stale closure prevention)
- Break long ternary in useFileUpload.ts onto multiple lines - Collapse multiline if condition in upload.store.ts reset()
- SUMMARY.md with task commits, decisions, and self-check - STATE.md updated with phase 36 position and metrics - ROADMAP.md updated with plan progress (1/2 complete)
- Create UploadListItem.tsx with fine-grained Zustand selectors per file ID - Support encrypting, uploading, complete, error, and cancelled states - Progress bar with aria-valuenow/aria-valuemax, pulse animation for encrypting - Cancel, retry, dismiss buttons with aria-labels and focus-visible styles - Completion flash timer (1000ms) with cleanup on unmount - Delete all popup/modal/upload-item CSS rules from upload.css - Add inline upload CSS with terminal aesthetic, reduced motion support
…onents - Merge upload entries into FileList with virtual entry sorting and duplicate filtering - Wire onRetryUpload from FileBrowser through FileList to UploadListItem via handleFileDrop - Remove UploadModal import/render from FileBrowser - Delete UploadModal.tsx and UploadItem.tsx (D-11) - Update barrel exports in index.ts
- Add 36-02-SUMMARY.md with task commits and self-check - Update STATE.md progress and metrics - Update ROADMAP.md plan progress
Fixes React Rules of Hooks violation — hooks were defined after the `if (!file) return null` guard, causing "Rendered more hooks than previous render" when a file is removed from the store mid-render. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: eccd0812694c
Phase 36 (inline-upload-progress) verified — 8/8 must-haves passed. Hooks order gap resolved inline, all tests green. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 73418a451496
All 8/8 truths now VERIFIED after dfeaadf moved useCallback hooks before the early return guard. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 1a879ac749c3
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 8 minutes and 13 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
WalkthroughPhase 36: refactors web uploads to per-file tracking and inline upload rows. Replaces the floating UploadModal/UploadItem with UploadListItem rendered inside the FileList, updates the upload store/hooks/services/tests to per-file APIs, and adds planning/validation/docs for the change. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant FileBrowser
participant useDropUpload
participant UploadService
participant UploadStore
participant FileList
participant UploadListItem
User->>FileBrowser: drop file(s)
FileBrowser->>useDropUpload: handleFileDrop([file], folderId)
loop per-file
useDropUpload->>useDropUpload: createUploadId(filename)
useDropUpload->>UploadStore: addFile(uploadId, filename, folderId, file)
useDropUpload->>UploadService: uploadFile(file, cancelToken)
UploadService->>UploadStore: updateFileProgress(uploadId, percent)
UploadService->>UploadStore: setFileStatus(uploadId, 'complete'|'error')
end
FileList->>UploadStore: subscribe files (scoped)
UploadStore-->>FileList: files map (filtered)
FileList->>UploadListItem: render per upload entry
UploadListItem->>UploadStore: read status/progress
UploadListItem->>UploadStore: cancelFile/removeFile / call onRetry -> useDropUpload
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #410 +/- ##
==========================================
- Coverage 61.28% 61.25% -0.04%
==========================================
Files 133 133
Lines 9790 9790
Branches 988 986 -2
==========================================
- Hits 6000 5997 -3
- Misses 3574 3577 +3
Partials 216 216
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
- Extract createUploadId() and isActiveUpload() helpers to eliminate
3x ID format duplication and 5x status-check duplication
- Remove redundant setFileComplete (setFileStatus('complete') already
handles progress: 100)
- Make cancelFile atomic (cancel + remove in single set() call),
removing two-step race in UploadListItem
- Add same-value guard to updateFileProgress to skip no-op store
updates on repeated progress ticks
- Add ref-based equality check to FileList's upload selector to
prevent re-rendering entire file list on every progress callback
- Use stable timestamps (0) for virtual upload entries
- Remove dead useFileUpload hook (zero consumers) and its only caller
uploadFiles() from upload.service.ts
- Remove 'cancelled' status from PerFileUpload (cancelFile now deletes
the entry immediately)
- Remove WHAT-not-WHY comments from UploadListItem JSX
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: e095e7612dbd
There was a problem hiding this comment.
Pull request overview
Replaces the floating upload popup with inline per-file upload progress rows integrated into the file browser list, backed by a refactored per-file upload store.
Changes:
- Refactored
useUploadStorefrom batch-level fields to a per-fileMapwith per-file cancel/retry/progress. - Added
UploadListItemand wiredFileList/FileBrowserto render uploads inline (and removedUploadModal/UploadItem+ popup CSS). - Updated upload flows (
useDropUpload,upload.service,useFileUpload) and adjusted store tests for the new per-file API.
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/web/src/styles/upload.css | Removes popup styles and adds inline upload row/progress/button styling. |
| apps/web/src/stores/upload.store.ts | Introduces per-file upload tracking (Map) and per-file actions (add/update/cancel/retry/reset). |
| apps/web/src/stores/tests/upload-error-recovery.test.ts | Rewrites tests to assert per-file error transitions and orphan cleanup behavior. |
| apps/web/src/services/upload.service.ts | Migrates sequential upload flow to create/update per-file store entries. |
| apps/web/src/hooks/useFileUpload.ts | Derives legacy/batch-like fields from the per-file Map for compatibility. |
| apps/web/src/hooks/useDropUpload.ts | Migrates SDK + duplicate-upload flows to per-file store actions and inline progress model. |
| apps/web/src/components/file-browser/index.ts | Updates barrel exports to remove popup components and export UploadListItem. |
| apps/web/src/components/file-browser/UploadModal.tsx | Deleted (popup UI removed). |
| apps/web/src/components/file-browser/UploadListItem.tsx | New inline upload progress row with cancel/retry/dismiss actions. |
| apps/web/src/components/file-browser/UploadItem.tsx | Deleted (popup row UI removed). |
| apps/web/src/components/file-browser/FileList.tsx | Merges upload “virtual entries” into sorted list and renders UploadListItem. |
| apps/web/src/components/file-browser/FileBrowser.tsx | Removes popup and wires retry callback to drop-upload handler. |
| .planning/phases/36-inline-upload-progress/* | Adds planning/spec/verification artifacts for Phase 36. |
| .planning/config.json | Updates planning git branch templates. |
| .planning/STATE.md | Updates planning state/progress and notes Phase 36. |
| .planning/ROADMAP.md | Adds Phase 36 entry. |
Comments suppressed due to low confidence (1)
apps/web/src/services/upload.service.ts:131
uploadFiles()creates a per-file entry in the upload store, but ifuploadFile()throws the catch block only logs and rethrows. The corresponding store entry never transitions to'error', so the UI/store can get stuck showing an in-progress upload with no way to retry/dismiss. Track the currentuploadIdand callsetFileStatus(uploadId, 'error', message)(and/orremoveFile) on non-cancel errors.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
.planning/STATE.md (1)
82-89: Minor table formatting inconsistency (informational only).Lines 82-89 use a different column format than the preceding rows (combined "Phase 33 P01" naming vs separate Phase/Plan columns). This causes the table to have mismatched column counts.
Based on learnings,
.planning/STATE.mdmetrics are auto-generated during GSD execution and minor inconsistencies are acceptable since they don't affect code behavior.Optional: Align table format for consistency
-| Phase 33 P01 | 11min | 2 tasks | 3 files | -| Phase 33 P02 | 3min | 1 tasks | 3 files | -| Phase 34 P01 | 3min | 2 tasks | 9 files | -| Phase 34 P02 | 4min | 2 tasks | 8 files | -| Phase 34 P03 | 2min | 1 tasks | 1 files | -| Phase 35 P02 | 5min | 4 tasks | 6 files | +| 33 | 01 | 11min | 2 | 3 | +| 33 | 02 | 3min | 1 | 3 | +| 34 | 01 | 3min | 2 | 9 | +| 34 | 02 | 4min | 2 | 8 | +| 34 | 03 | 2min | 1 | 1 | +| 35 | 02 | 5min | 4 | 6 | | Phase 35 P03 | 8min | 4 tasks | 14 files | -| Phase 35 P05 | 5min | 3 tasks | 3 files | +| 35 | 03 | 8min | 4 | 14 | +| 35 | 05 | 5min | 3 | 3 |🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.planning/STATE.md around lines 82 - 89, The table rows using "Phase 33 P01", "Phase 33 P02", etc. combine Phase and Plan into one column causing a mismatched column count; update those rows to separate the Phase and Plan into the same columns as the rest of the table (e.g., split "Phase 33 P01" into "Phase 33" and "P01") so all rows have consistent column counts and alignment with the table header.apps/web/src/components/file-browser/FileList.tsx (2)
113-134: Ref-based equality check is a solid optimization but has a subtle gap.The custom equality logic at lines 126-130 compares
idandstatusbut notfilename. While filename changes mid-upload are unlikely, theentriesarray includesfilename(line 122), suggesting it was intended for comparison. If consistency is desired:🔧 Optional fix to include filename in equality check
prev.length === entries.length && - prev.every((p, i) => p.id === entries[i].id && p.status === entries[i].status) + prev.every((p, i) => p.id === entries[i].id && p.status === entries[i].status && p.filename === entries[i].filename)That said, this is minor since filename won't change during an upload. The overall pattern correctly prevents re-renders on progress ticks per 36-RESEARCH.md Pitfall 6.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/file-browser/FileList.tsx` around lines 113 - 134, The equality check in the uploadEntries selector uses prevEntriesRef and compares only id and status, but the UploadEntry includes filename; update the comparison used in the useUploadStore selector (the block building uploadEntries/prevEntriesRef) to also compare filename so changes to filename are detected and cause updates; ensure you still return the same prev array when id, status, and filename all match to preserve the ref-based optimization.
136-157: Type cast hides structural mismatch withFolderChild.
UploadVirtualEntryincludes the_uploadingdiscriminator field (line 20) whichFolderChild(union ofFolderEntry | FilePointer) doesn't have. The cast on line 157 bypasses TypeScript's structural checks. This works becausesortItemsonly accessestypeandname, but future changes tosortItemscould silently break.Consider a discriminated union or intersection type if this pattern expands:
type SortableItem = FolderChild | UploadVirtualEntry;For now, the cast is acceptable given the limited scope and clear
_uploadingcheck at render time (line 199).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/file-browser/FileList.tsx` around lines 136 - 157, The code unsafely casts UploadVirtualEntry to FolderChild when calling sortItems, hiding a structural mismatch because UploadVirtualEntry contains the _uploading discriminator; change the types so sortItems accepts a union that includes UploadVirtualEntry (e.g., introduce SortableItem = FolderChild | UploadVirtualEntry) and update the call site from sortItems(allItems as FolderChild[]) to sortItems(allItems as SortableItem[]), keeping the existing runtime _uploading checks in rendering (and adjusting any sortItems signature or overloads to accept SortableItem).apps/web/src/components/file-browser/UploadListItem.tsx (1)
4-8: Make the retry contract non-optional.Error rows always render
[R], butonRetryis optional and Line 56 silently degrades the button into a dismiss when the callback is missing. Since the current caller chain already wires retry through, tightening the prop here lets TypeScript catch future regressions.♻️ Proposed fix
type UploadListItemProps = { /** Upload entry ID in the upload store */ fileId: string; /** Callback to re-trigger the upload for a failed file (passes the original File object) */ - onRetry?: (file: File) => void; + onRetry: (file: File) => void; }; @@ const handleRetry = useCallback(() => { const originalFile = file?.file; removeFile(fileId); - if (originalFile && onRetry) { + if (originalFile) { onRetry(originalFile); } }, [fileId, removeFile, file?.file, onRetry]);Also applies to: 53-59, 117-127
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/file-browser/UploadListItem.tsx` around lines 4 - 8, The prop onRetry on UploadListItemProps is currently optional but the UI always renders a retry control; make the contract strict by removing the optional marker so onRetry: (file: File) => void is required. Update the UploadListItemProps type and any component signature for UploadListItem to reflect the non-optional prop, remove any runtime fallback logic that treats onRetry as missing (e.g., the conditional at the button render around line 56), and fix all callers to pass the retry callback where UploadListItem is instantiated so TypeScript enforces the new contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/stores/upload.store.ts`:
- Around line 28-29: createUploadId(filename: string) currently builds keys with
filename + Date.now(), which can collide if two same-name files start within the
same millisecond; update createUploadId to append a collision-resistant
component (e.g., a UUID/random hex string from crypto.randomUUID() or a secure
random bytes and hex encoding, or an incrementing unique counter) so the keys
used by the Map<string, PerFileUpload> are unique even for same filenames
started concurrently; ensure the new id still includes filename for readability
but add the random/UUID suffix and update any code that parses or relies on the
old format (refer to createUploadId and all Map access using those keys).
- Around line 75-82: updateFileProgress currently unconditionally sets status:
'uploading' which can revert terminal entries; modify updateFileProgress(id,
progress) to first retrieve the entry from state.files and return state
immediately if entry.status is a terminal state (e.g., 'complete' or 'error' —
match your code's terminal values), otherwise proceed to check for the no-op
(same progress) and update the Map; reference the updateFileProgress function
and the state.files Map when making this change so you only overwrite status for
non-terminal entries.
---
Nitpick comments:
In @.planning/STATE.md:
- Around line 82-89: The table rows using "Phase 33 P01", "Phase 33 P02", etc.
combine Phase and Plan into one column causing a mismatched column count; update
those rows to separate the Phase and Plan into the same columns as the rest of
the table (e.g., split "Phase 33 P01" into "Phase 33" and "P01") so all rows
have consistent column counts and alignment with the table header.
In `@apps/web/src/components/file-browser/FileList.tsx`:
- Around line 113-134: The equality check in the uploadEntries selector uses
prevEntriesRef and compares only id and status, but the UploadEntry includes
filename; update the comparison used in the useUploadStore selector (the block
building uploadEntries/prevEntriesRef) to also compare filename so changes to
filename are detected and cause updates; ensure you still return the same prev
array when id, status, and filename all match to preserve the ref-based
optimization.
- Around line 136-157: The code unsafely casts UploadVirtualEntry to FolderChild
when calling sortItems, hiding a structural mismatch because UploadVirtualEntry
contains the _uploading discriminator; change the types so sortItems accepts a
union that includes UploadVirtualEntry (e.g., introduce SortableItem =
FolderChild | UploadVirtualEntry) and update the call site from
sortItems(allItems as FolderChild[]) to sortItems(allItems as SortableItem[]),
keeping the existing runtime _uploading checks in rendering (and adjusting any
sortItems signature or overloads to accept SortableItem).
In `@apps/web/src/components/file-browser/UploadListItem.tsx`:
- Around line 4-8: The prop onRetry on UploadListItemProps is currently optional
but the UI always renders a retry control; make the contract strict by removing
the optional marker so onRetry: (file: File) => void is required. Update the
UploadListItemProps type and any component signature for UploadListItem to
reflect the non-optional prop, remove any runtime fallback logic that treats
onRetry as missing (e.g., the conditional at the button render around line 56),
and fix all callers to pass the retry callback where UploadListItem is
instantiated so TypeScript enforces the new contract.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 76e096b4-e392-4194-ae22-7830a952cca3
📒 Files selected for processing (27)
.planning/ROADMAP.md.planning/STATE.md.planning/config.json.planning/phases/36-inline-upload-progress/.gitkeep.planning/phases/36-inline-upload-progress/36-01-PLAN.md.planning/phases/36-inline-upload-progress/36-01-SUMMARY.md.planning/phases/36-inline-upload-progress/36-02-PLAN.md.planning/phases/36-inline-upload-progress/36-02-SUMMARY.md.planning/phases/36-inline-upload-progress/36-CONTEXT.md.planning/phases/36-inline-upload-progress/36-DISCUSSION-LOG.md.planning/phases/36-inline-upload-progress/36-RESEARCH.md.planning/phases/36-inline-upload-progress/36-UI-SPEC.md.planning/phases/36-inline-upload-progress/36-VALIDATION.md.planning/phases/36-inline-upload-progress/36-VERIFICATION.mdapps/web/src/components/file-browser/FileBrowser.tsxapps/web/src/components/file-browser/FileList.tsxapps/web/src/components/file-browser/UploadItem.tsxapps/web/src/components/file-browser/UploadListItem.tsxapps/web/src/components/file-browser/UploadModal.tsxapps/web/src/components/file-browser/index.tsapps/web/src/hooks/index.tsapps/web/src/hooks/useDropUpload.tsapps/web/src/hooks/useFileUpload.tsapps/web/src/services/upload.service.tsapps/web/src/stores/__tests__/upload-error-recovery.test.tsapps/web/src/stores/upload.store.tsapps/web/src/styles/upload.css
💤 Files with no reviewable changes (5)
- apps/web/src/hooks/index.ts
- apps/web/src/services/upload.service.ts
- apps/web/src/hooks/useFileUpload.ts
- apps/web/src/components/file-browser/UploadItem.tsx
- apps/web/src/components/file-browser/UploadModal.tsx
- Make upload ID collision-safe with incrementing sequence suffix - Guard updateFileProgress against terminal states (complete/error) - Fix orphan unpin: only collect unregistered duplicate-path CIDs, not SDK-registered files that are already in IPNS - Conditionally render retry button only when onRetry and file.file are available - Update VERIFICATION.md to reflect current code (no setFileComplete, no retryFile, atomic cancelFile) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: ca87b369e6a7
|
@coderabbitai please review |
|
🧠 Learnings used✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.planning/phases/36-inline-upload-progress/36-VERIFICATION.md:
- Around line 4-6: The front-matter fields (status:, score:, gaps:) at the top
currently claim success but later text declares a BLOCKER; pick a single
authoritative outcome and make the document consistent: either change the top
front-matter to reflect the blocker (e.g., status: blocked/failed, adjust score
from 8/8 to the correct value, and add the blocking item into gaps: with a short
description) or remove/resolve the blocker language in the later sections so
they match a passed state; update all occurrences of the blocker text (the
phrase “BLOCKER” and “one gap blocks full goal achievement”) to match the chosen
authoritative outcome so the front-matter and body are synchronized.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: db0a3dad-0b07-492c-b015-11ea755e71aa
📒 Files selected for processing (4)
.planning/phases/36-inline-upload-progress/36-VERIFICATION.mdapps/web/src/components/file-browser/UploadListItem.tsxapps/web/src/hooks/useDropUpload.tsapps/web/src/stores/upload.store.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/src/hooks/useDropUpload.ts
- apps/web/src/components/file-browser/UploadListItem.tsx
- apps/web/src/stores/upload.store.ts
Remove stale BLOCKER and gaps language from initial verification — all anti-patterns and gaps have been resolved in subsequent commits. Frontmatter (passed, 8/8, gaps: []) now matches body text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 420fe751454e
The date column is hidden on mobile (responsive.css), making cancel/retry/dismiss inaccessible. Mirror FileListItem's pattern: render actions in both the desktop date cell and a separate .file-list-item-mobile-actions cell that shows on mobile. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 1e66510df185
|
@coderabbitai please review |
|
🧠 Learnings used✅ Actions performedReview triggered.
|
- Hover background now uses :not() to exclude error/complete rows, preserving their state-specific tint on hover - Completion auto-removal (1s timer) moved from UploadListItem's useEffect to setFileStatus in the store, so completed entries are always pruned even if the user navigates away from the folder Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 9277e581df62
Summary
Replaces the floating
UploadModalpopup with inline upload progress rows integrated directly into the file browser list, providing in-context upload feedback.What changed
Map<string, PerFileUpload>replaces batch-level state. Each file has independent status, progress, cancel token, and error. Actions:addFile,updateFileProgress,setFileStatus,removeFile,cancelFile,retryFile.UploadListItemcomponent — inline progress row matching theFileListItemgrid layout. Fine-grained Zustand selector (subscribes to single file entry). Progress bar, cancel/retry/dismiss buttons, 1s completion flash, ARIA accessibility.FileList— upload rows appear at their alphabetical position alongside real files. Folder-scoped filtering viatargetFolderId. Ref-based equality check on the selector prevents re-renders on progress ticks.UploadModal.tsx,UploadItem.tsx, and all popup CSS deleted.useFileUploadhook, redundantsetFileCompleteaction,uploadFiles()batch function. ExtractedcreateUploadId()andisActiveUpload()helpers. MadecancelFileatomic (cancel + delete in singleset()).Files changed (code)
stores/upload.store.tscomponents/file-browser/UploadListItem.tsxcomponents/file-browser/FileList.tsxcomponents/file-browser/FileBrowser.tsxcomponents/file-browser/UploadModal.tsxcomponents/file-browser/UploadItem.tsxhooks/useDropUpload.tshooks/useFileUpload.tsservices/upload.service.tsuploadFiles()batch functionstyles/upload.cssTest plan
tsc --noEmit)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests
Documentation