diff --git a/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx b/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx
index 28386df8cf8..b68d4481452 100644
--- a/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx
+++ b/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx
@@ -44,6 +44,7 @@ import {
import { inverseValidBucketNameRegex, validBucketNameRegex } from './CreateBucketModal.utils'
import {
getVersioningPlanLimits,
+ setMockBucketProtection,
useIsStorageProtectionEnabled,
type VersioningPlanLimits,
} from './StorageProtection.constants'
@@ -169,6 +170,20 @@ export const CreateBucketModal = ({ open, onOpenChange }: CreateBucketModalProps
})
track('storage_bucket_created', { bucketType: 'STANDARD' })
+ // [Prototype] Object versioning has no platform API yet — persist it to
+ // the in-memory mock store so the buckets list reflects it right away.
+ setMockBucketProtection(values.name, {
+ versioning: values.enable_versioning ? 'enabled' : 'disabled',
+ versionExpiryDays:
+ values.enable_versioning && typeof values.version_expiry_days === 'number'
+ ? values.version_expiry_days
+ : null,
+ maxNoncurrentVersions:
+ values.enable_versioning && typeof values.max_noncurrent_versions === 'number'
+ ? values.max_noncurrent_versions
+ : null,
+ })
+
toast.success(`Successfully created bucket ${values.name}`)
form.reset()
setSelectedUnit(StorageSizeUnits.MB)
diff --git a/apps/studio/components/interfaces/Storage/EditBucketModal.tsx b/apps/studio/components/interfaces/Storage/EditBucketModal.tsx
index 7668f53ee32..5fe89daa6bf 100644
--- a/apps/studio/components/interfaces/Storage/EditBucketModal.tsx
+++ b/apps/studio/components/interfaces/Storage/EditBucketModal.tsx
@@ -36,6 +36,7 @@ import {
import {
getMockBucketProtection,
getVersioningPlanLimits,
+ setMockBucketProtection,
useIsStorageProtectionEnabled,
type VersioningPlanLimits,
} from '@/components/interfaces/Storage/StorageProtection.constants'
@@ -188,6 +189,20 @@ export const EditBucketModal = ({ visible, bucket, onClose }: EditBucketModalPro
}
}
+ // [Prototype] Object versioning has no platform API yet — persist it to
+ // the in-memory mock store so the buckets list reflects it right away.
+ setMockBucketProtection(bucket.id, {
+ versioning: values.enable_versioning ? 'enabled' : 'disabled',
+ versionExpiryDays:
+ values.enable_versioning && typeof values.version_expiry_days === 'number'
+ ? values.version_expiry_days
+ : null,
+ maxNoncurrentVersions:
+ values.enable_versioning && typeof values.max_noncurrent_versions === 'number'
+ ? values.max_noncurrent_versions
+ : null,
+ })
+
updateBucket({
projectRef: ref,
id: bucket.id,
diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilePreviewPane.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilePreviewPane.tsx
index 11f3b91698d..5529bc51b6b 100644
--- a/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilePreviewPane.tsx
+++ b/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilePreviewPane.tsx
@@ -120,7 +120,7 @@ export const DeletedFilePreviewPane = () => {
Restore
}
disabled={!canUpdateFiles || file.heldBySnapshot}
onClick={() => setShowDeleteConfirm(true)}
diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesHeaderSelection.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesHeaderSelection.tsx
index d71d19db21d..8c3444dd7e4 100644
--- a/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesHeaderSelection.tsx
+++ b/apps/studio/components/interfaces/Storage/StorageExplorer/DeletedFilesHeaderSelection.tsx
@@ -102,7 +102,7 @@ export const DeletedFilesHeaderSelection = () => {
}
disabled={!canUpdateFiles || isEveryItemHeld}
diff --git a/apps/studio/components/interfaces/Storage/StorageProtection.constants.ts b/apps/studio/components/interfaces/Storage/StorageProtection.constants.ts
index 56bbff4b9e0..d3e926cdb3b 100644
--- a/apps/studio/components/interfaces/Storage/StorageProtection.constants.ts
+++ b/apps/studio/components/interfaces/Storage/StorageProtection.constants.ts
@@ -94,5 +94,15 @@ const DEFAULT_PROTECTION: BucketProtection = {
export const getMockBucketProtection = (bucketName: string | undefined): BucketProtection =>
(bucketName && PROTECTED_BUCKETS[bucketName]) || DEFAULT_PROTECTION
+/**
+ * Writes a bucket's versioning settings into the in-memory mock store, so
+ * changes made via the create/edit bucket modals are reflected immediately
+ * elsewhere (e.g. the buckets list "Versioning" column) without a real API.
+ * Resets on page refresh, since it's plain module state.
+ */
+export const setMockBucketProtection = (bucketName: string, protection: BucketProtection): void => {
+ PROTECTED_BUCKETS[bucketName] = protection
+}
+
export const isBucketVersioned = (bucketName: string | undefined) =>
getMockBucketProtection(bucketName).versioning === 'enabled'
diff --git a/apps/studio/components/interfaces/Storage/Trash/Trash.tsx b/apps/studio/components/interfaces/Storage/Trash/Trash.tsx
index 4902d9aecd5..2e24f459237 100644
--- a/apps/studio/components/interfaces/Storage/Trash/Trash.tsx
+++ b/apps/studio/components/interfaces/Storage/Trash/Trash.tsx
@@ -92,7 +92,7 @@ export const Trash = ({ bucketId }: TrashProps) => {
{isSuccess && objects.length > 0 && (
}
disabled={objects.length === heldInBucketCount}
onClick={() => setShowDeleteAll(true)}
diff --git a/apps/studio/components/interfaces/Storage/Trash/TrashList.tsx b/apps/studio/components/interfaces/Storage/Trash/TrashList.tsx
index 1b81ed5b75b..65f08732177 100644
--- a/apps/studio/components/interfaces/Storage/Trash/TrashList.tsx
+++ b/apps/studio/components/interfaces/Storage/Trash/TrashList.tsx
@@ -113,7 +113,7 @@ export const TrashList = ({
Restore
onDeleteForever(object)}
tooltip={{
diff --git a/apps/studio/components/interfaces/Storage/Trash/TrashSelectionBar.tsx b/apps/studio/components/interfaces/Storage/Trash/TrashSelectionBar.tsx
index 54c531bd6b3..daf63fd0863 100644
--- a/apps/studio/components/interfaces/Storage/Trash/TrashSelectionBar.tsx
+++ b/apps/studio/components/interfaces/Storage/Trash/TrashSelectionBar.tsx
@@ -58,7 +58,7 @@ export const TrashSelectionBar = ({
}
disabled={!canUpdateFiles || isEveryItemHeld}
diff --git a/apps/studio/data/storage/protection/bucket-trash-query.ts b/apps/studio/data/storage/protection/bucket-trash-query.ts
index 1014749d57a..133dc5ff68b 100644
--- a/apps/studio/data/storage/protection/bucket-trash-query.ts
+++ b/apps/studio/data/storage/protection/bucket-trash-query.ts
@@ -2,7 +2,13 @@ import { useMutation, UseMutationOptions, useQuery, useQueryClient } from '@tans
import { toast } from 'sonner'
import { storageKeys } from '../keys'
-import { getMockTrashObjects, mockDelay, type TrashObject } from './protection-mocks'
+import {
+ deleteMockTrashObjectsPermanently,
+ getMockTrashObjects,
+ mockDelay,
+ restoreMockTrashObjects,
+ type TrashObject,
+} from './protection-mocks'
export type BucketTrashVariables = {
projectRef?: string
@@ -29,7 +35,10 @@ export const useBucketTrashRestoreMutation = ({
}: UseMutationOptions = {}) => {
const queryClient = useQueryClient()
return useMutation({
- mutationFn: () => mockDelay(undefined, 500),
+ mutationFn: async (variables) => {
+ restoreMockTrashObjects(variables.objectIds)
+ await mockDelay(undefined, 500)
+ },
async onSuccess(data, variables, context) {
await queryClient.invalidateQueries({
queryKey: storageKeys.trash(variables.projectRef, variables.bucketId),
@@ -57,7 +66,10 @@ export const useBucketTrashDeleteMutation = ({
}: UseMutationOptions = {}) => {
const queryClient = useQueryClient()
return useMutation({
- mutationFn: () => mockDelay(undefined, 600),
+ mutationFn: async (variables) => {
+ deleteMockTrashObjectsPermanently(variables.objectIds)
+ await mockDelay(undefined, 600)
+ },
async onSuccess(data, variables, context) {
await queryClient.invalidateQueries({
queryKey: storageKeys.trash(variables.projectRef, variables.bucketId),
diff --git a/apps/studio/data/storage/protection/protection-mocks.ts b/apps/studio/data/storage/protection/protection-mocks.ts
index b6bd8e40996..07e458d6367 100644
--- a/apps/studio/data/storage/protection/protection-mocks.ts
+++ b/apps/studio/data/storage/protection/protection-mocks.ts
@@ -6,6 +6,11 @@
* designed and demoed end-to-end. The query and mutation hooks in this folder
* are shaped exactly like the real ones so swapping in a real fetcher later is
* a localized change.
+ *
+ * The trash store below lives in a plain module-level variable (not
+ * persisted), so restoring/permanently deleting a file during a session is
+ * visible immediately across the app, and resets the moment the page is
+ * refreshed.
*/
export type ObjectVersionAction = 'initial upload' | 'overwrite' | 'restore'
@@ -80,7 +85,12 @@ export const getMockObjectVersions = (_objectName: string): ObjectVersion[] => [
},
]
-export const getMockTrashObjects = (_bucketId: string): TrashObject[] => [
+/**
+ * Mutable in-memory "deleted files" store. Shared across buckets, mirroring
+ * the previous stateless mock's behavior of returning the same list
+ * regardless of bucketId — only mutated via restore/delete below.
+ */
+let trashObjects: TrashObject[] = [
{
id: 'trash-1',
name: 'round-3/final.png',
@@ -111,7 +121,77 @@ export const getMockTrashObjects = (_bucketId: string): TrashObject[] => [
expiresAt: null,
heldBySnapshot: true,
},
+ {
+ id: 'trash-4',
+ name: 'avatars/user-42.jpg',
+ originalPath: 'avatars/',
+ deletedAt: daysAgo(5, '16:45:00'),
+ deletedBy: 'mark@acme.co',
+ size: 2.3 * MB,
+ expiresAt: daysAhead(25),
+ heldBySnapshot: false,
+ },
+ {
+ id: 'trash-5',
+ name: '2026-06-report.pdf',
+ originalPath: 'exports/',
+ deletedAt: daysAgo(7, '10:00:00'),
+ deletedBy: 'api key ····f21',
+ size: 512 * KB,
+ expiresAt: daysAhead(23),
+ heldBySnapshot: false,
+ },
+ {
+ id: 'trash-6',
+ name: 'round-2/preview.webp',
+ originalPath: 'matches/round-2/thumbnails/',
+ deletedAt: daysAgo(2, '19:30:00'),
+ deletedBy: 'jane@acme.co',
+ size: 96 * KB,
+ expiresAt: null,
+ heldBySnapshot: true,
+ },
+ {
+ id: 'trash-7',
+ name: 'debug-2026-07-01.log',
+ originalPath: 'logs/',
+ deletedAt: daysAgo(14, '03:12:00'),
+ deletedBy: 'system',
+ size: 3.4 * MB,
+ expiresAt: daysAhead(16),
+ heldBySnapshot: false,
+ },
+ {
+ id: 'trash-8',
+ name: 'old-assets.zip',
+ originalPath: 'archive/',
+ deletedAt: daysAgo(29, '23:55:00'),
+ deletedBy: 'jane@acme.co',
+ size: 18 * MB,
+ expiresAt: daysAhead(1),
+ heldBySnapshot: false,
+ },
]
+export const getMockTrashObjects = (_bucketId: string): TrashObject[] => [...trashObjects]
+
+/** Removes the given ids from the trash store, simulating a restore. */
+export const restoreMockTrashObjects = (objectIds: string[]): TrashObject[] => {
+ trashObjects = trashObjects.filter((object) => !objectIds.includes(object.id))
+ return [...trashObjects]
+}
+
+/**
+ * Removes the given ids from the trash store, simulating a permanent delete.
+ * When `objectIds` is omitted, deletes every object that isn't held by a
+ * snapshot (mirrors the "Delete all permanently" action).
+ */
+export const deleteMockTrashObjectsPermanently = (objectIds?: string[]): TrashObject[] => {
+ trashObjects = objectIds
+ ? trashObjects.filter((object) => !objectIds.includes(object.id))
+ : trashObjects.filter((object) => object.heldBySnapshot)
+ return [...trashObjects]
+}
+
export const mockDelay = (value: T, ms = 350): Promise =>
new Promise((resolve) => setTimeout(() => resolve(value), ms))