Fix a silent correctness bug: archive purge used `created_at` which is
pinned to the first print, so reprinting a two-year-old archive yesterday
would still make it eligible for a 365-day purge. The preview and purge
queries now age each archive by `COALESCE(completed_at, started_at,
created_at)` — reprints refresh the clock.
Also flesh out both purge modals (File Manager + Archives) with an
explicit "What happens when you click Purge" effects list so users see
upfront that library files go to Trash (reversible) while archives are
hard-deleted (irreversible), plus what disk artefacts get removed.
Backend:
- services/archive_purge.py: `_last_activity_expr()` helper used by
preview, purge, and sample query
- tests/integration/test_archive_purge_api.py: new test covering the
reprinted-archive case
Frontend:
- PurgeOldFilesModal / PurgeArchivesModal: new effects bullet list
- i18n: reprint-aware ageLabel/description/warning and effects bullets
across all 8 locales (en/de fully translated, rest English fallback)
Docs:
- wiki/features/archiving.md: "How old is measured" note + effects list
- wiki/features/file-manager.md: "What happens when you click Purge"
section + explicit age-rule breakdown
- CHANGELOG: archive auto-purge entry rewritten to mention reprint
semantics, `archives:purge` permission backfill, and updated test count
Adds an archive counterpart to the library trash sweeper shipped in the
previous commit. Unlike the library flow, archives are hard-deleted —
print history is a decaying timeline, so there is no trash intermediate;
download or favourite anything you want to keep first.
Backend
- New ArchivePurgeService (backend/app/services/archive_purge.py) with
its own 15-minute scheduler loop and a 24h throttle on actual purge
runs. Delegates every delete to the existing safety-checked
ArchiveService.delete_archive so the 3MF, thumbnail, timelapse, source
3MF, F3D, and photo folder all get cleaned up together with the DB
row. Per-row session via async_session() avoids commit-per-row churn
on any caller-passed session.
- New /archives/purge/{preview,settings} + POST /archives/purge routes
gated on a dedicated archives:purge permission (not archives:delete_all)
so admins can delegate bulk-delete to a role without granting
per-archive delete on other users' rows.
- seed_default_groups() now backfills both library:purge and
archives:purge on the Administrators group for upgraded installs —
the original library:purge was added after Administrators was first
seeded so the "create if not exists" path skipped existing DBs and
left admins without the permission.
- 8 new integration tests (defaults, settings roundtrip, bound
validation, preview, manual purge, auto-purge enabled path, 24h
throttle, disabled skip).
Frontend
- Settings → Archives card gains an auto-purge toggle + age input (7d
floor, 10y ceiling, 365d default), with a save-toast on every change.
The bulk "Purge old" button lives on the Archives page header
(rightmost, after Upload 3MF) to match the File Manager pattern —
configuration in Settings, one-shot action on the page.
- New PurgeArchivesModal mirrors PurgeOldFilesModal: live preview (count
+ total size freed + sample filenames) debounced at 300ms, amber
"hard-delete, no undo" warning.
- Admin-only UI gates on archives:purge via the standard hasPermission
hook; Permission TS union updated.
- i18n blocks across all 8 locales (en/de full, other 6 English
fallback per project convention).
Docs
- CHANGELOG entry under 0.2.4b1 following the existing library-trash
entry.
- bambuddy-wiki archiving.md gains a new "Auto-Purge" section.
- bambuddy-website features.html gets a matching bullet.
Verification: python -m ruff check backend/app/ clean; 25 integration
tests pass (8 archive_purge + 17 library_trash regression); npm run
build clean.