MinusCircle read as "removed / null" — Archive reads closer to the
delete-marker's actual role (a placeholder recording that a delete
was applied at this point in history).
"Without one, every noncurrent version is retained indefinitely and
keeps counting toward storage costs." — replaces the previous two-
sentence description.
The Collapse wrapper's fixed overflow-hidden was clipping focus rings
and outlines on nested inputs at rest. Switch to a variants-based
approach where overflow: hidden is only in effect while the height is
actually animating — transitionEnd flips it back to visible once the
enter animation settles.
Wrap conditional sections in framer-motion AnimatePresence so height +
opacity ease in and out instead of snapping — subtle (180ms), applied
to:
- The suspension notice / upgrade prompt / public-bucket + tightening
warnings / whole LifecyclePolicySection (versioning toggle).
- The both/either mode row (both retention conditions set).
- The "No lifecycle policy" empty-state warning.
`initial={false}` on each AnimatePresence so a section that's already
open on modal-open doesn't animate in on first paint — only actual
user-driven toggles animate.
- Trim the section description to "Automatically expire noncurrent
versions".
- Dim the entire Retained noncurrent versions row (label + hint +
input) at opacity-60 when the age field is empty, so the dependency
reads at a glance rather than only the input being greyed out.
Version history:
- Add "delete marker" as a first-class ObjectVersion action and seed
one into the mock so a stale marker shows up in the history.
- Render it with a struck/dashed thumbnail glyph and a "Delete marker"
label, and drop the download/URL/restore actions — the only useful
action on an empty placeholder is permanently removing it.
- Row is non-clickable (nothing to preview) and dimmed.
- Confirmation copy is tailored to markers (no version id shown, no
"current version is not affected" — there's no meaningful content).
Bucket lifecycle policy form:
- Add a one-line description under the "Lifecycle policy" heading
explaining what these policies do.
- Disable the "Retained noncurrent versions" input when
"Noncurrent version expiration" is empty (S3 requires the
noncurrent-days condition on any noncurrent-count rule). A short
description under the field explains the dependency.
- Clear the retained value on the exact age→empty transition so a
save can never persist an orphaned cap, but keep any pre-existing
orphaned value visible (and editable once age is set again) rather
than silently discarding it on modal open.
Replace the FormItemLayout wrapper for the both/either row with a
plain label + RadioGroupStacked stack — the previous default-vertical
FormItemLayout wrap was producing the correct DOM but the effect
wasn't visible depending on cache/reload state. Explicit
`flex flex-col gap-y-2 w-full` removes any ambiguity: label above,
radio group below, spanning the full section width.
Move the both/either RadioGroupStacked to the bottom of the lifecycle
policy section and drop layout="flex-row-reverse" — with the default
vertical layout the label sits above the radio group and the group
spans the full section width, so the two-line option descriptions have
room to breathe. Descriptions tightened to fit the wider footprint.
Wrap the both/either RadioGroupStacked in FormItemLayout with
layout="flex-row-reverse", matching the two number inputs above and
below — so the label sits on the left and the radio group aligns with
the input controls above/below.
Options relabelled to read naturally with the new "Expire when" left-
hand label ("Both conditions are met" / "Either condition is met").
Swap the ToggleGroup BOTH/EITHER control for a stacked RadioGroup from
the design system — each option gets a concise one-sentence
description of which logic it applies, matching the way similar
mutually-exclusive choices are surfaced elsewhere in Studio.
Lifecycle policy section:
- Drop the descriptive intro sentence, the broom icon on the heading,
and the collapsible "See how this works" example — three ways of
saying the same thing, per feedback.
- Move the BOTH / EITHER join-mode toggle out of the sentence and into
a proper ToggleGroup that sits between the two condition inputs,
visually joining them (only rendered when both conditions are set —
a single condition has nothing to combine).
Suspension flow:
- Trim the inline admonition down to a one-liner heads-up.
- The full "already-retained data stays, no new versions get created,
re-enable any time" explanation moves to a ConfirmationModal that
fires on save when the user is suspending a live-versioning bucket;
confirming replays the write, cancelling bails.
- No confirmation for buckets that were already suspended.
Renames the noun phrase that names the policy object — the section
heading in the create/edit bucket modals, the file preview panel
tooltip title, the empty-state copy in the archived / trash views, and
the internal ExpirationPolicySection component — to "lifecycle policy",
matching S3's own terminology.
Preserves verb / action-form uses of expire/expiration where they
describe what happens when the policy fires (ExpirationSentence,
ExpirationExplainer, ExpirationMode, version_expiry_days, "Noncurrent
version expiration" label, etc.) — those name the action, not the
policy.
Create modal: expiration inputs default to 30 days and 10 versions
instead of empty, so toggling versioning on surfaces a policy that's
ready to save. Clearing a field still removes that condition from the
policy.
Edit modal: same defaults, but only when the bucket has never had
versioning enabled — buckets already versioned keep their existing
values (including explicit null == "no limit on this condition"), so
the modal never silently retro-adds a condition the user removed.
- Thinner + fainter skewed stripes on archived rows (1px lines at 5%
alpha with 7px spacing, was 6px lines at 9% alpha with 12px spacing).
- Sort merged archived + live rows together using the explorer's current
sortBy/sortByOrder preference instead of always appending archived
items at the end. Falls back to deletedAt for archived items that
don't have their own created_at/last_accessed_at so they still land
somewhere sensible; live-item nulls-last behavior is preserved.
Adds a "Show archived" option to the view-options dropdown in the file
explorer header (versioned buckets only). When enabled:
- Archived (soft-deleted) files show up in the folder they used to live
in, alongside live objects — coalesced under a synthesized parent
folder when the whole path is gone (an ancestor with no live
descendants at that level). A live item of the same name always wins.
- Archived rows and synthesized folders render with a faint skewed
stripes background + dimmed foreground so they read as
"recoverable-not-active" at a glance, and their row-level actions
dropdown is replaced by an "Archived" tag.
- Clicking an archived row swaps the normal preview slot for the
archived-file preview panel (already used in the standalone Show
archived view), so restore + permanently-delete actions live in one
consistent place. Clicking a live row or opening a folder clears it.
The standalone "Show archived" view is unchanged and remains the
destination for bulk selection and cross-folder browsing.
Backed by getArchivedOverlayItems (with unit tests) which handles the
tolerance for the mock trash data's inconsistent originalPath/name
duplication, and skips synthesizing archived stand-ins when a live
item of the same name is already at that level.
Both the archived files table and the redesigned preview panel now show
the version's own id on every row, dropping the "Was current" badge and
the "(overwrite)"/"(initial upload)" action label.
- Rename "Show deleted versions" toggle to "Show archived" and align copy
throughout this surface to archive/archived/soft-deletion terminology,
consistent with the S3 delete-marker model without exposing that term to
users.
- Fix restore semantics: restoring any row — the top-level group or an
individual version — now fully un-archives the whole file (removes the
delete marker), rather than the previous mock behavior of just filtering
a version out of the list without actually restoring anything.
- Abstract the delete marker away: the version that was live at the moment
a file was archived is folded into the same merged, expandable version
list as its retained noncurrent versions (tagged "Was current"), instead
of being implicit in the top-level row's own fields. Permanently deleting
that specific row promotes the next most recent version in its place via
a new deleteCurrentTrashVersionPermanently mock + mutation hook.
- Fix bulk restore/delete to correctly split a mixed selection of top-level
object ids and nested version keys and route each to the right endpoint
(new DeletedFilesList.utils.ts, with unit tests) — previously a bulk
action on a selected nested version silently did nothing.
- Redesign DeletedFilePreviewPane to match the live file preview panel:
same header/close-button chrome, a version-select swaps the top slot for
a restore widget (mirroring the live compare-and-restore widget, minus
the comparison since there's no live "current" to compare against), and
a collapsible Versions section reusing VersionThumbnail row styling.
Selecting a version swaps the top preview slot for the compare
widget, which is easy to miss if the version list further down is
where the click happened. Scroll the panel's own scroll container
back to the top when a version is selected for comparison.
The compare widget no longer just swaps the default preview slot - it
sticks to the top of the panel's own scroll area while the version
history scrolls underneath it, and the "Added on"/"Last modified"
fields hide while comparing since they aren't relevant to that
decision. Thumbnail height now shrinks with viewport height using the
same clamp() pattern as the default preview.
1. Reduce the gap between the file name and its mime/size meta line in
the header, and drop the redundant "· N versions" suffix (already
shown by the Versions section header below).
2. The expiration-policy info tooltip now has a title ("Expiration
policy") and a link to open the Edit bucket settings modal (via the
shared `edit` query-state key), so there's a path from "what's the
rule" to "where do I change it".
3. Drop the "Kept" label on version rows — it's the default state, so
showing nothing is quieter than repeating it on every row.
4. Under an EITHER policy, a version at the cap boundary now shows its
age countdown alongside "Expires on next upload" (e.g. "+ expires
in 29d"), since that date is still meaningful context even though
the cap would evict it sooner.
5. Renamed the "N kept" chip to "N retained", and unified all policy
chips (day/retained tokens + BOTH/EITHER) to share one visual style
instead of two different treatments.
Implements the "File preview panel — reorganized" handoff:
- Header consolidates to file name/meta + a single kebab (Download, Get
URL, divider, Delete file) plus the close button, separated by a
divider — no more standalone action row further down the panel.
- The panel is one scrolling column; the preview is no longer pinned.
- Every version row gets a small type-glyph thumbnail; the current
version gets a distinct brand-colored restore glyph.
- Selecting a noncurrent version turns the top slot into an inline
compare-and-restore widget (selected → current, with a right arrow),
instead of swapping the big preview or opening a modal. The selected
row is tinted and shows a "Comparing" pill.
- Retention policy collapses to one inline row: a broom-and-sparkles
icon, mono tokens (30d / N kept), a greyscale BOTH/EITHER badge only
when both rules are configured, and an info icon with the full rule.
- Per-row state is now one of exactly four inline labels — Kept /
Expires in Nd / Expires on next upload / Expiring now — derived by
computeVersionFate() per the "never promise a date that isn't certain
yet" rule, replacing the old tooltip-only indicator and #N-of-M badge.
- The soft-delete explainer moves out of the always-visible panel and
into the delete confirmation step, shown only for a single-file
delete on a bucket with versioning history.
Added VersionHistory.utils.ts + tests covering the 7 reference policy
cases from the handoff's operator-notation matrix.
- Preview thumbnail max height dropped from 230px to 180px
(formula: clamp(120px, calc((100vh - 144px) * 0.4), 180px))
- Actions dropdown menu items now use gap-x-2 so the icon and
label have breathing room instead of butting up against each
other
- Per-version expiry indicator collapses into a single broom-
sparkles icon next to the version date; the previous text +
badge combo ("Expires in 5d" + "2/3") is now the tooltip
content, keeping the row compact while preserving all the
policy detail on hover
- Remove the dot column and its wrapping flex so each version
sits flush to the left edge of the panel
- Rewrite the header both/either explainer as a full sentence
so the logic is self-explanatory ("Versions are removed only
when they exceed both..." / "...as soon as they exceed
either...")
- Move the broom-sparkles icon to the right of the 'Expiration
policy' section title in the bucket modal
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
Cap the preview thumbnail at 230px (was 320px) and derive the
target height from what's left of the viewport instead of raw
vh — ~144px of chrome sits above (close button + padding +
filename summary), so a plain vh clamp waited too long to shrink.
Now uses clamp(120px, (100vh - 144px) * 0.4, 230px) so the
preview starts giving room to the sections below noticeably
sooner as viewport height drops.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Move filename and summary details right below the preview
thumbnail, above the sections
- Preview thumbnail is now viewport-height aware via
clamp(120px, 40vh, 320px): stays visible above scrollable
sections on tall viewports, and shrinks to a 120px floor on
short ones so the sections always have room to scroll
- Replace the Details/Versions tabs with vertically-stacked
collapsible sections (PreviewSection) — both default open
- Remove the connecting vertical line between version dots
- Remove the truncated version id from each row (it's cognitive
overhead; the timestamp is the visible handle)
- Remove the action label ('overwrite', 'initial upload') from
each row — no meaningful information gain
- Move per-version actions (Download, Get URL, Restore, Delete)
into a MoreVertical dropdown; delete permanently uses the
destructive text color
- Add a shared inline BroomSparklesIcon (Lucide broom-sparkles
isn't in the pinned lucide-react version) and use it as the
expiration indicator icon in the version list and next to the
'Expiration policy' section title in the bucket modal
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Flip noncurrent version numbering so the oldest (closest to
eviction, at the bottom of the list) is #1 and the newest is
the highest number — matches how the list reads top-to-bottom
- Change the '#N of M' text label to a compact 'N/M' Badge
- Add a brush-cleaning icon in front of the 'Expires in Nd' line
to signal auto-cleanup at a glance
- Remove the 'At cap — ' prefix from the cap warning
- Fix EditBucketModal persistence: emptying a versioning input
and saving now correctly clears the setting (was silently
falling back to the prior stored value); also switch the write
key to bucket.name for consistency with the read path
- Add id={field.name} + inputMode='numeric' on the versioning
inputs so label clicks focus the input and mobile numeric
keyboards surface
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Remove the 'Restoring is non-destructive' admonition from the
bottom of the Versions tab
- Refactor getMockObjectVersions to accept the bucket's current
cap and expiryDays and adapt its output so version-list labels
are always consistent with the policy:
* Never returns a version older than the retention window
(no 'Past Nd limit' surprises when the user set fewer days)
* Never returns more noncurrent versions than the cap
(no '#3 of 2' when the user tightened the cap)
- Anchor all version timestamps to the real clock via
daysAgoFromNow so the mock stays fresh regardless of when the
prototype is opened, instead of drifting away from the fixed
BASE_DATE
- Wire useObjectVersionsQuery to look up the bucket's protection
and pass it into the mock on every fetch, so policy edits from
the bucket modal reflect in the panel next time it opens
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
Show expiration policy context directly in the Versions tab:
- Cap badge and retention badge in the header section
- Combined mode indicator (both/either) when both policies are set
- At-cap and nearing-cap warnings
- Per-version VersionExpiryIndicator showing days remaining and
cap position (#N of M) with warning colors based on combined
and/or policy logic
- Pre-computed noncurrent indices for efficient lookup
- Versioning-suspended admonition banner
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Call trigger() after each field.onChange so zod validation runs
immediately as the user types, surfacing errors via FormMessage
- Simplify the S3 cap error message to 'Cannot exceed 100 versions'
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Add S3_MAX_NONCURRENT_VERSIONS constant (100) and enforce it in
superRefineBucketProtection independently of plan limits
- Remove HTML min attributes from both inputs so only Supabase inline
errors (FormMessage via FormItemLayout) appear — no browser tooltips
- Plan-specific bounds still enforced as before; the S3 cap is checked
first with a distinct error message
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- 'both' mode: show a second example where one condition isn't met,
making it clear the version is kept because both must be exceeded
- 'either' mode: show two examples — one where only the version cap
is exceeded, one where only the age limit is exceeded — to
illustrate that either condition alone triggers deletion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Days only: show a version at days+1 (deleted) vs days-1 (kept)
- Versions only: show versions+1 total, oldest one gets deleted
- Both conditions: show versions+1 at days+1, explain the and/or logic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Change InputGroupText suffix from 'max' to 'versions' on the retained
noncurrent versions input
- Show 'See how this works' explainer when either expiration field has a
value, not only when both are set
- Add context-specific explainer text for days-only, versions-only, and
both-conditions cases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Replace custom raw <input> elements with FormItemLayout + InputGroup +
FormInputGroupInput + InputGroupAddon pattern from the design system
- Use layout='flex-row-reverse' for label-left, input-right alignment
- Both version_expiry_days and max_noncurrent_versions default to empty
(no prefill on toggle)
- Add warning Admonition when no expiration policy is configured to
alert about ongoing storage costs
- Remove unused useFormState import and prefill logic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
Replace the standard form-field layout with the design-handoff UI:
- Sentence builder paragraph that adapts to which fields are filled
(both empty → 'retained indefinitely', one filled → describes that
condition, both filled → sentence with inline toggle)
- Inline [both | either] pill toggle embedded in the sentence text
when both conditions are set (maps to ExpirationMode 'and' / 'or')
- Compact number inputs with suffix labels ('days' / 'max'),
right-aligned text, and em-dash placeholder
- Collapsible 'See how this works' section with a dynamic example
that uses the actual input values and updates on toggle
- Both fields are now optional — empty means 'no limit' for that
condition. Validation only checks min/max when a value is provided.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
Replace bg-border-stronger (oklch with alpha) with solid hsl values:
- Light mode: hsl(0 0% 80%) — neutral mid-gray
- Dark mode: hsl(0 0% 30%) — neutral mid-gray
This prevents the accentuated color at the intersection where
the vertical and horizontal lines overlap.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Lift expandedVersionIds state to DeletedFilesContext (shared between
DeletedFilesList and FileExplorerHeader)
- Move 'Expand all' / 'Collapse all' buttons from the table header into
the FileExplorerHeader top bar with outline button variant
- Change tree-connector line color from bg-foreground-muted to
bg-border-stronger for a subtler appearance
- Increase gap between horizontal tree branch and version title text
(pl-[22px] → pl-[36px], w-[11px] → w-[8px])
- Apply same tree line changes to standalone Trash page (TrashList)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Tree connector lines use bg-foreground-muted instead of bg-border for
better visibility
- Horizontal branch shortened from w-[15px] to w-[11px], creating a 4px
gap between the line end and the version text (text still aligns with
parent name)
- Replaced the icon-only expand/collapse toggle in the table header with
two separate text buttons: 'Expand all' and 'Collapse all', separated
by a dot divider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
Replace CornerDownRight icon with CSS tree-connector lines for nested
version rows. Each version draws:
- A vertical line from the top border to the junction point (center)
- A horizontal branch from the junction to the version text
- For non-last versions, the vertical line continues to the bottom
border, connecting seamlessly with the next row
- For the last version, the vertical line stops at the junction (└ shape)
Lines are positioned at left-[23px] to align with the parent row's
chevron center (16px cell padding + 7px icon center). Version text
starts at pl-[22px] to align with the parent row's name text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Version rows are now selectable via checkboxes for batch multi-selection,
using composite keys (objectId::versionId) in the shared selection state
- Clicking a version row opens the preview panel with version-specific
metadata (version ID, action, created date, size) and a 'no preview
available' placeholder
- Aligned CornerDownRight icon with parent row chevron and version text
with parent name text (both use gap-x-2 and size-14 icons)
- Added expand/collapse all toggle button in the table header top right
(ChevronsUpDown / ChevronsDownUp icons)
- Restore and delete actions on version rows now perform real client-side
effects: version-level mock store mutations + query cache invalidation
- Removed the Expires column from both DeletedFilesList and TrashList
- Extended DeletedFilesContext with selectedDeletedVersion state
- Added version-level mutation functions to protection-mocks.ts
- Added useTrashVersionRestoreMutation and useTrashVersionDeleteMutation
hooks to bucket-trash-query.ts
- Updated Trash page (standalone) with version-level mutation wiring
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- PreviewPane: add Info hint below Delete button when versioning is
active, explaining that deleting soft-deletes the object and all
versions and they can be restored from the deleted versions view
- VersionHistory: add Get URL (Copy) icon button on every version row
alongside existing Download/Restore/Delete actions
- DeletedFilesList & TrashList: implement collapsible nested grouping
for noncurrent versions under parent objects with ChevronRight/Down
expand toggle, version count badge, and per-version Restore/Delete
actions on hover
- protection-mocks: expand mock data from 8 to 12 trash objects with
richer version histories (up to 6 noncurrent versions per object)
to properly showcase the nested deleted versions UI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
Task A: Add ExpirationMode (and/or) toggle between retention-days and
max-versions fields in bucket settings. 'and' = single S3 lifecycle policy
(both conditions must be met), 'or' = two independent rules (either triggers).
Wired into form schema, create/edit bucket modals, and mock store.
Task B: Move 'Show deleted versions' toggle from breadcrumb bar into the
file explorer table header, right of the search input. Switch is on the left
of the label text.
Task C: Add noncurrent version rows beneath each deleted object in TrashList.
Nested rows have left indentation with a CornerDownRight icon and lighter
background. Added DeletedObjectVersion interface and mock data to
protection-mocks.ts. Updated all user-facing terminology from 'files' to
'versions' across Trash, TrashList, TrashSelectionBar, DeletedFilesList,
DeletedFilesHeaderSelection, DeletedFilePreviewPane, and bucket page tabs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
- Reverted the timeline row's Restore action back to its icon-only
text-variant button — the ask to make Restore a default-variant
button was about the "Back to latest" action in the version
preview banner, not the per-row action in the Versions tab
- The version preview banner's action is now labeled Restore, styled
as a default-variant button, and actually restores the previewed
version as current (instead of just clearing the preview)
- Versioning can no longer be turned fully "off" on a bucket that's
ever had it enabled — it moves to a new suspended state instead,
matching how bucket versioning actually behaves. Suspending only
stops new noncurrent versions from being created; nothing already
retained is deleted, so the destructive typed-confirmation flow for
disabling versioning is gone
- Added `hasVersioningHistory` (enabled OR suspended) alongside the
existing `isBucketVersioned` (enabled only) and repointed every
surface that manages or warns about retained version/trash data to
the former, since a suspended bucket can still be sitting on plenty
of it
- Bucket list "Versioning" column, plan-downgrade admonition, and the
Versions tab now all reflect the three states (Enabled / Suspended /
never enabled)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p