SubscribeStar gallery-dl always writes `title: ""` and embeds the leading
sentence inside `content` HTML. Confirmed against the operator's
/mnt/Data/Patreon/Cheunart/subscribestar/ dump 2026-05-27: every
post's JSON has `title: ""` and a content like
`<div>Lets say hello to you guys with my Belle <br><br><br></div>`.
FC's sidecar parser, treating empty strings as missing, had been leaving
post_title NULL on every subscribestar post since FC-3 shipped.
Fix at two layers:
1. `backend/app/utils/sidecar.py` — new `_first_line_text(body, limit)`
helper strips HTML tags, collapses whitespace, returns the first
non-empty line truncated to 120 chars with ellipsis. `parse_sidecar`
now falls back to this when `title` resolves to None and a
`content`/`description`/`caption` value is present. Patreon's
non-empty titles short-circuit the fallback so existing behavior is
unchanged. Four new tests in test_sidecar_util.py pin: derivation
from content, truncation at 120 chars, explicit-title precedence,
no-content no-fallback.
2. `alembic 0024_backfill_post_title_from_description` — backfills the
same logic across existing Post rows where `post_title IS NULL OR
post_title = ''` AND description is present. Idempotent (re-running
is a no-op once titles are populated). Downgrade is a no-op since
there's no safe way to tell derived rows from genuine ones.
After deploy + migration: subscribestar posts will surface a meaningful
title in PostCard, post feed search, etc.
The two prefix-parsing tests were pinned to `artist:Eric`, but `artist`
was removed from KNOWN_KINDS in commit 4cad07a (provenance is a separate
axis from tags). The parser now keeps `artist:` literal, so the assertion
`body["name"] == "Eric"` failed.
Repointed to `character:Saber` (still in KNOWN_KINDS). Also updated the
stale `artist:` docstring example in parse_kind_prefix to `fandom:`.
Caught by [[reference-grep-pinned-tests-in-plans]] — should have grep'd
tests/ for `artist:` when shrinking KNOWN_KINDS. Banking the miss.
slugify() produces ASCII-only lowercase slugs from arbitrary text (used for
artist slugs and tag slugs). paths.derive_subdir/derive_top_level_artist
extract the destination layout and folder→artist convention from an import
source path. hash_suffixed_name builds IR-style 'stem__hash10.ext' filenames.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>