Soft auto-apply (retract + confirm, no self-training) + tagging UX (reject-rest, tag-input race, modal playlist) #197

Merged
bvandeusen merged 12 commits from dev into main 2026-07-06 21:03:30 -04:00
Owner

Soft auto-apply (milestone #139)

Auto-applied tags become provisional and self-correcting:

  • Retraction sweep (daily): re-scores standing head_auto/ccip_auto tags and drops the ones now below their auto-apply threshold; skips operator-confirmed tags; silent (no hard negative).
  • No self-training: unconfirmed auto-applied tags no longer count as head training positives (_ids_with_tag / _eligible_tag_ids) or seed CCIP references (shared _positive_char_tag filter) — so a misfire can't reinforce itself.
  • Confirm/Keep UI: auto-tag chips show an auto badge + a one-click ✓ Keep. Confirming promotes the tag to a training positive AND shields it from retraction. Applied-tags payload now carries source + confirmed; a confirm trips both change-detectors so the next incremental Retrain folds it in.
  • Defaults: head_auto_apply_min_positives 30→50 and ccip_auto_apply_threshold 0.92→0.95 (migration 0081, bumps the live row only if still at the old default). Graduation precision stays 0.97 (operator: general tuning was fine).

Tagging UX

  • Reject rest — per suggestion-category one-click clear of the remaining (reversible).
  • Tag-input race fixed — autocomplete no longer shows stale wrong-prefix results (inflight-token guard).
  • Modal playlist — prev/next walks the current gallery filter again (generalized the post-scoped cycle into a reusable playlistIds).

One migration (0081). CI green on dev head 18bb25f (ruff, frontend build + unit, backend unit, integration all pass).

🤖 Generated with Claude Code

## Soft auto-apply (milestone #139) Auto-applied tags become provisional and self-correcting: - **Retraction sweep** (daily): re-scores standing head_auto/ccip_auto tags and drops the ones now below their auto-apply threshold; skips operator-confirmed tags; silent (no hard negative). - **No self-training**: unconfirmed auto-applied tags no longer count as head training positives (`_ids_with_tag` / `_eligible_tag_ids`) or seed CCIP references (shared `_positive_char_tag` filter) — so a misfire can't reinforce itself. - **Confirm/Keep UI**: auto-tag chips show an **auto** badge + a one-click ✓ Keep. Confirming promotes the tag to a training positive AND shields it from retraction. Applied-tags payload now carries `source` + `confirmed`; a confirm trips both change-detectors so the next incremental Retrain folds it in. - **Defaults**: `head_auto_apply_min_positives` 30→50 and `ccip_auto_apply_threshold` 0.92→0.95 (migration 0081, bumps the live row only if still at the old default). Graduation precision stays 0.97 (operator: general tuning was fine). ## Tagging UX - **Reject rest** — per suggestion-category one-click clear of the remaining (reversible). - **Tag-input race fixed** — autocomplete no longer shows stale wrong-prefix results (inflight-token guard). - **Modal playlist** — prev/next walks the current gallery filter again (generalized the post-scoped cycle into a reusable `playlistIds`). One migration (0081). CI green on dev head `18bb25f` (ruff, frontend build + unit, backend unit, integration all pass). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 12 commits 2026-07-06 21:03:23 -04:00
feat(ml): stricter auto-apply defaults to cut misfires (milestone 139)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 39s
CI / integration (push) Failing after 3m48s
cbc3e11a53
head_auto_apply_precision 0.97→0.98, head_auto_apply_min_positives 30→50,
ccip_auto_apply_threshold 0.92→0.95 (operator-asked). Model defaults change for
fresh installs; migration 0081 bumps the existing singleton row IFF still at the
old default (won't clobber a deliberate operator change). ml_admin bounds already
permit these. Fixed a stale comment in the auto-apply test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(ml): soft auto-apply — retract auto-tags now below threshold (milestone 139)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 29s
CI / integration (push) Failing after 3m41s
3006e84cc0
Daily scheduled_retract_auto_tags re-scores standing auto-applied tags and drops
the ones the model no longer supports:
- retract_auto_applied_heads: per graduated head, re-score its source='head_auto'
  images (bounded — only the images already carrying the auto-tag, not the whole
  library) and remove ones now < auto_apply_threshold.
- retract_auto_applied_ccip: per source='ccip_auto' character tag, max-cosine the
  image's figure vectors vs that character's prototypes; remove ones now below the
  ccip auto-apply threshold.
Both SKIP operator-confirmed tags (TagPositiveConfirmation) and are SILENT — a low
score isn't proof the tag was wrong, so no hard negative is recorded (that's
reserved for an operator removal). No-op unless the relevant auto-apply switch is
on. New daily beat. sklearn-free tests for both paths + the disabled no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
test(ml): bump auto-apply test head n_pos default 30→60 past the new floor (m139)
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) Successful in 3m42s
0de726ed48
The stricter head_auto_apply_min_positives (30→50, migration 0081) dropped the
_head helper's default n_pos=30 below the support floor, so the "supported head"
sweep tests saw the head as ineligible (n_applied 0). Move the default to 60; the
explicit n_pos=5 under-supported test stays correct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(ml): auto-applied tags don't train a head unless confirmed (milestone 139)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 32s
CI / integration (push) Successful in 3m41s
2d44a26bdf
Makes auto-apply truly "soft" for heads: _ids_with_tag (head positives) and
_eligible_tag_ids (graduation count) now count human-applied + operator-confirmed
tags only, via a shared _AUTO_SOURCES (head_auto/ccip_auto/ml_auto) exclusion.
Unconfirmed auto-applied tags no longer train the head that judges them, so a
misfire can't reinforce itself and the retraction sweep can actually drop it.
Confirming a tag (TagPositiveConfirmation) promotes it to a positive AND protects
it from retraction. sklearn-free tests. CCIP reference exclusion is the companion
piece, next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
Each category section header gets a subtle "Reject rest" action that dismisses
every still-unhandled suggestion in it at once (store.dismissRemaining, parallel
dispatch). Canonical tags persist a rejection and stay flagged (reversible,
one-click un-reject); raw creates-new-tag rows drop client-side. Shows only when
the section has unhandled items. No confirm dialog — it's fully reversible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
revert(ml): keep head auto-apply precision at 0.97 (operator: general tuning was fine)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 31s
CI / integration (push) Successful in 3m42s
7d3a3b4a83
Milestone 139 raised head_auto_apply_precision 0.97→0.98; operator confirmed the
general-tag confidence was already well tuned, so revert that. The support floor
(min_positives 30→50) and CCIP match confidence (0.92→0.95) stay. Migration 0081
(not yet deployed) edited to drop the precision bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
fix(ui): tag autocomplete no longer shows stale wrong-prefix results (race)
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 3m37s
d3984ccb0d
The keystroke debounce cleared the timer but not an already-fired fetch, so a
slower earlier-prefix response ("s") could land after "sex" and overwrite the
dropdown with wrong-prefix matches (operator-flagged with a "sex"→Stockings/
Super Mario screenshot). Gate each autocomplete response on a useInflightToken
(cancel on every keystroke, isCurrent() after the await) so only the latest
query's results are applied.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(ui): confirm/keep auto-applied tags (milestone 139)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Failing after 28s
CI / integration (push) Successful in 3m39s
775941609d
Auto-applied tags are provisional (they don't train the model + can be retracted
until confirmed), so surface and confirm them:
- Backend: list_for_image + get_image_with_tags now include `source` + a
  `confirmed` flag on each applied tag (via serialize_tag, image-scoped; defaulted
  for autocomplete/directory callers).
