Commit Graph
39000 Commits
Author SHA1 Message Date
Claude 2bb99226a5 refactor(storage): narrow the replace failure instead of typing it any
The handler only needs a tus DetailedError or an Error message, so both
are narrowed from unknown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:17:01 +00:00
Francesco Sansalvadore cb2407b46b update version expiration explanation tooltip 2026-10-05 14:17:01 +00:00
Claude a18b7305fb fix(storage): show the new version in the preview after a replace
The preview URL is cached against the object's path for a week and the
object itself is served with `cache-control: 3600`, so after writing a new
version the panel kept rendering the bytes from before it.

The default preview now pins to the current version's id, which changes the
query key and the URL whenever a new version lands, and `replaceFile`
invalidates every cached URL for the path.

Also reports what Storage actually rejected on a replace: a bad mime type
(415) and an oversized file (413) surfaced as a raw tus error before, while
the ordinary upload path has always explained both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:17:01 +00:00
Claude cf40a39e8c refactor(storage): trim comments to one line where they earn their place
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:17:01 +00:00
Claude 96987d263f feat(storage): replace a file with a new version
Nothing in the dashboard wrote a new version of an existing object. You
could upload a file beside it under a different name, or delete it and
upload again — which on a versioned bucket leaves a delete marker in the
history rather than a clean succession.

Both the row menu and the file preview panel now offer it, reading
"Upload new version" on a versioned bucket and "Replace file" elsewhere,
since that is what each one does.

- `pickFile` creates and clicks the input synchronously, keeping the
  picker inside the user gesture that opened it
- `replaceFile` writes to the object's existing path with `x-upsert`,
  which is what produces the new version. It skips `findPreviousUploads`,
  so an interrupted upload to the same path can't be resumed over this
  one, and invalidates that object's version history on success
- `getStorageItemPath` gives a row's full bucket path one definition;
  `ConfirmPurgeModal` was computing the same thing inline

Uploads have no platform endpoint, so this goes over the same resumable
transport as the existing upload path rather than through `data/`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:17:01 +00:00
Claude d581b1060f chore(storage): let knip see the archived version delete mutation again
The preview pane added here is its first consumer, so the suppression the
earlier PR needed no longer applies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:17:01 +00:00
Claude 1d0a79dab2 fix(storage): preview an archived file and its versions
The pane said no preview was available and the version rows drew a generic
icon, so an archived file was unreadable before restoring it. Every
preview names its version id: the object's own path resolves to the delete
marker that archived it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:41 +00:00
Claude 0219463a33 fix(storage): stop the versioning badge filling its table cell
The badge carried a `flex` class that overrode the primitive's own
`inline-flex`, so in the bucket list it stretched to the cell width
instead of hugging its label.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:41 +00:00
Claude f92d0889ee refactor(storage): reuse the versioning badge in the bucket list
The bucket list had its own plain Enabled / Suspended badges, so the two
surfaces could drift. Both now render BucketVersioningPill, which carries
the icon and the tooltip; the list drops the "Versioning" prefix because
its column header already supplies it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:40 +00:00
Claude 455b077312 refactor(storage): move the versioning badge next to the bucket actions
The badge now sits left of Policies rather than in the breadcrumb, spells
out the state it reports, and leaves the tooltip to say what that state
means instead of repeating the label. Enabled reads as a neutral fact, so
it uses the default badge rather than success.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:40 +00:00
Claude 53e4c8cc39 fix(storage): say archiving, not deleting, in the versioning pill tooltip
On a versioned bucket the row action reads Archive, so the tooltip now
matches what the user actually clicks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:40 +00:00
Claude eb754bccc4 fix(storage): bound the versioning pill tooltip width
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:40 +00:00
Claude 3c32bb78f5 fix(storage): show a restored file without a refresh
Restoring invalidated the archived list, so the archived row went away, but
the file did not come back: the live listing is the explorer's own state
rather than a React Query cache, so nothing refetched it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:40 +00:00
Claude 48d8d95a39 refactor(storage): trim comments to one line where they earn their place
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:40 +00:00
Claude 9e23c4db7d feat(storage): show a versioning pill in the bucket breadcrumbs
The bucket page named the bucket and flagged it as Public, but said
nothing about versioning — you had to open bucket settings to find out
whether writes were keeping recoverable copies.

