Artist identity/storage/display decoupling — rename, move-source, add-time name resolution (#130) #193

Merged
bvandeusen merged 4 commits from dev into main 2026-07-04 22:43:12 -04:00
Owner

Merges dev → main. All CI-green on dev head 0963bf0 (run 1931). Migration 0077 (drops uq_artist_name).

Fixes the artist-model fragility: one slug column was doing identity + storage-path + display, both name and slug were UNIQUE. Decoupled into an immutable, unique, readable slug (the on-disk path key — never moves) and a freely-editable, non-unique display name.

Rename (step 1)

Inline pencil-edit on the artist header → PATCH /api/artists/<id>; name only, slug/files untouched. Migration 0077 drops name-uniqueness so two real creators can share a name.

Move source into another artist (step 4)

A "Move…" action per source on the artist Management tab → POST /api/sources/<id>/reassign. Re-attributes the source, its posts, and its images to the target artist (no files move — immutable slug); deletes the old artist if left empty. Consolidates split/mis-named artists.

Identity-by-source (step 2)

quick-add resolves the artist by an existing (platform, url) source first, so a re-add reuses the artist even after it was renamed.

Add-time display-name resolution (steps 3 + 5)

The extension add now names artists by their real display name, not the URL handle: pixiv via the app API (was the numeric id), patreon via the campaigns API, subscribestar via the profile page. Best-effort, falls back to the readable handle on any failure. gallery-dl platforms keep their handle. All curator-core — the extension is unchanged (sends only the URL).

🤖 Generated with Claude Code

https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM

Merges dev → main. All CI-green on dev head `0963bf0` (run 1931). **Migration 0077** (drops uq_artist_name). Fixes the artist-model fragility: one `slug` column was doing identity + storage-path + display, both `name` and `slug` were UNIQUE. Decoupled into an immutable, unique, readable **slug** (the on-disk path key — never moves) and a freely-editable, **non-unique display name**. ### Rename (step 1) Inline pencil-edit on the artist header → `PATCH /api/artists/<id>`; name only, slug/files untouched. Migration 0077 drops name-uniqueness so two real creators can share a name. ### Move source into another artist (step 4) A "Move…" action per source on the artist Management tab → `POST /api/sources/<id>/reassign`. Re-attributes the source, its posts, and its images to the target artist (no files move — immutable slug); deletes the old artist if left empty. Consolidates split/mis-named artists. ### Identity-by-source (step 2) quick-add resolves the artist by an existing (platform, url) source first, so a re-add reuses the artist even after it was renamed. ### Add-time display-name resolution (steps 3 + 5) The extension add now names artists by their real display name, not the URL handle: **pixiv** via the app API (was the numeric id), **patreon** via the campaigns API, **subscribestar** via the profile page. Best-effort, falls back to the readable handle on any failure. gallery-dl platforms keep their handle. All curator-core — the extension is unchanged (sends only the URL). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
bvandeusen added 4 commits 2026-07-04 22:43:06 -04:00
feat(artist): editable display name + rename surface; drop name-uniqueness (#130 step 1)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 21s
CI / backend-lint-and-test (push) Successful in 32s
CI / integration (push) Successful in 3m35s
87d53db0cb
First step of decoupling artist identity/storage/display. migration 0077 drops
uq_artist_name so the display name is free text (two genuinely different creators
can share a name); the slug stays the immutable, unique storage/identity key (the
on-disk path component — untouched, so nothing moves). ArtistService.rename +
PATCH /api/artists/<id> change the name ONLY. Frontend: inline pencil-edit on the
artist header (mirrors TagCard), slug/route unaffected so no navigation. Fixes the
operator's 'no surface to rename an artist' + the name-collision fragility.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(artist): move a source into another artist (#130 step 4)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 28s
CI / integration (push) Successful in 3m36s
a69bd1baa8
Operator ask: a surface to merge new sources into existing artists (consolidate
the singleton artist a fresh add spins up). Enabled by the #130 slug decoupling —
the storage path is immutable, so re-attribution moves NO files. SourceService
.reassign moves the source, re-points its posts (Post.source_id==S) and the
images it contributed (ImageProvenance via S, scoped to the old artist so shared
images aren't stolen), and deletes the old artist if it's left fully empty (else
clears its subscription flag). POST /api/sources/<id>/reassign. Frontend: a
'Move…' action per source on the artist Management tab → artist-autocomplete
picker → confirm → routes to the target (whose slug is stable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(artist): pixiv display-name at add-time + identity-by-source (#130 steps 2+3)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 29s
CI / integration (push) Successful in 3m32s
0c4b8aef8c
Final piece of the artist decoupling. (1) Identity-by-source: quick_add_source
resolves the artist by an existing (platform, url) Source first, so a re-add
reuses the artist even after it was renamed (its frozen slug no longer matches
the name) — a slug-based lookup would have duplicated it. (2) Pixiv naming: a new
pixiv source resolves the real display name via the app API (PixivClient
.resolve_display_name → /v1/user/detail) using the stored token, so the artist is
'Kurotsuchi Machi' not '12345678' — and its name-derived slug matches what a
native download produces, unifying them. Falls back to the numeric id when no
token/crypto. ExtensionService gains the crypto seam; the endpoint passes it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(artist): resolve patreon + subscribestar display names at add-time (#130 step 5)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 32s
CI / integration (push) Successful in 3m41s
0963bf0db3
Parity with pixiv (operator ask): the extension add now resolves the real
display name for our other native platforms too, not just the URL handle.
patreon_resolver.resolve_display_name reads the campaigns API's
attributes.name; SubscribeStarClient.resolve_display_name pulls the creator
name off the profile page (og:title, else the <title> stripped of the
SubscribeStar suffix). extension_service._resolve_artist_name dispatches per
platform (pixiv=token, patreon/subscribestar=cookies via get_cookies_path),
best-effort in an executor, falling back to the readable URL handle on any
failure. Still all curator core — the extension is unchanged (sends only the
URL). gallery-dl platforms keep the handle (readable, no native client).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
bvandeusen merged commit ebac34e17b into main 2026-07-04 22:43:12 -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#193