-
released this
2026-05-25 19:37:09 -04:00 | 644 commits to dev since this releaseWhat's new
ML worker no longer crashes on SentencePiece import
embedder.pynow usesSiglipImageProcessor.from_pretrained()(image-only) instead ofAutoProcessor.from_pretrained()— the full processor pulled inSiglipTokenizerwhich requiredsentencepieceat import time and killed the ml-worker before any tag_and_embed could run. FC's embedder is image-only by design.
Import-tab UX overhaul
- Deep scan button alongside Quick scan — backend already accepted
mode: 'deep'(chains abackfill_phashafter the walk); now exposed in the UI. /api/import/statusnow returnssource_path+scan_mode— fixes the "Scanning undefined —" banner.- Settings tabs strip is now
position: sticky; top: 64px— no more scrolling to the top of the page to switch tabs. - Import tab reorders to Trigger → Tasks → Filters so hit/miss feedback sits directly under the buttons.
- Toast wording adapts per mode; task list re-fetches 2s after trigger.
Browser-extension publish workflow corrected
- Extension bumped to 1.0.1 to trigger initial sign-and-publish.
- Workflow's commit step previously globbed
fabledcurator-*.xpibut AMO renames signed XPIs to use its addon-id-safe-string (e.g.997017ca3e104e30a75a-1.0.1.xpi) — now globs*.xpiand canonicalizes the copy tofabledcurator-<version>.xpiso the backend's whitelist keeps working. - Workflow now self-retriggers on edits to its own file (path filter includes
.forgejo/workflows/extension.yml) — no more manual dispatch on workflow fixes.
Known follow-ups (next release)
- The signed XPI side-commit didn't actually land on main in this cycle despite the workflow showing success on run #309 — investigating; expect a v26.05.25.4 with the bundled extension once root-caused.
- CI speedup via pre-baked
requirements.txtinci-python:3.14image + job-shard of the integration suite across the 6-capacity runner (operator just expanded act_runner capacity 2→6).
Verification post-deploy
- Restart ml-worker → fire any tag_and_embed — should no longer ImportError on sentencepiece.
- Settings → Import: Deep scan button present, tabs stay pinned to top of viewport while scrolling Maintenance panel.
🤖 Generated with Claude Code
Downloads
-
released this
2026-05-25 14:30:50 -04:00 | 855 commits to main since this releasePatch release on top of v26.05.25.1. See PR #12 for the change list.
Operator-facing win
Scanning the GS download dir now enriches superseded images with the GS sidecar's post metadata. Previously
_supersede()was file-only — bigger version of an IR-migrated image would replace the file but lose the gallery-dl JSON sidecar's Post/Source/ImageProvenance entirely. The behavior is additive: any existing IR-migration provenance survives, and the new GS-sidecar provenance gets added as a secondImageProvenancerow that the modal surfaces.UI
- Scan trigger button now produces visible feedback (immediate "Scan triggered" toast + 2s status refresh + "Scan complete — no new files" toast when the scan quick-finalizes against an already-imported library).
CI
- pip wheel cache +
uv pip install --systemwhen available — ~2 min saved on warm runs. pytest --durations=25on integration job to expose slowest tests for future targeted optimization.
Schema changes
None.
Next step for the operator
Now that this is on main + deployed, run a deep scan (Settings → Import → Quick scan with deep mode, or
curl -X POST /api/import/trigger -d '{"mode":"deep"}'). For each GS file at/import/...:- Same SHA256 as an existing FC row →
_deep_rederivebackfills phash + sidecar - Phash matches a smaller existing IR-migrated image →
_supersedeswaps file AND applies sidecar - Brand new → fresh import with sidecar
🤖 Generated with Claude Code
Downloads
-
released this
2026-05-25 13:04:39 -04:00 | 860 commits to main since this releasePatch release on top of v26.05.25.0. See PR #11 for the full change list.
Operator-facing wins
- Gallery now sorts by original post date when known — IR-migrated content surfaces at its actual publish year instead of piling up in one FC-scan-week bucket. Re-run
/api/migrate/tag_applyto backfillprimary_post_idon existing migrated images. - DeviantArt + Pixiv PostMetadata is no longer silently dropped during
tag_applyphase 4 — one of two root causes of the missing-provenance complaint. - Corrupt files in
/importno longer Celery-autoretry-loop forever — PILOSErrornow skips asinvalid_imagecleanly. - Stuck batches are recoverable from the UI: new "Clear stuck..." button on the import task list, plus an inline action next to the scanning spinner. No more SQL needed.
- Quick scan button is always visible (was hidden by a wedged active batch in the old UI).
- Camie v2 ML tagger actually works —
download_models.pywas fetching the wrong filenames;tagger.pyhad wrong preprocessing + wrong output handling. Aligned with the publishedonnx_inference.pyreference. - Directory card UI: 2× width, bounded preview slot heights so tall source images can't escape.
Maintenance / recovery
recover_interrupted_tasksBeat sweep now flipspending/queuedorphans (>30 min) tofailedin addition to recyclingprocessingrows. Fixes the 5,490-row pile ascan_directorycrash can leave behind./api/import/clear-stuckmanual escape hatch for the autoretry-loop case.
Schema changes
None. All fixes are logic-only.
Recovery steps
After deploying:
- Re-run
/api/migrate/tag_applyon the existing manifest — phase 4 is idempotent; backfillsprimary_post_idand picks up previously-skipped DeviantArt + Pixiv entries. - Gallery + timeline + jump immediately reflect publish dates.
🤖 Generated with Claude Code
Downloads
- Gallery now sorts by original post date when known — IR-migrated content surfaces at its actual publish year instead of piling up in one FC-scan-week bucket. Re-run
-
released this
2026-05-25 01:47:52 -04:00 | 870 commits to main since this releaseTwo roadmap stages cut together. See PR #10 for the full change list.
FC-3h — Backup as first-class service
Backup promoted out of the migration tooling into a permanent FC feature, split into independent DB + image components.
BackupRunmodel + alembic 0017/0018backend/app/services/backup_service.py(pg_dump / tar+zstd / restore / unlink)- 6 Celery tasks on
maintenancequeue (autoretry, per-kind soft/hard time limits) - Beat: hourly nightly tick (settings-gated, default OFF) + daily retention prune
/api/system/backup/*blueprint with typed-token confirmation on restore + delete- New Settings → Maintenance card with DB + Images sub-sections
FC-3k — Admin destructive actions
Five first-class destructive actions, surviving FC-3j cleanup of the migration tooling.
cleanup_service.py(artist cascade + image unlink + tag delete + prune-unused, lifted frommigrators/cleanup.py)- 2 Celery tasks for long-running ops (delete-artist-cascade, bulk-delete-images)
/api/admin/*blueprint (6 endpoints) with graduated confirmation friction (Tier A single-click / Tier B yes/no with count / Tier C typed-token)ArtistDangerZone.vue,BulkEditorPanel"Delete selected", per-tag dots-menu (Merge + Delete),TagMaintenanceCard.vue(prune-unused preview)BackupConfirmModalgeneralized toDestructiveConfirmModaland shared between FC-3h + FC-3k
Inheritance
Both stages inherit FC-3i's
task_runlifecycle (recovery sweep, daily prune, soft/hard time limits, duration tracking) for the long-running Celery tasks. No new lifecycle plumbing.Schema
- alembic 0017 —
backup_runtable - alembic 0018 —
backup_*knobs onimport_settings
Run
alembic upgrade headafter pulling.What's not in this release
- IR migration verification (operator-driven manual check before FC-3j)
- FC-3j (remove migration tooling) — depends on verification
- FC-3g extension verification
🤖 Generated with Claude Code
Downloads
-
released this
2026-05-24 21:48:19 -04:00 | 707 commits to dev since this releaseFC-3i ships: IR-style operator visibility into every Celery lane, plus two dogfood fixes that unblock the IR migration UI.
FC-3i — System Activity Overview
- New
task_runtable populated automatically by Celery signal handlers; no per-task instrumentation. - Recovery sweep + retention sweep on Beat (5 min / daily).
/api/system/activity/{queues,workers,runs,failures}endpoints.- Settings → Activity tab with queues+workers pane, recent failures pane (24h, clickable error-type pills), full activity table with queue/status filters.
- Quick summary card on Settings → Overview.
Migration UX unblock
- Backup-gate retired on the migration API; IR/GS ingest from the UI now works without a recent backup. FC-3h will rewrite backup as a first-class feature.
Modal Escape
- Document-level keydown listener so Escape closes the viewer modal even when a
<video>element has focus.
Operator workflow
- Pull the new image; force-update
app,worker,ml-worker,scheduler,web. - Alembic auto-applies migration 0016 (creates
task_run). - Within 5 min,
task_runrows accumulate for every task that fires. - Settings → Activity tab live-tracks queue depths, worker counts, recent failures across all lanes.
Downloads
- New
-
released this
2026-05-24 14:30:26 -04:00 | 724 commits to dev since this releaseExtends the IR migration so post-level data (title, description, source URL, publish date, attachment count) survives IR shutdown, and adds a modal-side artist fallback so IR-imported images without ImageProvenance still show a "by " line.
IR Post/Provenance restore
- IR export script bumped to schema v2; dumps PostMetadata as
image_postswith per-post sha256 lists. ir_ingest.pyaccepts schema 1 or 2; carriesimage_postsinto the manifest.tag_apply.pynew phase 4: find-or-createsSource+Post+ImageProvenanceper IR PostMetadata entry. Idempotent, dry-run safe.
Modal artist fallback
/api/gallery/image/<id>now returnsartist: {id, name, slug}whenimage_record.artist_idis set.ProvenancePanel.vueshows a "by " line when ImageProvenance is empty but a direct artist FK exists.
Operator workflow
- Pull this image; force-update services.
- Deploy a one-shot
ir-exportservice inside the IR stack (see chat for snippet) → runsscripts/export_for_fabledcurator.py --output /staging/ir-export-v2.json. - Settings → Maintenance → Legacy migration → upload the new JSON to the IR slot.
- Click "3. Ingest IR" (works now thanks to the FormData fix in v26.05.24.1).
- Click "5. Apply IR tags" — phase 4 fires alongside phases 1-3.
- Open any image modal: ImageProvenance entries render for images-in-posts; artist fallback line renders for everything else.
Downloads
- IR export script bumped to schema v2; dumps PostMetadata as
-
released this
2026-05-24 12:52:52 -04:00 | 727 commits to dev since this releaseBig release rolling up FC-3g (Firefox extension), worker-level recovery hardening, and several dogfood fixes surfaced during the IR migration.
Headline — FC-3g: Firefox extension
Plain-JS MV3 Firefox extension under
extension/. Six platforms (Patreon, SubscribeStar, Hentai-Foundry, Discord, Pixiv, DeviantArt). Popup with per-platform Export + Export All + Sources tab. Floating "Add to FabledCurator" button on artist pages. New backend/api/extension/quick-add-source+/api/extension/manifest+/extension/<filename>(serves signed XPI withapplication/x-xpinstallMIME). NewBrowserExtensionCard.vueon Settings → Maintenance. CI workflow signs via Mozilla AMO (unlisted) on main pushes + commits XPI back so next build bundles it into the Docker image.Worker resilience
- Recovery sweep tightened 30min → 5min — flips stuck
processingrows automatically. import_media_filebody-wrapped so no exit leaves rows inprocessing.- Celery
autoretry_for(OperationalError, DBAPIError, OSError) + exponential backoff onimport_media_file,generate_thumbnail,tag_and_embed,download_source. - Per-task
soft_time_limit+time_limitsafety nets.
Migration UX
- FormData passthrough in
useApi— IR/GS export-file UI uploads now actually work. - Backup subprocess 30-min wall-clock timeout (FC-3h queued for the full promotion).
UI
- Global
.fc-contentpadding-top so view content doesn't start hidden behind the sticky TopNav.
CI / lint
- extension.yml YAML schema fix.
- ASYNC240 —
/api/extension/manifestfilesystem work moved toasyncio.to_thread.
Test hygiene
test_recover_interrupted_only_oldunpinned from the threshold value.
Operator workflow
- After pulling: force-update
app,worker,ml-worker,scheduler,web. - 2224 stuck-processing rows from the IR migration will recover within 5 min of
schedulercoming up. - First signed XPI lands ~5-10 min after this release (Mozilla signing + bot commit triggers a second build that bundles the XPI). After that, the Browser Extension card on Settings → Maintenance goes from "No bundled extension found" to the "Install Firefox extension" CTA.
Downloads
- Recovery sweep tightened 30min → 5min — flips stuck
-
v26.05.24.0 — TopNav re-fix Stable
released this
2026-05-23 22:49:44 -04:00 | 747 commits to dev since this releaseSingle-commit follow-up to v26.05.23.3's TopNav layout attempt.
Fix
The
1fr auto 1frgrid layout shipped in v26.05.23.3 wasn't actually symmetric. CSS Grid treats1frasminmax(auto, 1fr), so a side cell whose intrinsic content (brand on the left, action button on the right) exceeded its 1fr share quietly grew past it. On narrower viewports the centered link block still shifted when Gallery or Showcase rendered their teleport-slot action buttons.The replacement is a flex layout with explicit equal-weight side cells (
flex: 1 1 0on both.fc-nav-leftand.fc-nav-actions), keeping the middle anchored regardless of side content widths.Operator notes
- Pull and force-update
web(frontend) — the only change is infrontend/src/components/TopNav.vue. - Hard-refresh the browser (Ctrl+Shift+R) to pick up the new content-hashed CSS bundle.
Downloads
- Pull and force-update
-
released this
2026-05-23 22:06:22 -04:00 | 749 commits to dev since this releasePatch release rolling up five dogfood-driven fixes from the live IR migration.
UI fixes
- Poll task list during scan: Recent Import Tasks list now refreshes every 5 s while a batch is in flight, so the table tracks the counter that's already polling.
- TopNav grid layout: switching to Gallery or Showcase no longer shifts the centered links — the nav now uses a
1fr / auto / 1frgrid with mirrored side columns so contextual action buttons (Select / Shuffle) don't rebalance the layout. - Viewer modal haze + fit-to-view: the modal background is now mid-opacity obsidian +
backdrop-filter: blur(8px)(matches TopNav haze) and large images fit the viewport at zoom 1× instead of overflowing. - Viewer modal close-on-click-outside: IR-parity. Click on the image still toggles zoom; click on the surrounding haze closes the modal.
Backend fix
- Auto-finalize empty scans + accurate active-batch stats: a re-triggered scan that finds every file already on a non-failed
ImportTaskno longer leaves the batch row stuck inrunningforever.scan_directoryflips the empty batch tocompleteimmediately;/api/system/statsfilters its active-batch query to running batches that still have outstandingImportTaskwork, so the UI tracks the batch actually doing the import instead of the most recently-triggered empty one.
Operator notes
- If you have a previous empty batch stuck in
running(counters all zero), one-shot SQL fix:UPDATE import_batch SET status='complete', finished_at=NOW() WHERE total_files=0 AND status='running'; - Force-update
appto pick up the/api/system/statsquery change. - Force-update
web(frontend) for the four UI fixes. - Force-update
schedulerto pick up thescan_directoryauto-finalize behavior.
Downloads
-
released this
2026-05-23 12:19:31 -04:00 | 755 commits to dev since this releasePatch release rolling up two post-migration fixes.
Fixes
- Serve
/images/<path>from disk: without this route, the SPA catch-all swallowed every thumbnail/original URL and returnedindex.html, causing the aspect-ratio-shaped grey placeholders in Showcase and Gallery. Added a guardedsend_from_directoryroute registered before the SPA fallback.
Features
/api/migrate/cleanupmigrator: targeted destructive cleanup — deletes everyimage_recordattributed to one artist (CASCADE handlesimage_tag,image_provenance,series_page,tag_suggestion_rejection), unlinks originals + thumbnails on disk, optionally sweepsimport_taskrows under a givensource_path_prefix, removes emptyimport_batchrows, and finally deletes the artist row. Built for the IR-migration rescue case where the scan derived a bogusimagerepoartist from a mismatched bind-mount layout. Honorsdry_runfor a safe preview; requiresforce: trueto bypass the pre-migration backup guard.
Operator workflow for the IR re-do
After pulling this image and force-updating services:
# 1. Dry-run to verify scope (no writes) curl -sX POST http://curator.traefik.internal/api/migrate/cleanup \ -H 'Content-Type: application/json' \ -d '{"slug":"imagerepo","dry_run":true,"force":true}' # 2. Actually wipe the IR import curl -sX POST http://curator.traefik.internal/api/migrate/cleanup \ -H 'Content-Type: application/json' \ -d '{"slug":"imagerepo","force":true,"source_path_prefix":"/import/imagerepo"}' # 3. Poll until complete curl -s http://curator.traefik.internal/api/migrate/runs/<id> | jqThen change the IR bind mount in
docker-compose.ymlfrom/var/lib/imagerepo/images:/import/imagerepo:ro→/var/lib/imagerepo/images:/import:roso the scan sees<artist>/<file>as the top level. Re-trigger scan, then re-runtag_apply(the manifest at/images/_migration_state/ir_tag_manifest.jsonsurvives the cleanup).Downloads
- Serve