`BucketVersioningPill` sits beside the bucket name, brand for enabled and
warning for suspended, with the distinction in its tooltip. A bucket that
has never been versioned renders nothing: that is most buckets, and a
badge on all of them says nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:39 +00:00
Claude 0369a4b140 feat(storage): address archived objects by path in the preview pane
The archived mutations take the object's full bucket-relative path, which
is what the delete endpoint addresses. `ArchivedObject` already carries it.

The buckets list's Versioning column needs no change: it reads
`getBucketVersioningState`, which now reports the bucket's real status
instead of `disabled` for everything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:39 +00:00
Francesco SansalvadoreandClaude Sonnet 5 5b0ad2b573 feat(storage): add the archived file preview pane
Clicking an archived row now opens a preview pane for it: where the file used to
live, when it was archived, its retained versions, and the actions to bring it
back or delete it for good. This is what makes the Archive action in the file
preview panel a round trip rather than a one-way door.

- `ArchivedFilePreviewPane` — the pane, replacing `PreviewPane` while an archived
  row is selected
- `ArchivedVersionRestoreWidget` — takes over the top of the pane when a specific
  retained version is selected
- `ArchivedVersionRow` — one version, with its restore / delete actions
- `StorageExplorer` — the pane swap

Plus the tail of the feature, all behind the feature preview:

- The buckets list gains a Versioning column. The prototype hardcoded the column
  total in three places; a `useVersioningColumnSpan` hook now supplies the delta
  so the several `colSpan`s can't drift. Note the table was restructured on
  master into a shared standard/virtualized shape since the prototype, so this is
  a fresh implementation rather than a port.
- `DeleteBucketModal` and `EmptyBucketModal` say what destruction takes with it
  once a bucket is retaining archived files and versions.

Fixes carried over rather than ported:

The prototype's version rows were an `<li role="button" tabIndex={0}>` with a
dropdown trigger nested inside — interactive controls inside an interactive
element. They're a real `<button>` beside the menu now, so Enter and Space work
and the menu isn't swallowed.

The prototype routed permanent version deletes through two separate mutations
depending on whether the version was current at archive time; that's one
mutation with a `wasCurrentAtArchive` variable, so the API shape stays a
one-file change.

The prototype duplicated the same confirmation copy, ternary-inside-ternary in
JSX, across two files. There's one consumer now and the copy is a flat helper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-05 14:16:39 +00:00
Claude ce44a02cc9 chore(storage): let knip see the archived purge and restore mutations again
The row menu added here is their first consumer, so the suppression the
previous PR needed no longer applies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:39 +00:00
Claude 1cbfb20998 fix(storage): give the archived row's icon button an explicit tab index
The lint preset errors on a focusable element without one, so the branch
failed the lint job.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:17 +00:00
Claude 41384f8750 test(storage): type the file explorer row fixtures instead of casting to any
One typed factory replaces five casts, so a row fixture can no longer
drift from the prop it stands in for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:17 +00:00
Claude f85cb868f7 fix(storage): give an archived explorer row its real mime type
It was hardcoded empty, so every archived row drew the generic file icon
whatever it held.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:16 +00:00
Claude 46778a253e feat(storage): act on an archived folder from its own row menu
An archived folder had no menu, so an archived empty folder could only be
reached through the placeholder file the listing now hides. Restore and
Delete permanently apply to every archived object under the prefix, which
is all a folder is. Live folders on a versioned bucket gain the same pair,
with Delete renamed to Archive to match what it does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:16 +00:00
Claude 99607b2f32 refactor(storage): move the archived icon into the file icon slot
An archived row has no checkbox to swap in, so the archive icon takes the
leading slot outright and the trailing badge goes away. That drops the
badge's solid background, which only existed to keep the icon readable
with the file name running under it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:16 +00:00
Claude 54624698ef feat(storage): give archived rows their own actions menu
Archived rows dropped the row menu entirely, leaving the badge as their
only control. They get it back with the two actions an archived file can
take: restore, and delete permanently. Downloading, copying a URL,
renaming and moving have no live object to act on.

