Release v26.05.26.0 — FC-Cleanup tab + UniqueViolation fix + error modal + extension install fix #22

Merged
bvandeusen merged 0 commits from dev into main 2026-05-26 08:26:46 -04:00
bvandeusen commented 2026-05-26 08:26:22 -04:00 (Migrated from git.fabledsword.com)

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-commitmerge_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

## 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)
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledCurator#22