feat: debounced object list refresh on upload/delete task completion#65
feat: debounced object list refresh on upload/delete task completion#65
Conversation
- Add bucketName to AnyTask type for task filtering - Watch task completions per bucket in BrowserContent with 1500ms debounce - Clean up stale IDs from tracking set when tasks are removed - Remove premature refresh on upload enqueue (upload-picker onSuccess) - Remove premature refresh on delete enqueue (list.tsx handlers) Co-authored-by: overtrue <1472352+overtrue@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adjusts the object browser refresh behavior so the object list updates after upload/delete task completion (not enqueue), and reduces UI flicker by debouncing refreshes during bursts of completed tasks.
Changes:
- Add optional
bucketNametoAnyTaskto support per-bucket filtering. - In the browser content view, listen for completed upload/delete tasks for the current bucket and trigger a debounced refresh.
- Remove immediate refresh calls after delete task creation (since the deletion hasn’t executed yet).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
contexts/task-context.tsx |
Extends AnyTask with bucketName for bucket-scoped task logic. |
app/(dashboard)/browser/content.tsx |
Adds task-completion-driven, debounced refresh logic and removes upload-picker success refresh wiring. |
components/object/list.tsx |
Stops refreshing immediately after enqueueing delete tasks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export type UploadTask = { | ||
| id: string | ||
| kind: "upload" | ||
| status: string | ||
| progress: number | ||
| error?: string | ||
| displayName: string | ||
| subInfo: string | ||
| actionLabel: string | ||
| } | ||
|
|
||
| export type AnyTask = { | ||
| id: string | ||
| kind: string | ||
| status: string | ||
| progress: number | ||
| error?: string | ||
| displayName: string | ||
| subInfo: string | ||
| actionLabel: string | ||
| bucketName?: string | ||
| } |
There was a problem hiding this comment.
AnyTask now includes bucketName, and upload/delete tasks created by the helpers already have a required bucketName. However, the exported UploadTask type in this file does not include bucketName, so it’s now out of sync with the actual shape of upload tasks and the new per-bucket filtering usage. Consider either adding bucketName to UploadTask here or removing this local UploadTask type in favor of importing the canonical UploadTask from lib/upload-task to avoid type drift.
| }, [tasks, bucketName, setRefreshTrigger]) | ||
|
|
||
| React.useEffect(() => { | ||
| return () => { | ||
| if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current) | ||
| } | ||
| }, []) |
There was a problem hiding this comment.
The debounce timer is only cleared on unmount (separate effect). If bucketName changes while a timer is pending (e.g., navigation within the same component instance), the scheduled callback can still fire and trigger a refresh unrelated to the new bucket state. Consider clearing any existing debounceTimerRef.current in the main effect cleanup (or whenever bucketName changes) so pending timers don’t leak across bucket switches/rerenders.
| }, [tasks, bucketName, setRefreshTrigger]) | |
| React.useEffect(() => { | |
| return () => { | |
| if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current) | |
| } | |
| }, []) | |
| return () => { | |
| if (debounceTimerRef.current) { | |
| clearTimeout(debounceTimerRef.current) | |
| debounceTimerRef.current = null | |
| } | |
| } | |
| }, [tasks, bucketName, setRefreshTrigger]) |
Object list was refreshing immediately on task enqueue rather than task completion — meaning uploads/deletes hadn't actually happened yet. Additionally, no debounce meant bulk uploads caused continuous list re-renders and flickering.
Changes
contexts/task-context.tsxbucketName?: stringtoAnyTaskto enable per-bucket task filteringapp/(dashboard)/browser/content.tsxuseTasks()and watch forcompletedupload/delete tasks scoped to the current bucketSetref; prune stale IDs when tasks are removed from the manager to prevent unbounded growthonSuccess={handleRefresh}fromObjectUploadPicker— refresh is now driven by actual completion, not dialog dismissalcomponents/object/list.tsx(onRefresh ?? fetchObjects)()calls fromhandleDelete/handleDeleteAllVersions— tasks weren't executed yet at that pointType of Change
Testing
Checklist
Screenshots (if applicable)
Additional Notes
The manual refresh button in the object list toolbar remains functional and unaffected.
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.