The menu markup moves into `RowActionsMenu` so both kinds of row render the
same control rather than a copy of it, and the context menu opens the same
options for an archived file. A synthesized folder still has none, since it
is not a real object.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:16 +00:00
Claude dbe24411b3 fix(storage): refresh the archived list whenever the explorer refreshes
Archiving a file refetched the folder listing, so the row vanished, but
left the archived list cached — so it never came back as an archived row
until a manual refresh.

The archived overlay is part of what the explorer shows, so invalidating it
belongs in `refetchAllOpenedFolders` rather than at each call site. That
covers deleting, purging, uploading and the refresh button in one place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:16 +00:00
Claude f00758054a fix(storage): keep the icon on archived rows and badge them with an icon
An archived row has no checkbox, but it still carried the rule that hides
the row icon on hover to make room for one — so hovering left the slot
empty. The rule now applies only where a checkbox actually swaps in.

The Archived label becomes the archive icon with the word as a tooltip, on
a solid background: the row's stripes and a long file name both run
underneath it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:16 +00:00
Claude 59e6c9eb11 refactor(storage): trim comments to one line where they earn their place
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:16 +00:00
Francesco SansalvadoreandClaude Sonnet 5 539015ecc3 feat(storage): show archived files inline in the file explorer
Adds "Show archived" to the file explorer's view options. With it on, archived
files appear in place in the listing — dimmed and striped, with an Archived
badge — instead of being invisible. Folders that exist only because something
archived sits inside them are synthesized too, so a nested archived file is
reachable by drilling in.

The badge is the only way to reach the file for now; the archived file preview
pane it opens arrives in the next PR.

- `useStoragePreference` — `showArchivedInline`, stored per project
- `ArchivedFilesContext` — owns the archived list and which object is selected
- `FileExplorer` — merges archived rows into each column and re-sorts, since the
  server-side sort never sees them
- `FileExplorerRow` — dimmed striped rendering, no checkbox, no context menu
- `FileExplorerHeader` — the toggle, rendered only behind the feature preview
- `StorageExplorer` — mounts the provider

Three prototype problems fixed rather than carried over:

The prototype fetched the archived list with no `enabled` gate, so every bucket
page fetched it whether or not the feature was on. It also mounted a second
subscription **per archived row** just to resolve that row's id on click. Both
are now one query in the provider, gated on the flag, the toggle and the bucket.

The prototype's archived marker was an `aria-hidden` span, and archived rows drop
both the checkbox and the context menu — so they had nothing keyboard-reachable
and nothing a screen reader could announce. The badge is now a real button that
opens the file, with folders keeping a plain label since they have no object to
open.

The stripe pattern was a hardcoded `rgba(120,120,120,0.05)`; it now derives from
`currentColor` so it tracks the row's text color across themes.

The overlay is deliberately not gated on the bucket's versioning state: a
never-versioned bucket simply has nothing archived, and a suspended one can still
be retaining files, so the query result is the honest answer either way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-05 14:16:15 +00:00
Claude ca61db446e chore(storage): hold knip off the archived mutations until their consumers land
Each is reported as an unused file on this PR only; the rows and preview pane
that call them come later in the stack, and those PRs drop the entries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 14:16:15 +00:00
Claude b9cb0cb926 test(storage): type the archived objects fixture instead of casting to any
Same treatment as the object versions fixture: the row type comes from
the function under test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:47:02 +00:00
Claude 48ddb59fef feat(storage): carry an archived version's mime type through the query
The API returns it and the mapper dropped it, so nothing downstream could
tell an image from a PDF. The archived preview needs it to render.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:47:02 +00:00
Claude 481823e4c5 fix(storage): hide the empty folder placeholder from archived rows
The live listing filters it out of every folder, but the archived overlay
was rendering it as a file. It still rolls its folder up, so an archived
empty folder stays visible rather than exposing the placeholder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:47:01 +00:00
Claude b00e1f1080 refactor(storage): trim comments to one line where they earn their place
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:47:01 +00:00
Claude 1fa25ea03c feat(storage): fetch and mutate archived objects against Storage
An archived object is one whose top row is a delete marker: gone from the
live listing, versions still retained. `list-v2` reports those rows once
`deleteMarkers` and `noncurrentVersions` are included, so the archived
list is a grouping of that listing rather than a dedicated endpoint.

