Skip to content

feat(security): Settings → Maintenance with selectable SQLite backups manager#277

Merged
graydawnc merged 3 commits into
mainfrom
security/settings-maintenance
May 21, 2026
Merged

feat(security): Settings → Maintenance with selectable SQLite backups manager#277
graydawnc merged 3 commits into
mainfrom
security/settings-maintenance

Conversation

@graydawnc
Copy link
Copy Markdown
Collaborator

What

Adds a Maintenance section to Settings → Security letting users review and selectively delete the SQLite backup snapshots Spool writes before destructive migrations or manual rollbacks. Closes the long-standing "my ~/.spool/backups/ is several GB and I can't tell what's safe to delete" gap, which is a hard prerequisite for the Security Scan GA gate (per the backlog).

Scope is intentionally limited to backups only — Vacuum DB is deferred to its own PR so the (potentially main-thread-blocking) VACUUM concern gets reviewed on its own.

API surface

Function Replaces
listBackups(db) → BackupFileInfo[] n/a (new)
deleteBackups(db, names[]) → { deleted, bytesFreed } cleanBackups({ keep })
backupDirFor(db) unchanged

BackupFileInfo carries name, sizeBytes, mtimeMs, and kind: 'auto' \| 'manual'. The regex listing all spool-pre-*.db is wider than before (the old v\d+ form hid manual rollback snapshots from the UI); deleteBackups independently re-validates the name and rejects path traversal before touching the filesystem.

UI behaviour

  • Multi-select list (max-h-72 scrollable) inside one bordered box. Each row: checkbox · filename (mono, truncate) · auto/manual chip · size · age.
  • List header: tri-state master checkbox (none / partial / all) → click toggles all/none. Shows total stats when nothing selected; swaps to "N selected · X MB" + inline Delete button when selection exists. Row height is fixed (h-8) so the Delete button's appearance never jitters the layout.
  • Two quick-action chips below the list: Select auto (N), Select all but newest.
  • Delete is click-twice in-place — no modal. Idle (amber outline) → confirm (filled amber) → busy (spinner).

Feedback

  • Delete success / failure / partial-success → sonner toasts (matches the share / import / undo pattern elsewhere).
  • Initial-load failure renders inline (toasts auto-dismiss; the list literally has nothing to render so the message has to stick).

i18n

  • 25 new backups_* keys. en is the source of truth.
  • zh-CN and zh-TW are fully translated by the user.
  • ja / ko / de / fr keep the EN copy as stubs pending native review.

Test plan

  • core 315/315 (incl. listBackups ordering + kind tagging, deleteBackups traversal rejection, missing-file tolerance, empty-input no-op)
  • app 229/229 (incl. locale parity + plural-form audit)
  • typecheck clean
  • Manually verified in dev with 16 seeded backups (10 auto + 6 manual):
    • tri-state header (none → partial → all → clear)
    • Select auto / Select all but newest correctness
    • Delete success toast + actual file removal
    • Empty-state copy correct after deleting every backup
    • Initial-load failure path renders inline, not the misleading "no backups"
  • CI re-verifies the same in merge queue

graydawnc and others added 3 commits May 21, 2026 12:57
Strip narrative comments from maintenance.ts and SecurityPane's
MaintenanceRow / formatBytes. Keep WHY notes (BACKUP_FILENAME_RE
scope, VACUUM no-transaction, stat/rm best-effort skips).
Reworks the Settings → Maintenance feature so users explicitly pick
which backup snapshots to delete instead of an opaque keep-N flow.
Removes the Vacuum DB row from this PR — it will land separately so
the (potentially main-thread-blocking) VACUUM concern is reviewed on
its own.

## Core

- Replace `cleanBackups({keep})` with `listBackups()` + `deleteBackups(names[])`.
- Broaden the on-disk match to `^spool-pre-.+\.db$` so manual rollback
  snapshots (e.g. `spool-pre-pr5-revert-…`) are visible to the user;
  the previous `v\d+` regex hid them. Each row is tagged `auto` or
  `manual` so the UI can distinguish at a glance.
- `deleteBackups` rejects path-traversal names + non-matching filenames
  before touching the filesystem.
- Tests cover list ordering, kind tagging, traversal rejection, empty
  inputs, missing-file tolerance.

## UI

- New `BackupsManager` component with a multi-select list (max-h-72
  scrollable) inside a single bordered box.
- List header: tri-state master checkbox (none / partial / all),
  shows total when nothing selected, swaps to selected count + size
  + inline Delete button when selection exists. Fixed-height row
  (`h-8`) so the appearance of the Delete button never causes layout
  jitter.
- Two quick-action chips below the list: `Select auto (N)`,
  `Select all but newest`.
- Delete is click-twice in-place (no modal); the button switches from
  amber outline (idle) to filled amber (confirm) to spinner (busy).

## Feedback

- Delete success / failure / partial-success go through sonner toasts,
  matching the share / import / undo pattern elsewhere in the app.
- Initial-load failure surfaces inline (toasts auto-dismiss, but the
  list literally has nothing to render so the message has to stick).

## i18n

- 25 new `backups_*` keys; zh-CN and zh-TW are fully translated.
- ja / ko / de / fr keep the EN copy as stubs pending native review.
- The `_pendingNativeReview_maintenance` sentinel key is removed —
  it served only the partial-translation state and is now noise.

## Test plan

- [x] core 315/315 vitest
- [x] app 229/229 vitest (incl. locale parity, plural-form audit)
- [x] typecheck clean
- [x] Manually verified in dev with mixed auto+manual seed:
  - tri-state header behaves (none → all → clear)
  - Select auto / Select all but newest correctness
  - Delete success toast + actual file removal
  - Empty-state copy correct after deleting every backup
  - Initial-load failure path renders inline (not the misleading
    "no backups" message)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@graydawnc graydawnc added this pull request to the merge queue May 21, 2026
Merged via the queue into main with commit 651d9db May 21, 2026
3 checks passed
@graydawnc graydawnc deleted the security/settings-maintenance branch May 21, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant