c06cbc0 feat(ci): inline extension sign into build.yml + Forgejo Release Assets as XPI cache — The merge-commit race between build.yml and extension.yml (which left :latest without the XPI for ~5min until a side-commit triggered a second build) is eliminated. build.yml now has a sign-extension job that runs BEFORE build-web as a dependency. AMO blocks re-signing the same version with a 409, so signed XPIs are cached as assets on ext-<version> Forgejo releases; future builds hit the cache and skip AMO unless the extension version bumps. Extension version bumped 1.0.1 → 1.0.2 (AMO has 1.0.1 locked from a prior sign). extension.yml shrinks to lint-only.
e6ededb feat(deep-scan): IR-parity port — Deep scan now actually does what users expect (re-apply sidecar metadata, fill NULL phash/artist on existing rows) instead of being a near-no-op chained to backfill_phash. New refreshed status + ImportBatch.refreshed counter (migration 0019). scan_directory(mode=deep) skip-set now excludes only in-flight statuses (completed/skipped paths get re-queued); quick scan keeps the original "any non-failed" semantics. UI banner shows refreshed N alongside imported/skipped; misleading "Scan complete — no new files" toast replaced with honest workload-aware messaging.
52d7905 perf(importer): cache phash candidates on Importer — Archive imports were soft-timeout-failing on large libraries (operator hit SoftTimeLimitExceeded() 2026-05-26) because each archive member re-queried ALL ImageRecord.phash IS NOT NULL rows. Cache loads once per Importer instance, appends on each imported/superseded outcome, invalidates on supersede/deep-rephash. Same one-cache-per-archive-import pattern IR uses.
770bcf3 feat(artist): tab split for reachable DangerZone — ArtistView gets Overview + Settings tabs (sticky at top). The artist cascade-delete UI moves from the bottom of the page (unreachable under the infinite-scroll image grid) into the Settings tab where it's one click from any state.
Test changes
test_importer_deep.py: updated existing test for the new "refreshed" status (was pinned to old "skipped" + error sentinel)
test_scan_deep_requeue.py (new): pins the quick-vs-deep skip-set divergence
Expected first run of new build.yml on main
sign-extension resolves ext version 1.0.2 → no ext-1.0.2 Forgejo release exists yet → cache miss
AMO sign for 1.0.2 succeeds (new version) → uploads signed XPI to a fresh ext-1.0.2 release as asset + uploads to Actions artifact
build-web downloads artifact → copies to frontend/public/extension/fabledcurator-1.0.2.xpi + -latest.xpi → docker build → tags :main + :latest with bundled XPI
Subsequent main pushes that don't bump the extension version hit the cache (cache ext-1.0.2 release already exists with asset) → skip AMO sign → download cached XPI → build with it. No race ever.
## Summary
Four commits on dev:
- **`c06cbc0` feat(ci): inline extension sign into build.yml + Forgejo Release Assets as XPI cache** — The merge-commit race between `build.yml` and `extension.yml` (which left `:latest` without the XPI for ~5min until a side-commit triggered a second build) is eliminated. `build.yml` now has a `sign-extension` job that runs BEFORE `build-web` as a dependency. AMO blocks re-signing the same version with a 409, so signed XPIs are cached as assets on `ext-<version>` Forgejo releases; future builds hit the cache and skip AMO unless the extension version bumps. Extension version bumped 1.0.1 → 1.0.2 (AMO has 1.0.1 locked from a prior sign). `extension.yml` shrinks to lint-only.
- **`e6ededb` feat(deep-scan): IR-parity port** — Deep scan now actually does what users expect (re-apply sidecar metadata, fill NULL phash/artist on existing rows) instead of being a near-no-op chained to `backfill_phash`. New `refreshed` status + `ImportBatch.refreshed` counter (migration 0019). `scan_directory(mode=deep)` skip-set now excludes only in-flight statuses (completed/skipped paths get re-queued); quick scan keeps the original "any non-failed" semantics. UI banner shows `refreshed N` alongside imported/skipped; misleading "Scan complete — no new files" toast replaced with honest workload-aware messaging.
- **`52d7905` perf(importer): cache phash candidates on Importer** — Archive imports were soft-timeout-failing on large libraries (operator hit `SoftTimeLimitExceeded()` 2026-05-26) because each archive member re-queried ALL `ImageRecord.phash IS NOT NULL` rows. Cache loads once per Importer instance, appends on each imported/superseded outcome, invalidates on supersede/deep-rephash. Same one-cache-per-archive-import pattern IR uses.
- **`770bcf3` feat(artist): tab split for reachable DangerZone** — ArtistView gets Overview + Settings tabs (sticky at top). The artist cascade-delete UI moves from the bottom of the page (unreachable under the infinite-scroll image grid) into the Settings tab where it's one click from any state.
## Test changes
- `test_importer_deep.py`: updated existing test for the new `"refreshed"` status (was pinned to old `"skipped" + error sentinel`)
- `test_phash_dedup.py::test_import_task_maps_superseded_to_complete_and_requeues`: added `"refreshed"` mapping assertion
- `test_scan_deep_requeue.py` (new): pins the quick-vs-deep skip-set divergence
## Expected first run of new build.yml on main
1. `sign-extension` resolves ext version 1.0.2 → no `ext-1.0.2` Forgejo release exists yet → cache miss
2. AMO sign for 1.0.2 succeeds (new version) → uploads signed XPI to a fresh `ext-1.0.2` release as asset + uploads to Actions artifact
3. `build-web` downloads artifact → copies to `frontend/public/extension/fabledcurator-1.0.2.xpi` + `-latest.xpi` → docker build → tags `:main` + `:latest` with bundled XPI
Subsequent main pushes that don't bump the extension version hit the cache (cache `ext-1.0.2` release already exists with asset) → skip AMO sign → download cached XPI → build with it. No race ever.
🤖 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.
Summary
Four commits on dev:
c06cbc0feat(ci): inline extension sign into build.yml + Forgejo Release Assets as XPI cache — The merge-commit race betweenbuild.ymlandextension.yml(which left:latestwithout the XPI for ~5min until a side-commit triggered a second build) is eliminated.build.ymlnow has asign-extensionjob that runs BEFOREbuild-webas a dependency. AMO blocks re-signing the same version with a 409, so signed XPIs are cached as assets onext-<version>Forgejo releases; future builds hit the cache and skip AMO unless the extension version bumps. Extension version bumped 1.0.1 → 1.0.2 (AMO has 1.0.1 locked from a prior sign).extension.ymlshrinks to lint-only.e6ededbfeat(deep-scan): IR-parity port — Deep scan now actually does what users expect (re-apply sidecar metadata, fill NULL phash/artist on existing rows) instead of being a near-no-op chained tobackfill_phash. Newrefreshedstatus +ImportBatch.refreshedcounter (migration 0019).scan_directory(mode=deep)skip-set now excludes only in-flight statuses (completed/skipped paths get re-queued); quick scan keeps the original "any non-failed" semantics. UI banner showsrefreshed Nalongside imported/skipped; misleading "Scan complete — no new files" toast replaced with honest workload-aware messaging.52d7905perf(importer): cache phash candidates on Importer — Archive imports were soft-timeout-failing on large libraries (operator hitSoftTimeLimitExceeded()2026-05-26) because each archive member re-queried ALLImageRecord.phash IS NOT NULLrows. Cache loads once per Importer instance, appends on each imported/superseded outcome, invalidates on supersede/deep-rephash. Same one-cache-per-archive-import pattern IR uses.770bcf3feat(artist): tab split for reachable DangerZone — ArtistView gets Overview + Settings tabs (sticky at top). The artist cascade-delete UI moves from the bottom of the page (unreachable under the infinite-scroll image grid) into the Settings tab where it's one click from any state.Test changes
test_importer_deep.py: updated existing test for the new"refreshed"status (was pinned to old"skipped" + error sentinel)test_phash_dedup.py::test_import_task_maps_superseded_to_complete_and_requeues: added"refreshed"mapping assertiontest_scan_deep_requeue.py(new): pins the quick-vs-deep skip-set divergenceExpected first run of new build.yml on main
sign-extensionresolves ext version 1.0.2 → noext-1.0.2Forgejo release exists yet → cache missext-1.0.2release as asset + uploads to Actions artifactbuild-webdownloads artifact → copies tofrontend/public/extension/fabledcurator-1.0.2.xpi+-latest.xpi→ docker build → tags:main+:latestwith bundled XPISubsequent main pushes that don't bump the extension version hit the cache (cache
ext-1.0.2release already exists with asset) → skip AMO sign → download cached XPI → build with it. No race ever.🤖 Generated with Claude Code