DRY + stability pass — extension, ML/settings backend, frontend cards (#161) #232

Merged
bvandeusen merged 7 commits from dev into main 2026-07-13 23:08:18 -04:00
Owner

Scoped DRY + stability pass across the three most-recently-touched surfaces, run under process #594 (over-DRY guard + adversarial verify). Milestone #161. No migrations. Net ≈ −87 backend lines + sizable frontend card reductions.

Divergence bugs fixed (not just cleanup)

  • F-D2 threshold clamp — HeadsCard/CropProposersCard sent unclamped values → API 400. Now structural in <SettingNumberField>.
  • F-D1 TranslationCard section headers rendered unstyled (.fc-section-h never global).
  • B-S1/B-S2 MLSettings + ImportSettings GET hand-listed fields while PATCH was table-driven → new field silently absent from GET. Both now table-driven.
  • B-D1/B-D2 ml.py copied ("face","figure") / _l2 instead of importing the canonical ones.
  • F-D5 opacity-muted anti-pattern; F-D3 the "Video embedding" card moved out of Tagging + renamed VideoEmbeddingCard.

Consolidations

  • Backend ML: _sigmoid / _conflict_scores / _insert_presentation_review / _applied_or_rejected; MLSettings.load/.load_sync; AUTO_APPLY_THRESHOLD_MIN/MAX; _recover_stalled_runs; patreon _campaigns_api_first.
  • Frontend: <SettingToggleRow> + <SettingNumberField> + useSettingSave across HeadsCard / CropProposersCard / MLBackfillCard / VideoEmbeddingCard; CSS tokens .fc-section-h / .fc-good / .fc-weak promoted global (~18 redefs removed).
  • Extension JS: api.webRoot(), exportPlatformCookies(), popup mutedNote() (no version bump — banks for the next ext release).

Left alone (over-DRY guard)

Two Python Patreon regexes (different exclusion sets), the JS↔Py regex mirror (cross-runtime), the three auto-apply sweep bodies (by-design guards), .fc-ok/.fc-bad, TranslationCard (err-alert save) + MLBackfillCard (plain labelled switch).

Verification

All commits CI-green on dev (backend 2274, CSS 2275, extension 2276/2277, frontend 2281). New tests: test_ml_dry_helpers, test_recover_stalled_head_runs. Frontend has no Vue type-check → operator live-reviews the settings cards.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi

Scoped DRY + stability pass across the three most-recently-touched surfaces, run under process #594 (over-DRY guard + adversarial verify). Milestone #161. No migrations. Net ≈ −87 backend lines + sizable frontend card reductions. ## Divergence bugs fixed (not just cleanup) - **F-D2** threshold clamp — HeadsCard/CropProposersCard sent unclamped values → API 400. Now structural in `<SettingNumberField>`. - **F-D1** TranslationCard section headers rendered unstyled (`.fc-section-h` never global). - **B-S1/B-S2** MLSettings + ImportSettings GET hand-listed fields while PATCH was table-driven → new field silently absent from GET. Both now table-driven. - **B-D1/B-D2** ml.py copied `("face","figure")` / `_l2` instead of importing the canonical ones. - **F-D5** opacity-muted anti-pattern; **F-D3** the "Video embedding" card moved out of Tagging + renamed VideoEmbeddingCard. ## Consolidations - Backend ML: `_sigmoid` / `_conflict_scores` / `_insert_presentation_review` / `_applied_or_rejected`; `MLSettings.load/.load_sync`; `AUTO_APPLY_THRESHOLD_MIN/MAX`; `_recover_stalled_runs`; patreon `_campaigns_api_first`. - Frontend: `<SettingToggleRow>` + `<SettingNumberField>` + `useSettingSave` across HeadsCard / CropProposersCard / MLBackfillCard / VideoEmbeddingCard; CSS tokens `.fc-section-h` / `.fc-good` / `.fc-weak` promoted global (~18 redefs removed). - Extension JS: `api.webRoot()`, `exportPlatformCookies()`, popup `mutedNote()` (no version bump — banks for the next ext release). ## Left alone (over-DRY guard) Two Python Patreon regexes (different exclusion sets), the JS↔Py regex mirror (cross-runtime), the three auto-apply sweep bodies (by-design guards), `.fc-ok`/`.fc-bad`, TranslationCard (err-alert save) + MLBackfillCard (plain labelled switch). ## Verification All commits CI-green on dev (backend 2274, CSS 2275, extension 2276/2277, frontend 2281). New tests: `test_ml_dry_helpers`, `test_recover_stalled_head_runs`. Frontend has no Vue type-check → operator live-reviews the settings cards. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
bvandeusen added 7 commits 2026-07-13 23:08:09 -04:00
Consolidate duplication accrued across the ML tagging + settings backend,
behavior-preserving (over-DRY guard applied — the three auto-apply sweep
BODIES stay separate; only their shared inner helpers are extracted).

- _sigmoid / _conflict_scores / _insert_presentation_review (heads.py): the
  score→prob transform (6 inlined sites), the presentation conflict signal
  (2 sites), and the ring-loud PresentationReview insert (2 sites, single-
  sourced so the mode column can't drift on the shared composite PK).
- _applied_or_rejected (training_data.py): the per-tag "applied ∪ rejected"
  skip-set, byte-identical at 3 sweep sites (heads.py x2, tasks/ml.py ccip).
- ccip sweep divergence fixes: import ccip._FIGURE_KINDS + training_data._l2norm
  instead of local copies that silently drift when the canonical changes.
- MLSettings.load / .load_sync classmethods (mirror ImportSettings); route all
  8 scalar_one singleton reads through them (the session.get None-path stays).
- GET serializers for MLSettings + ImportSettings are now table-driven off the
  same _EDITABLE tuples PATCH writes, so a new field can't be silently absent
  from GET (the split that historically dropped fields).
- AUTO_APPLY_THRESHOLD_MIN/MAX constant single-sources the [0.5,0.999] operating
  range across the service clamp + the 5 API validators.
- test_ml_dry_helpers.py pins _applied_or_rejected + _sigmoid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
recover_stalled_head_training_runs and recover_stalled_head_auto_apply_runs
were near-exact copies (coalesce-flip + keep-last-N prune, differing only in
model + two constants). Extract _recover_stalled_runs(model, stall_minutes,
keep_runs, label); the two tasks become thin wrappers. The other two recover
tasks are deliberately NOT folded in (library-audit has no prune tail; backup
uses a single started_at cutoff). test_recover_stalled_head_runs.py covers
both wrappers (stalled→error, fresh survives) — previously untested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
refactor(patreon): DRY the campaigns-API request (#161)
CI / lint (push) Failing after 2s
CI / backend-lint-and-test (push) Successful in 28s
CI / frontend-build (push) Successful in 29s
CI / integration (push) Successful in 4m0s
099e1e664c
_lookup_via_api and resolve_display_name shared ~90% of their body (same
endpoint, params, headers, error handling — differing only in which field
they pluck from data[0]). Extract _campaigns_api_first(vanity, cookies_path)
-> dict|None; callers pluck the campaign id vs the display name. Return-value
behavior preserved (the display-name path additionally gains the helper's more
granular warning logs). Covered by the existing test_patreon_resolver.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
fix(ml): drop unnecessary quotes on MLSettings.load annotations (UP037)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 28s
CI / backend-lint-and-test (push) Successful in 38s
CI / integration (push) Successful in 3m51s
05df51b749
Python 3.14 evaluates annotations lazily, so the self-referential return
type needs no forward-ref quotes — matches ImportSettings.load. Fixes the
ruff lint lane on the DRY-pass push.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
refactor(ui): DRY the settings-card CSS tokens + fix unstyled headers (#161)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 23s
CI / backend-lint-and-test (push) Successful in 35s
CI / integration (push) Successful in 3m57s
a2d1ed935d
- Promote .fc-section-h to a global token (app.css). It was copied identically
  into 4 cards, and TranslationCard used the class with NO local def — so its
  section headers rendered unstyled. Now fixed everywhere.
- Promote .fc-good / .fc-weak status colours to globals; delete the local copies
  in the GPU/heads cards. (.fc-ok stays local — divergent: on-surface in
  HeadsCard vs success in QueuesTable. .fc-bad stays — different name.)
- Delete 10 identical local .fc-muted redefinitions that crept back after the
  2026-06-09 sweep; the global utility already covers them.
- DbMaintenanceCard: opacity:0.6 muted text → the .fc-muted token (the exact
  anti-pattern that token's comment forbids).
- HeadsCard: collapse byte-identical ratePct() into pct().

CSS-only + one template class swap; no logic change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
refactor(extension): DRY the web-root transform + cookie-export flow (#161)
CI / lint (push) Successful in 2s
extension / lint (push) Successful in 11s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 30s
CI / integration (push) Successful in 3m53s
e92570a31e
Behavior-preserving (extension JS has lint-only CI + your manual test):
- api.webRoot() single-sources the baseUrl→web-root transform (strip trailing
  slash + /api) that was copy-pasted in background.js's self-update check and
  OPEN_ARTIST_PAGE, whose comments even cross-referenced each other.
- exportPlatformCookies(key) shares the extract→verify→upload spine between
  EXPORT_COOKIES (single) and EXPORT_ALL_COOKIES; it returns a structured
  outcome so each caller keeps its own response/skip messages verbatim.
- popup.js mutedNote(text) replaces the "centered muted note" div hand-rolled
  in the platform-loading, sources-loading, and empty-sources renderers.

No version bump — no behavior change, so it rides the next real ext release
rather than forcing an AMO re-sign + reinstall.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
refactor(ui): settings-card primitives + fix threshold clamp / card misgroup (#161)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 33s
CI / integration (push) Successful in 3m52s
extension / lint (pull_request) Successful in 10s
ec66ea5f83
Tier-3 frontend DRY for the ML settings cards, plus the F-D2 clamp bug and the
F-D3 card misgrouping.

New primitives (components/common + composables):
- <SettingToggleRow> — the accent-icon + .fc-section-h label + right-aligned
  switch row (HeadsCard x3, CropProposersCard). iconColor prop absorbs the
  on/off dim.
- <SettingNumberField> — compact numeric field that CLAMPS to [min,max] on
  commit. This fixes F-D2: HeadsCard/CropProposersCard previously sent
  Number(raw) straight to the API, so an out-of-range threshold bounced off the
  400 validator (only TranslationCard clamped). density prop for the grid cards.
- useSettingSave(patchFn) — the busy + patch + toast + revert-on-failure flow
  each card hand-rolled (HeadsCard x6 handlers, CropProposersCard, MLBackfillCard,
  VideoEmbeddingCard). Returns ok/false for the optimistic-switch revert.

Adopted in HeadsCard, CropProposersCard, MLBackfillCard (handler only — its
plain labelled switch is a different affordance), VideoEmbeddingCard.

F-D3: MLThresholdSliders.vue actually rendered a "Video embedding" (frame-
sampling) card but sat under "Tagging → Suggestion thresholds". Renamed it
VideoEmbeddingCard.vue and moved it to the "GPU agent & embeddings" section.

Left deliberately (over-DRY guard): TranslationCard uses an inline error ALERT
(not a toast), already clamps its confidence with a NaN fallback, and lives on
the ImportStore — a genuinely different save pattern, so forcing it onto
useSettingSave would change its UX.

Behaviour-preserving refactor; CI has no Vue type-check so this needs a live
UI pass (toggles persist + revert on failure, thresholds clamp on blur, video
card now under Embeddings).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
bvandeusen merged commit ce0dac3524 into main 2026-07-13 23:08:18 -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#232