From efc47d519d138f0a8fc333933c30ac3608f23cca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:17:55 +0000 Subject: [PATCH 1/2] Initial plan From b1f5dde7a75b8a7524cb3db0b878b18aa02ceda4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:24:45 +0000 Subject: [PATCH 2/2] feat: debounced object list refresh on upload/delete task completion - 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> --- app/(dashboard)/browser/content.tsx | 33 ++++++++++++++++++++++++++++- components/object/list.tsx | 2 -- contexts/task-context.tsx | 1 + 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/(dashboard)/browser/content.tsx b/app/(dashboard)/browser/content.tsx index e84f0d31..9c6f3515 100644 --- a/app/(dashboard)/browser/content.tsx +++ b/app/(dashboard)/browser/content.tsx @@ -13,6 +13,7 @@ import { ObjectUploadPicker } from "@/components/object/upload-picker" import { useBucket } from "@/hooks/use-bucket" import { useMessage } from "@/lib/feedback/message" import { buildBucketPath } from "@/lib/bucket-path" +import { useTasks } from "@/contexts/task-context" interface BrowserContentProps { bucketName: string @@ -100,6 +101,37 @@ export function BrowserContent({ bucketName, keyPath = "", preview = false, prev setRefreshTrigger((n) => n + 1) } + const tasks = useTasks() + const debounceTimerRef = React.useRef | null>(null) + const prevCompletedIdsRef = React.useRef(new Set()) + + React.useEffect(() => { + const currentIds = new Set(tasks.map((t) => t.id)) + for (const id of prevCompletedIdsRef.current) { + if (!currentIds.has(id)) prevCompletedIdsRef.current.delete(id) + } + const completedForBucket = tasks.filter( + (t) => + (t.kind === "upload" || t.kind === "delete") && + t.bucketName === bucketName && + t.status === "completed", + ) + const newCompletions = completedForBucket.filter((t) => !prevCompletedIdsRef.current.has(t.id)) + if (newCompletions.length > 0) { + newCompletions.forEach((t) => prevCompletedIdsRef.current.add(t.id)) + if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current) + debounceTimerRef.current = setTimeout(() => { + setRefreshTrigger((n) => n + 1) + }, 1500) + } + }, [tasks, bucketName, setRefreshTrigger]) + + React.useEffect(() => { + return () => { + if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current) + } + }, []) + return ( @@ -152,7 +184,6 @@ export function BrowserContent({ bucketName, keyPath = "", preview = false, prev onShowChange={setUploadPickerOpen} bucketName={bucketName} prefix={prefix} - onSuccess={handleRefresh} /> ) diff --git a/components/object/list.tsx b/components/object/list.tsx index e7c0f408..f5b75b40 100644 --- a/components/object/list.tsx +++ b/components/object/list.tsx @@ -438,7 +438,6 @@ export function ObjectList({ message.success(t("Delete task created")) } table.resetRowSelection() - ;(onRefresh ?? fetchObjects)() } catch (err) { message.error((err as Error)?.message ?? t("Delete Failed")) } @@ -454,7 +453,6 @@ export function ObjectList({ message.success(t("Delete task created")) } table.resetRowSelection() - ;(onRefresh ?? fetchObjects)() } catch (err) { message.error((err as Error)?.message ?? t("Delete Failed")) } diff --git a/contexts/task-context.tsx b/contexts/task-context.tsx index 9cf8c6c9..c9cef37d 100644 --- a/contexts/task-context.tsx +++ b/contexts/task-context.tsx @@ -27,6 +27,7 @@ export type AnyTask = { displayName: string subInfo: string actionLabel: string + bucketName?: string } const emptyTasks: AnyTask[] = []