- 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
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
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
- Move min/max/required validation for version_expiry_days and
max_noncurrent_versions into a shared superRefine (BucketDataProtectionFields.schema.ts),
called from each modal's own superRefine, instead of imperative
validateVersioningFields() + form.setError() calls in onSubmit. Errors
now surface the same way as the rest of the form (FormItemLayout/FormMessage),
matching Studio's established form-error convention.
- Switch the fields to the '' empty-sentinel + z.coerce.number() union
pattern (per the react-hook-form skill) instead of z.string().optional(),
keeping inputs fully controlled without special-casing undefined.
- Build each modal's schema dynamically via useMemo(() => schema(planLimits), ...)
since the versioning bounds depend on the async-loaded org plan.
- Replace destructured useFormContext().watch() with useWatch({ control, name })
in BucketDataProtectionFields, avoiding the ratcheted no-use-watch lint warning.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p