The flat, undelimited listing is deliberate — the inline overlay
synthesizes folders from full paths, so it needs the whole bucket, not one
level at a time. Paging is bounded.

- Restoring deletes the delete marker, which promotes the version beneath
  it back to current; nothing is copied
- Purging deletes every retained version and the marker, since leaving the
  marker would keep the object listed as archived with nothing under it
- Deleting one retained version addresses it by `{ path, versionId }`

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:47:01 +00:00
Francesco SansalvadoreandClaude Sonnet 5 d868416c1b feat(storage): add archived objects data layer
On a versioned bucket a delete is a soft delete, and the file preview panel
already calls that action Archive and promises the versions stay recoverable.
Nothing in the dashboard lets a user see or restore an archived file yet. This is
the data layer for that, with no UI: query and mutation shapes written to the
studio conventions, endpoints stubbed, returning empty.

- `archived-objects-query.ts` — `ArchivedObject` / `ArchivedObjectVersion` types
  and `archivedObjectsQueryOptions`
- `archived-object-restore-mutation.ts` — bring an archived object back
- `archived-object-purge-mutation.ts` — delete it and every version, permanently
- `archived-object-version-delete-mutation.ts` — remove one retained version
- `archivedOverlay.utils.ts` — synthesizes the explorer rows for one folder from
  the archived list
- `archivedVersions.utils.ts` — an archived object's history as one flat list

Two prototype problems fixed rather than carried over:

The prototype identified the "was current when archived" row by the sentinel
`versionId === objectId`, which was load-bearing across three files and would
break the moment real version ids arrived. `ArchivedObject` now carries a
`currentVersion` record, so merging invents no fields and the distinction is an
explicit `wasCurrentAtArchive` flag.

The prototype's object had both `name` and `originalPath`, inconsistently — the
path normalization existed mostly to strip a duplicated leaf folder that
inconsistency produced. There is now a single `path`, and `getArchivedSegments`
is the only function that interprets it, so a different API shape is a one-place
change.

Also drops `deletedBy` and `expiresAt`, which the prototype never rendered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-05 13:47:01 +00:00
Claude 55f69b2895 refactor(storage): trim comments to one line where they earn their place
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:47:01 +00:00
Francesco SansalvadoreandClaude Sonnet 5 1c8734a2bc fix(storage): restore the pointer to where retention is configured
The breakdown is the only place on the org usage page that tells a user how to
act on the noncurrent bytes they're being billed for, and the navigation path had
been trimmed out of it.

Also flags why the per-bucket rows aren't links: buckets are per-project and this
is an org-level page, so the endpoint needs to return a project ref before a row
can be linked — or even disambiguated, since two projects can both have an
`avatars` bucket.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-05 13:47:00 +00:00
Francesco Sansalvadore ac840959fa improve comments 2026-10-05 13:47:00 +00:00
Francesco SansalvadoreandClaude Sonnet 5 171108efff feat(storage): break down retained storage on the org usage page
Splits Storage Size into current versus noncurrent objects, and attributes the
noncurrent portion to the buckets responsible, so the cost of object versioning
sits with the metric it inflates.

The split is flag-aware. With the preview off, Storage Size renders exactly the
single real series it does today; `USAGE_CATEGORIES` takes an options argument
and only emits the two stacked segments when the preview is on.

Noncurrent is deliberately not split further into noncurrent versions and
soft-deleted files: the platform can't reliably tell the two apart after the
fact, and the user's lever for shrinking either is the same lifecycle policy.
Using S3's own terms also reads better than Studio-only words like "live".

- `storage-retention-usage-query.ts` — org-scoped (the prototype keyed this
  org-level data under `['projects', undefined, …]`), real `queryOptions` shape
  with the endpoint stubbed
- `StorageRetention.constants.ts` — one definition of the two segments, used by
  both the chart attributes and the breakdown table, so labels, colors and key
  casing cannot drift
- `StorageRetention.utils.ts` + tests — maps retention days onto chart points
- `StorageRetentionBreakdown.tsx` — the segment table, retained-data warning,
  and per-bucket list, rendered through the existing `additionalInfo` hook
- `Usage.colors.ts` — `COLOR_MAP` and `AttributeColor` moved out of
  `Usage.constants` so the breakdown can read a color token without a cycle
  back through the module that renders it

