Browse hub, series rename, full-prediction dropdown + a DRY pass (7 sweeps) #90
Reference in New Issue
Block a user
Delete Branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Rolls up dev since the last main merge (#89).
Features / UX
d5d23a9)409bbd4)DRY pass (process #594 hardened + 7 consolidation sweeps)
Process #594 gained the incomplete-consolidation guards (all-or-nothing, consumer enumeration, §8b exhaustiveness, per-consumer testing) and an app-wide visual/behavioral-pattern dimension. Then 7 sweeps, each in the pattern catalog:
<KebabMenu>— 7 hand-rolled kebabs → one modal-safe primitive (folds in a latent #711-class fix). (c774042).fc-muted— token defined in 12 style blocks → one global utility. (4e83b42)<SampleNameGrid>— 5 preview grids → one primitive. (4854d74)<CardHeading>— icon+title heading in 13 cards/dialogs → one primitive. (9deebfa)get_or_create— race-safe find-or-create in 4 async sites → one helper (root-causes the recurring duplicate-row/race bugs). (7b2a205,f1a664e)paginationcursor —encode/decode_cursordefined twice → one shared module. (074c586)tag_query— fandom self-join +{id,name,kind,fandom_id,fandom_name}serialization in 5 spots → one module. (14c244b)The over-DRY guard left genuine variants alone (typed-token confirm modal, chip-led dialog title, SourceService reject-on-conflict, TagDirectory's count-join, the importer's sync find-or-create).
All commits CI-green on their dev head SHA.
🤖 Generated with Claude Code
Posts, Artists, and Tags are the three 'browse the library by an axis' surfaces; Subscriptions stays purely management (operator-asked 2026-06-09). New BrowseView renders them as tabs (?tab=posts|artists|tags); only the active tab mounts. The old standalone paths become redirects into the matching tab, preserving deep-link query (/posts?post_id=N → /browse?tab=posts&post_id=N) and keeping the route names so existing { name: 'posts'|'artists'|'tags' } links and path pushes still resolve. Nav now reads Showcase · Gallery · Browse · Series · Subscriptions, with Settings pinned right. Test: /browse resolves; /tags and /artists redirect into their tabs; a posts deep link survives the redirect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>The fandom self-join (resolve a character's fandom NAME via Tag.fandom_id->Tag) and the {id,name,kind,fandom_id,fandom_name} dict were hand-written in TagService.autocomplete/.list_for_image, GalleryService.get_image_with_tags and the api/tags handlers — the last few grown by this session's fandom-on-chip feature. Consolidate to services/tag_query: fandom_join_alias() + tag_columns() build the select; serialize_tag(row) builds the dict. Now a new tag field is added in one place. Over-DRY guard: TagDirectoryService selects the full Tag ORM + an image-count aggregate (a different select shape) — left as its own variant. §8b: the fandom_lookup alias lives only in tag_query; gallery + both api/tags handlers serialize via serialize_tag. Test: serialize_tag handles enum + string kind. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>