Concurrent workers processing different files of the same post both did check-then-INSERT on Source, second one tripped uq_source_artist_platform_url → session poisoned → "Session's transaction has been rolled back" cascade. New _find_or_create_source + _find_or_create_post helpers in Importer use SQLAlchemy savepoints (begin_nested()); on IntegrityError, savepoint rolls back (outer transaction preserved) and re-select finds the row the concurrent op committed. Pinned by tests/test_importer_upsert_helpers.py.
Error display now flat-text + copyable (ebd9859)
The prior :title="..." tooltips on SystemActivityTab failures + ImportTaskList errors rendered multi-line SQLAlchemy tracebacks as cramped uncopyable popups. New shared ErrorDetailModal.vue opens on click: <pre> block with wrap + scroll, copy-to-clipboard button. Both surfaces switched from tooltip → click-handler.
Firefox extension install button now works (a06ada4)
Two bugs:
The "Install Firefox extension" button used window.location.assign(url) programmatically. Firefox's XPI-install gesture requires a directly-user-clicked anchor pointing at application/x-xpinstall. Switched to :href on the v-btn (Vuetify renders it as a real <a> element).
Manifest version display read "v latest" because XPI_DIR.glob("fabledcurator-*.xpi") included the -latest.xpi alias and the version-extraction regex returned the literal "latest". Now excludes the alias from version-detection; still serves it as latest_url. After deploy: card shows · Firefox · v1.0.3.
Adjustments from spec/plan worth knowing
DestructiveConfirmModal lives at components/modal/, not components/common/
Confirm token for audit apply changed from apply-audit-<id> → delete-audit-<id> (modal hardcodes action ∈ {restore, delete})
UnusedTagsCard.vue not created — existing TagMaintenanceCard.vue moved into CleanupView; MaintenancePanel drops its reference
None new this release. Migration 0020_library_audit_run already in v26.05.25.6.
Memory banked
reference-forgejo-auto-merge-pins-commit — merge_when_checks_succeed: true pins to PR head SHA at flag-set time; don't push more commits while a PR is queued (v26.05.25.6 release-notes-overclaim postmortem)
reference-image-record-sha256-fixture — sha256 fixtures must use ljust(64,...)[:64]
## What ships
### FC-Cleanup tab (full feature — backend was in v26.05.25.6 as dead code; this completes it)
New top-level **Settings → Cleanup** tab between Import and Maintenance, with four cards:
- **Minimum dimensions** — synchronous SQL preview + Tier-C `delete-min-dim-<sha8>` confirm via `DestructiveConfirmModal`
- **Transparency audit** — async Celery scan against the existing library, 5s status polling, Tier-C `delete-audit-<id>` confirm to delete matched images
- **Single-color audit** — same async pattern, threshold + tolerance inputs
- **Tag maintenance** — moved as-is from Maintenance tab (no duplicate `UnusedTagsCard` — existing component reused)
Backend additions: 9 endpoints under `/api/cleanup/*` (`backend/app/api/cleanup.py`), cleanup_service audit lifecycle (`start_audit_run` / `apply_audit_run` / `cancel_audit_run` with `AuditAlreadyRunning` / `AuditNotReady` / `ConfirmTokenMismatch` exceptions), confirm-token format aligned to modal's `${action}-${kind}-${runId}` convention.
Defaults pre-fill from `ImportSettings` on mount; operator can override per-audit.
### `psycopg.UniqueViolation` worker crash fix (`4da8d1d`)
Concurrent workers processing different files of the same post both did check-then-INSERT on `Source`, second one tripped `uq_source_artist_platform_url` → session poisoned → "Session's transaction has been rolled back" cascade. New `_find_or_create_source` + `_find_or_create_post` helpers in `Importer` use SQLAlchemy savepoints (`begin_nested()`); on `IntegrityError`, savepoint rolls back (outer transaction preserved) and re-select finds the row the concurrent op committed. Pinned by `tests/test_importer_upsert_helpers.py`.
### Error display now flat-text + copyable (`ebd9859`)
The prior `:title="..."` tooltips on SystemActivityTab failures + ImportTaskList errors rendered multi-line SQLAlchemy tracebacks as cramped uncopyable popups. New shared `ErrorDetailModal.vue` opens on click: `<pre>` block with wrap + scroll, copy-to-clipboard button. Both surfaces switched from tooltip → click-handler.
### Firefox extension install button now works (`a06ada4`)
Two bugs:
- The "Install Firefox extension" button used `window.location.assign(url)` programmatically. Firefox's XPI-install gesture requires a directly-user-clicked anchor pointing at `application/x-xpinstall`. Switched to `:href` on the `v-btn` (Vuetify renders it as a real `<a>` element).
- Manifest version display read "v latest" because `XPI_DIR.glob("fabledcurator-*.xpi")` included the `-latest.xpi` alias and the version-extraction regex returned the literal "latest". Now excludes the alias from version-detection; still serves it as `latest_url`. After deploy: card shows `· Firefox · v1.0.3`.
## Adjustments from spec/plan worth knowing
- `DestructiveConfirmModal` lives at `components/modal/`, not `components/common/`
- Confirm token for audit apply changed from `apply-audit-<id>` → `delete-audit-<id>` (modal hardcodes `action ∈ {restore, delete}`)
- `UnusedTagsCard.vue` not created — existing `TagMaintenanceCard.vue` moved into CleanupView; MaintenancePanel drops its reference
- `/api/cleanup/unused-tags/*` endpoints skipped — reused existing `/api/admin/tags/prune-unused`
## Schema changes
None new this release. Migration `0020_library_audit_run` already in v26.05.25.6.
## Memory banked
- `reference-forgejo-auto-merge-pins-commit` — `merge_when_checks_succeed: true` pins to PR head SHA at flag-set time; don't push more commits while a PR is queued (v26.05.25.6 release-notes-overclaim postmortem)
- `reference-image-record-sha256-fixture` — sha256 fixtures must use `ljust(64,...)[:64]`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What ships
FC-Cleanup tab (full feature — backend was in v26.05.25.6 as dead code; this completes it)
New top-level Settings → Cleanup tab between Import and Maintenance, with four cards:
delete-min-dim-<sha8>confirm viaDestructiveConfirmModaldelete-audit-<id>confirm to delete matched imagesUnusedTagsCard— existing component reused)Backend additions: 9 endpoints under
/api/cleanup/*(backend/app/api/cleanup.py), cleanup_service audit lifecycle (start_audit_run/apply_audit_run/cancel_audit_runwithAuditAlreadyRunning/AuditNotReady/ConfirmTokenMismatchexceptions), confirm-token format aligned to modal's${action}-${kind}-${runId}convention.Defaults pre-fill from
ImportSettingson mount; operator can override per-audit.psycopg.UniqueViolationworker crash fix (4da8d1d)Concurrent workers processing different files of the same post both did check-then-INSERT on
Source, second one trippeduq_source_artist_platform_url→ session poisoned → "Session's transaction has been rolled back" cascade. New_find_or_create_source+_find_or_create_posthelpers inImporteruse SQLAlchemy savepoints (begin_nested()); onIntegrityError, savepoint rolls back (outer transaction preserved) and re-select finds the row the concurrent op committed. Pinned bytests/test_importer_upsert_helpers.py.Error display now flat-text + copyable (
ebd9859)The prior
:title="..."tooltips on SystemActivityTab failures + ImportTaskList errors rendered multi-line SQLAlchemy tracebacks as cramped uncopyable popups. New sharedErrorDetailModal.vueopens on click:<pre>block with wrap + scroll, copy-to-clipboard button. Both surfaces switched from tooltip → click-handler.Firefox extension install button now works (
a06ada4)Two bugs:
window.location.assign(url)programmatically. Firefox's XPI-install gesture requires a directly-user-clicked anchor pointing atapplication/x-xpinstall. Switched to:hrefon thev-btn(Vuetify renders it as a real<a>element).XPI_DIR.glob("fabledcurator-*.xpi")included the-latest.xpialias and the version-extraction regex returned the literal "latest". Now excludes the alias from version-detection; still serves it aslatest_url. After deploy: card shows· Firefox · v1.0.3.Adjustments from spec/plan worth knowing
DestructiveConfirmModallives atcomponents/modal/, notcomponents/common/apply-audit-<id>→delete-audit-<id>(modal hardcodesaction ∈ {restore, delete})UnusedTagsCard.vuenot created — existingTagMaintenanceCard.vuemoved into CleanupView; MaintenancePanel drops its reference/api/cleanup/unused-tags/*endpoints skipped — reused existing/api/admin/tags/prune-unusedSchema changes
None new this release. Migration
0020_library_audit_runalready in v26.05.25.6.Memory banked
reference-forgejo-auto-merge-pins-commit—merge_when_checks_succeed: truepins to PR head SHA at flag-set time; don't push more commits while a PR is queued (v26.05.25.6 release-notes-overclaim postmortem)reference-image-record-sha256-fixture— sha256 fixtures must useljust(64,...)[:64]🤖 Generated with Claude Code