Operator-feedback batch. All CI-green on dev (run 624).
Tags
Acronym-safe casing (df2310b): normalize_tag_name only capitalizes the first letter of each word and preserves the rest — DC/NSFW survive (was lowercasing the tail → Dc/Nsfw). Matches ml/tag_name._title_word, so a Camie-suggested tag keeps the exact casing the suggestion UI showed when it round-trips through POST /api/tags on Accept. Lives only in the create endpoint, not the ML/allowlist path. Trade-off: all-caps input no longer folds to Title Case (protecting acronyms wins).
Patreon resolution
Diagnosable errors (3556a54): the three resolution surfaces (download event, dry-run preview, verify probe) now report source_url, the extracted vanity, and the exact campaigns-API lookup URL.
Resolver hardening (a559fab): when Patreon's /api/campaigns?filter[vanity]= returns empty (its known failure mode), fall back to scraping the campaign id from the creator page's bootstrap JSON — gallery-dl's actual method.
Imports / archives
"No images beside a zip" diagnostics (19eb4e9): when _import_archive captures an archive without extracting any image (probe rejected / corrupt / missing extractor backend / non-media-only), it now records {file, reason} under the event's metadata.unextracted_archives and logs a warning, instead of failing silently.
UI
Post-card thumbnail strip (9374f63): the hero-collage rail spans the full hero width (CSS grid, up to 5 cells + a "+N" overflow tile) instead of capping at 3 left-aligned cells.
Operator-feedback batch. All CI-green on dev (run 624).
## Tags
- **Acronym-safe casing** (`df2310b`): `normalize_tag_name` only capitalizes the first letter of each word and preserves the rest — `DC`/`NSFW` survive (was lowercasing the tail → `Dc`/`Nsfw`). Matches `ml/tag_name._title_word`, so a Camie-suggested tag keeps the exact casing the suggestion UI showed when it round-trips through `POST /api/tags` on Accept. Lives only in the create endpoint, not the ML/allowlist path. Trade-off: all-caps input no longer folds to Title Case (protecting acronyms wins).
## Patreon resolution
- **Diagnosable errors** (`3556a54`): the three resolution surfaces (download event, dry-run preview, verify probe) now report `source_url`, the extracted `vanity`, and the exact campaigns-API lookup URL.
- **Resolver hardening** (`a559fab`): when Patreon's `/api/campaigns?filter[vanity]=` returns empty (its known failure mode), fall back to scraping the campaign id from the creator page's bootstrap JSON — gallery-dl's actual method.
## Imports / archives
- **"No images beside a zip" diagnostics** (`19eb4e9`): when `_import_archive` captures an archive without extracting any image (probe rejected / corrupt / missing extractor backend / non-media-only), it now records `{file, reason}` under the event's `metadata.unextracted_archives` and logs a warning, instead of failing silently.
## UI
- **Post-card thumbnail strip** (`9374f63`): the hero-collage rail spans the full hero width (CSS grid, up to 5 cells + a "+N" overflow tile) instead of capping at 3 left-aligned cells.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The hero collage's thumbnail rail hard-capped at 3 fixed-80px cells, left-
aligned, so it never reached the edge of the (50%-width) hero. Make the rail a
CSS grid of equal columns (1fr) at a fixed height that stretches to the hero's
full width: show up to 5 thumbnails, and when a post has more images than fit,
the last cell becomes the "+N" overflow tile (count unchanged). Column count is
driven by --fc-rail-cols so the strip always reaches the hero edge regardless
of image count.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
normalize_tag_name now only capitalizes the first letter of each word and
leaves the rest of the word untouched (was lowercasing the tail, which turned
DC→Dc / NSFW→Nsfw). This matches ml/tag_name._title_word, so a Camie-suggested
tag keeps the exact casing the suggestion UI showed when it round-trips through
POST /api/tags on Accept — addressing "auto-suggested tags must obey
capitalization" and "don't mangle acronyms" in one rule.
Trade-off (operator-chosen): all-caps input no longer folds to Title Case, so
case-variant merging in #714 still folds the dominant lowercase-vs-Title case
but leaves all-caps stylizations distinct (protecting acronyms wins). Tests
updated + a new test documenting acronym preservation / non-folding.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Naming/lookup failures now report the source_url, the extracted vanity, and the
exact campaigns-API lookup URL attempted, so a "could not resolve campaign id"
error is diagnosable (wrong vanity? cookie/auth? creator renamed?) instead of
opaque. Applied to all three resolution surfaces: the native download event,
the dry-run preview, and the credential-verify probe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Patreon's /api/campaigns?filter[vanity]= lookup returns empty data for creators
that plainly exist (operator-flagged 2026-06-06 — Atole etc. erroring at the
resolve step). gallery-dl never used that endpoint; it pulls the campaign id out
of the creator page's bootstrap JSON. Add the same as a fallback: when the API
misses, GET the creator page (bare + /c/ vanity paths) and scrape the first
campaign id from any known embedding ("id":"…","type":"campaign" /
"campaign":{"data":{"id" / /api/campaigns/<id> / "campaign_id"). API is still
tried first (cheap, structured); the page scrape only runs on a miss.
Tests: API-empty → page-scrape fallback resolves; _scrape_campaign_id pattern
coverage. Existing API-path tests unchanged (happy paths short-circuit before
the fallback; failure paths hit the guarded scrape and still return None).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The recurring "post shows a zip but no images" report had no diagnostic: when
_import_archive captured an archive as a bare PostAttachment — because the
bomb-guard probe rejected it, or extraction yielded zero members (corrupt /
unsupported / missing extractor backend), or it held only non-media files — it
returned status="attached" silently.
Now those paths set ImportResult.error with the specific reason and log a
warning, and download_service records each as {file, reason} under the event's
metadata.unextracted_archives (None when every archive extracted cleanly). So
the next run names exactly which archives failed and why, instead of leaving the
operator to guess. No behaviour change to the happy path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Operator-feedback batch. All CI-green on dev (run 624).
Tags
df2310b):normalize_tag_nameonly capitalizes the first letter of each word and preserves the rest —DC/NSFWsurvive (was lowercasing the tail →Dc/Nsfw). Matchesml/tag_name._title_word, so a Camie-suggested tag keeps the exact casing the suggestion UI showed when it round-trips throughPOST /api/tagson Accept. Lives only in the create endpoint, not the ML/allowlist path. Trade-off: all-caps input no longer folds to Title Case (protecting acronyms wins).Patreon resolution
3556a54): the three resolution surfaces (download event, dry-run preview, verify probe) now reportsource_url, the extractedvanity, and the exact campaigns-API lookup URL.a559fab): when Patreon's/api/campaigns?filter[vanity]=returns empty (its known failure mode), fall back to scraping the campaign id from the creator page's bootstrap JSON — gallery-dl's actual method.Imports / archives
19eb4e9): when_import_archivecaptures an archive without extracting any image (probe rejected / corrupt / missing extractor backend / non-media-only), it now records{file, reason}under the event'smetadata.unextracted_archivesand logs a warning, instead of failing silently.UI
9374f63): the hero-collage rail spans the full hero width (CSS grid, up to 5 cells + a "+N" overflow tile) instead of capping at 3 left-aligned cells.🤖 Generated with Claude Code
Patreon's /api/campaigns?filter[vanity]= lookup returns empty data for creators that plainly exist (operator-flagged 2026-06-06 — Atole etc. erroring at the resolve step). gallery-dl never used that endpoint; it pulls the campaign id out of the creator page's bootstrap JSON. Add the same as a fallback: when the API misses, GET the creator page (bare + /c/ vanity paths) and scrape the first campaign id from any known embedding ("id":"…","type":"campaign" / "campaign":{"data":{"id" / /api/campaigns/<id> / "campaign_id"). API is still tried first (cheap, structured); the page scrape only runs on a miss. Tests: API-empty → page-scrape fallback resolves; _scrape_campaign_id pattern coverage. Existing API-path tests unchanged (happy paths short-circuit before the fallback; failure paths hit the guarded scrape and still return None). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>The recurring "post shows a zip but no images" report had no diagnostic: when _import_archive captured an archive as a bare PostAttachment — because the bomb-guard probe rejected it, or extraction yielded zero members (corrupt / unsupported / missing extractor backend), or it held only non-media files — it returned status="attached" silently. Now those paths set ImportResult.error with the specific reason and log a warning, and download_service records each as {file, reason} under the event's metadata.unextracted_archives (None when every archive extracted cleanly). So the next run names exactly which archives failed and why, instead of leaving the operator to guess. No behaviour change to the happy path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>