fix(tags): fandom views aggregate images via their characters #130

Merged
bvandeusen merged 1 commits from dev into main 2026-06-26 00:28:22 -04:00
Owner

Browse fandom cards and the gallery fandom filter showed only images tagged with the fandom directly — images carrying one of the fandom's characters (the common case) were invisible to it.

Root cause: character.fandom_id models fandom ownership, but every image↔tag query routed purely through direct image_tag rows and never used the FK.

Fix (derive at query time, not propagate): one shared membership predicate in tag_query.pyimage_in_tag_scope / image_in_any_tag_scope (an image belongs to a tag if tagged directly OR, when the tag is a fandom, carrying a character whose fandom_id is that tag; the character leg is empty for non-fandom tags, so it applies uniformly). Routed through:

  • gallery _apply_scope — include, OR-groups, and symmetric exclude
  • directory image_count — correlated COUNT(DISTINCT) scalar subquery
  • directory previews — UNION of direct + via-character, then ROW_NUMBER <= 3
  • cleanup count_tag_associations — Tier-B delete prompt now reports a fandom's true blast radius

character.fandom_id stays the single source of truth (no materialized rows to reconcile on reassign/merge/remove); tagging flow unchanged. Tests added in test_gallery_service / test_tag_directory_service / test_cleanup_service. CI green on dev (run 1509).

🤖 Generated with Claude Code

Browse fandom cards and the gallery fandom filter showed only images tagged with the fandom **directly** — images carrying one of the fandom's characters (the common case) were invisible to it. Root cause: `character.fandom_id` models fandom ownership, but every image↔tag query routed purely through direct `image_tag` rows and never used the FK. Fix (derive at query time, not propagate): one shared membership predicate in `tag_query.py` — `image_in_tag_scope` / `image_in_any_tag_scope` (an image belongs to a tag if tagged directly OR, when the tag is a fandom, carrying a character whose `fandom_id` is that tag; the character leg is empty for non-fandom tags, so it applies uniformly). Routed through: - gallery `_apply_scope` — include, OR-groups, and symmetric exclude - directory `image_count` — correlated `COUNT(DISTINCT)` scalar subquery - directory previews — `UNION` of direct + via-character, then `ROW_NUMBER <= 3` - cleanup `count_tag_associations` — Tier-B delete prompt now reports a fandom's true blast radius `character.fandom_id` stays the single source of truth (no materialized rows to reconcile on reassign/merge/remove); tagging flow unchanged. Tests added in test_gallery_service / test_tag_directory_service / test_cleanup_service. CI green on dev (run 1509). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 1 commit 2026-06-26 00:28:16 -04:00
fix(tags): fandom views aggregate images via their characters
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 18s
CI / backend-lint-and-test (push) Successful in 28s
CI / integration (push) Successful in 3m24s
10434509d3
A fandom owns characters via Tag.fandom_id, but every image<->tag query
went purely through direct image_tag rows, so a fandom only surfaced
images literally tagged with it — images carrying one of its characters
were invisible to its browse count, previews, and gallery filter.

Derive membership at query time instead of materializing fandom rows
(which would drift on every reassign/merge/remove). Add one shared
predicate in tag_query.py — image_in_tag_scope / image_in_any_tag_scope:
an image belongs to a tag if tagged with it directly OR (when the tag is
a fandom) carrying a character whose fandom_id is that tag. The character
leg is empty for non-fandom tags, so it applies uniformly with no kind
branching. Route all read sites through it:

- gallery _apply_scope: include, OR-groups, and symmetric exclude
- directory image_count: correlated COUNT(DISTINCT) scalar subquery
- directory previews: UNION direct + via-character, then ROW_NUMBER<=3
- cleanup count_tag_associations: Tier-B delete prompt now reports a
  fandom's true blast radius (was 0 for fandoms with no direct rows)

find_unused_tags already protected fandoms via used_via_fandom; left as is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bvandeusen merged commit 93034f580d into main 2026-06-26 00:28:22 -04:00
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#130