SubscribeStar .art age fix, modal tag-flow sync, system tags / training hygiene (#128) #190

Merged
bvandeusen merged 9 commits from dev into main 2026-07-03 09:16:16 -04:00

9 Commits

Author SHA1 Message Date
bvandeusen 65bd1c22c3 test: whole-table tag counts become non-system counts
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 18s
CI / backend-lint-and-test (push) Successful in 33s
CI / integration (push) Successful in 3m32s
The four remaining run-1895 failures were stale expectations, not
predicate bugs — prune/reset returned the right counts, but these tests
verified no-deletion by counting the ENTIRE tag table (or asserting the
full kind set), which now includes the three seeded hygiene tags that
survive prunes and resets by design. Filter is_system=false with a
pointer to #128 so future system tags cannot re-break them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
2026-07-03 08:40:11 -04:00
bvandeusen f77e75147d feat(tags): system-tag UI markers + full protection sweep (step 4 of #128)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 32s
CI / integration (push) Failing after 3m33s
UI: shield marker + tooltip on TagChip and TagCard; system tags hide
rename/merge/delete affordances (chip kebab entirely — set-fandom never
applies to their general kind; remove stays, un-tagging is normal use).
Aliases stay available: mapping model outputs ONTO a system tag is
useful. Directory cards carry is_system.

Every destructive path that could take out a system row is now guarded,
found by sweeping run 1891s off-by-three failures — each one was a
surface that would have eaten the seeded tags:
- prune-unused: predicate exempts is_system (they ship with zero
  applications and matched every unused condition)
- reset-content: predicate exempts is_system AND keeps their
  applications — hygiene flags describe the file, not content tagging
- admin tag DELETE: refused with system_tag error
- normalize_existing_tags: scan excludes is_system — canonicalization
  would recase wip -> Wip behind TagService.rename's guard, breaking
  the name-keyed presentation lookup

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
2026-07-03 08:34:16 -04:00
bvandeusen 723f023e6a feat(gallery): similar() hides presentation images (banner / editor screenshot)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 33s
CI / integration (push) Failing after 3m34s
Step 3 of milestone #128. Presentation-tagged images cluster on UI
chrome rather than content, so near any one of them they fill the whole
more-like-this grid. Excluded from candidates in the ONE whole-image
similarity surface (gallery similar mode, explore walk, and RelatedStrip
all ride GalleryService.similar) — the anchor itself may be a banner,
and wip stays surfaced: only the training pipelines exclude it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
2026-07-02 23:23:30 -04:00
bvandeusen 19744fa41d fix(tests): resync serial sequences after baseline restore
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 37s
CI / integration (push) Failing after 3m32s
TRUNCATE ... RESTART IDENTITY resets every sequence to 1, and the
baseline restore re-inserts seeded rows WITH their explicit ids —
leaving each sequence pointing below MAX(id). Harmless while the only
baseline rows lived in tables tests never sequence-insert into
(ml_settings id=1); migration 0075 seeded tag rows and every Tag insert
after the first truncate collided on pk_tag id=1 (205 failures, run
1888 — find_or_create then surfaced it as NoResultFound via its
conflict-recovery re-select). setval every restored table with a serial
id column past its restored rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
2026-07-02 23:21:52 -04:00
bvandeusen e6f128c894 feat(ml): training hygiene — system-tagged images are absent from other concepts training
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 34s
CI / integration (push) Failing after 4m28s
Step 2 of milestone #128. _hygiene_excluded_ids (training_data.py) is the
one shared predicate: images carrying any system tag are dropped from
every OTHER concepts head training — not positives (a rough wip tagged
as a character drags the head toward generic-sketch) and not rejection
or sampled negatives (a wip OF character X is not evidence against X).
A system tags own head trains on them unfiltered; that is what makes
auto-flagging banners work. Selection is split out of train_head as the
sklearn-free head_training_ids so CI (no sklearn) can pin the behavior.

CCIP: reference prototypes skip hygiene-tagged images — a faceless wip
figure region must never become an identity reference — and the ref
cache signature now counts hygiene applications, since tagging an image
wip changes the reference set without touching character/region counts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
2026-07-02 23:19:41 -04:00
bvandeusen e9891ee9f3 feat(tags): system tags — is_system column, seeded hygiene tags, protection guards
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 32s
CI / integration (push) Failing after 4m49s
Training hygiene step 1 (milestone #128). Migration 0075 adds
tag.is_system and seeds wip / banner / editor screenshot (kind=general),
ADOPTING an existing same-(name,kind) tag case-insensitively instead of
duplicating. These rows drive the upcoming training exclusions, so they
are protected: rename and merge-away refuse system tags (merge-INTO
stays allowed — folding an operator's old hygiene tag into the system
row is the intended move; merge is the only tag-delete path, so that
guard covers deletion). is_system rides every tag serialization.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
2026-07-02 23:14:49 -04:00
bvandeusen 581b778528 fix(modal): accept-by-known-id keeps the raw suggestion row identity for the drop
CI / lint (push) Successful in 4s
CI / frontend-build (push) Successful in 29s
CI / backend-lint-and-test (push) Successful in 1m6s
CI / integration (push) Successful in 4m31s
Spreading canonical_tag_id onto a raw suggestion changed its _keyOf
identity, so _dropEverywhere missed the actual list row and the panel
kept showing an already-accepted suggestion. Pass the resolved id as an
option instead; pinned with a raw-suggestion spec.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
2026-07-02 22:02:24 -04:00
bvandeusen 6d314d662f feat(modal): applied tags drop from search instantly; manual add == accepting a suggestion
CI / lint (push) Successful in 4s
CI / frontend-build (push) Successful in 40s
CI / backend-lint-and-test (push) Successful in 2m50s
CI / integration (push) Successful in 3m53s
Three tag-flow gaps in the view modal (and the Explore workspace, which
shares TagPanel):
- the type-to-add dropdown now filters both its sections against the
  imageledger applied tags reactively, so a just-added tag disappears
  from search the moment the chip rail updates instead of after a
  modal refresh
- manually picking or creating a tag the model also suggested routes
  through the suggestion-accept flow: the acceptance is recorded for
  head training and the row leaves the panel, instead of the add
  silently bypassing the feedback loop
- removing a tag reloads the suggestion lists, so a model-suggested tag
  returns to the suggestions area (flagged rejected, one-click
  reversible) rather than vanishing until the next modal open

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
2026-07-02 22:00:40 -04:00
bvandeusen b54243a1ff fix(subscribestar): inject the 18+ age cookie on every SubscribeStar domain
CI / lint (push) Successful in 4s
CI / frontend-build (push) Successful in 41s
CI / backend-lint-and-test (push) Successful in 3m1s
CI / integration (push) Successful in 4m51s
The cookie was pinned to .subscribestar.adult only; cookies are
domain-scoped, so sources on subscribestar.art (Elasid, event #54116)
never sent it and every poll 302d to /age_confirmation_warning. Emit
one line per domain (.com/.adult/.art) with a per-domain presence
check, and admit .art in the platform url_pattern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
2026-07-02 21:51:51 -04:00