Known limitation: with the preview on, the stacked chart and the breakdown read
zeros until the retention endpoint exists. No mock data is shipped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-05 13:47:00 +00:00
Claude fed0fecf53 fix(storage): keep the preview panel's URL button and row handles as they were
The redesign renamed the panel's "Get URL" button to "Copy URL" while the row
context menu kept "Get URL", leaving one action with two names, and gave the
panel's file name a `title` — the attribute the explorer rows use as their
handle, so `getByTitle` matched two elements once a preview was open.

Also snap five off-token sizes to the scale the ratchet enforces, and point the
E2E delete helper at the confirmation's real label now that it says what it
does instead of ConfirmationModal's "Submit" default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:46:41 +00:00
Claude a30e2bafb6 fix(storage): say the version matched the policy and will be deleted
Leads with what happened and that it is permanent, then bounds the wait.
The previous wording blamed a missing schedule; the real reason there is no
exact timestamp is that the cleanup spans several systems.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:38:36 +00:00
Claude 5b734c8775 fix(storage): say cleanup can take up to 24 hours
There is no computable expiry timestamp: cleanup runs at no fixed moment
and removal can lag by a day, so the tooltip says so rather than implying
the next pass is imminent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:38:36 +00:00
Claude 701e193260 feat(storage): say a version is queued to expire, not expiring now
"Expiring now" claimed a moment that does not exist: cleanup is a periodic
pass, so a version that has met the policy stays listed until it runs. The
tooltip says why, within a max width.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:38:36 +00:00
Claude d02989069c fix(storage): refresh the row after restoring a version
Restoring a noncurrent version invalidated the version list but left the
explorer row showing the old size, type and modified date, since the live
listing is the explorer's own state rather than a React Query cache.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:38:35 +00:00
Claude 5e027db3dd refactor(storage): trim comments to one line where they earn their place
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:38:35 +00:00
Claude a9d953b999 fix(storage): preview a version's own bytes, not the current ones
The version history rendered a generic mime-type icon per row, and the
compare widget put that same icon on both sides — so every entry for a
file looked identical and the comparison showed nothing to compare.

The sign and public-url endpoints already accept `options.versionId`;
nothing asked for it. `useFetchFileUrlQuery` now takes a `versionId` and
keys on it, and the preview rendering moves out of `PreviewPane` into a
`FilePreview` component both the pane and the compare widget use, so a
version preview cannot silently fall back to the current bytes.

Image rows in the history list render their own thumbnail under 5MB;
larger files and other mime types keep the icon rather than pull a whole
object down for a 28px box.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:38:35 +00:00
Claude 732d680563 feat(storage): offer Archive and Delete permanently from the row menu
The file preview panel already splits the two on a versioned bucket, but
the explorer's own row menu still offered a single "Delete" — which
archives there, without saying so.

The row menu now reads "Archive" on a versioned bucket and gains a
"Delete permanently" entry beside it.

`ConfirmPurgeModal` is mounted once by the explorer and driven by
`itemToPurge` on the store, the same shape the row delete already uses.
The preview panel routes its own permanent delete through it too, so the
confirmation copy exists in one place rather than two.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:38:35 +00:00
Claude 52f82ebb1f feat(storage): drive version history off the real endpoints
The preview panel now addresses versions by the object's full path, which
is what the list, move and delete endpoints take, rather than by the leaf
name the explorer renders. `VersionHistory` keeps `objectName` for copy
and takes `path` separately.

Expiry countdowns read the bucket's stored lifecycle policy instead of an
empty placeholder, so a row's fate reflects the policy that governs it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
2026-10-05 13:38:35 +00:00
Francesco SansalvadoreandClaude Sonnet 5 c8f4433c2b fix(storage): tell the truth in the empty version history
The empty state read "Overwriting this file will retain a recoverable copy here"
regardless of whether the bucket was actually versioned. Since
`getBucketVersioningState` reports `disabled` for every bucket until the API
exposes the field, that promise was showing on every file in every bucket.

Splits it: `disabled` now says versioning is off and points at the bucket
settings; `enabled`/`suspended` keep the original copy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-05 13:38:34 +00:00
Francesco Sansalvadore f6a3797322 improve comments 2026-10-05 13:38:34 +00:00