- Frontend: TagChip badges an unconfirmed auto-tag with an "auto" pill + a
  one-click Keep/confirm (✓) → POST /images/<id>/tags/<id>/confirm, which promotes
  it to a training positive and shields it from the retraction sweep; TagPanel
  reloads so the badge + button drop once confirmed.
Contract test for the source/confirmed payload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(ml): CCIP references exclude unconfirmed auto character tags + confirm trips detectors (m139)
CI / lint (push) Failing after 3s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Failing after 42s
CI / integration (push) Successful in 3m46s
bae077e323
Completes "no self-training": unconfirmed auto-applied character tags no longer
seed CCIP references — character_references + the prototype builder
(_current_fingerprints/_rebuild_one) gain a shared _positive_char_tag filter
(human-applied OR operator-confirmed), mirroring the head-positive exclusion.

Confirming a tag also has to move the change-detectors, or an incremental
refresh/Retrain right after a confirm wouldn't fold the tag in (only the nightly
full pass would): the CCIP global gate now counts character confirmations, and
the head training fingerprint counts confirmations. Test for the CCIP path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(ui): modal prev/next walks the current gallery filter (#1322)
CI / lint (push) Failing after 2s
CI / frontend-build (push) Failing after 17s
CI / backend-lint-and-test (push) Failing after 27s
CI / integration (push) Successful in 3m43s
3bf41ecac3
The image modal cycled GLOBAL neighbours; now the gallery hands it a snapshot of
the currently-filtered, ordered id list so prev/next moves through exactly what
you're viewing — the filtered-playlist behaviour lost in the ImageRepo→FC move.
Generalized the modal store's post-scoped cycle into a `playlistIds` playlist
reused by both GalleryView and PostCard (falls back to global neighbours when no
playlist is passed, e.g. Explore's "open full viewer").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
test(tag): serialize_tag unit tests include the new source/confirmed keys (m139)
CI / lint (push) Failing after 3s
CI / frontend-build (push) Failing after 17s
CI / backend-lint-and-test (push) Successful in 27s
CI / integration (push) Successful in 3m46s
17433c69d4
The confirm-UI change added source + confirmed to serialize_tag; two exact-dict
unit tests in test_tag_query.py failed on the new keys. Add them (default
None/False for rows without image scope).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
fix: ruff C416 (dict() over comprehension) + frontend test playlistIds rename
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 27s
CI / integration (push) Successful in 3m41s
18bb25f140
- heads.py: conf_map = dict(conf) instead of a dict comprehension (ruff C416).
- postCard.spec.js: the modal-playlist rename (postImageIds→playlistIds) missed
  this frontend test (grep was src-only); update the expected call args.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
bvandeusen merged commit a723ef436b into main 2026-07-06 21:03:30 -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#197