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
- 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
Frontend for the system-tag refactor (milestone #157 step 6).
- HeadsCard: new 'Auto-tag work-in-progress' section (enable + tag-confidence +
conflict knobs) for wip/editor process auto-apply, mirroring the chrome card;
copy notes they stay VISIBLE and the head only learns from titles/manual (no
runaway). Presentation copy narrowed to banner-only.
- HiddenReviewStrip: mode-aware — chrome flags read 'hidden as X / Keep hidden /
Un-hide'; process flags read 'auto-tagged X / Keep tag / Remove tag'. Same
endpoints (the backend returns mode), different words.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HeadsCard gains a "Hide presentation chrome" section: on/off switch + "Hide
confidence" (presentation_auto_apply_threshold) + "Flag if content ≥"
(presentation_conflict_threshold), wired to MLSettings via patchSettings and
loaded on mount. Makes the step-4 sweep's thresholds operator-tunable
(config-in-UI). wip is called out as never auto-hidden.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
Settings IA per the approved A3 design (the old layout was the two-app merge
fossilized):
- Import tab retired: ImportTriggerPanel + ImportTaskList deleted (manual
/import scans stay API-level; imports arrive via downloads/extension, heal
via the Layer-2 auto-refetch sweep, and show in Activity). ImportFiltersForm
moves to Maintenance → 'Ingestion & filters' and loads its own settings; the
import store shrinks to settings-only (no remaining consumers of the
scan/task-list machinery). Overview's pending banner now points at Activity.
- Maintenance regrouped: Ingestion & filters / GPU agent & embeddings
(GpuAgent, Failed processing, CPU embedding backfill) / Tagging (sliders,
Heads, Aliases) / Library health (MissingFiles, Thumbnails, DB, Archive
re-extract demoted last) / Storage.
- One extension home: BrowserExtensionCard moves from Settings → Overview to
Subscriptions → Settings, above the API key bar it authenticates.
- Single-color import filter WIRED: skip_single_color/threshold existed since
FC-2 but nothing read them (the audit module's docstring said as much) —
now enforced on both import paths via the audit's canonical predicate
(tolerance 30, matching the Cleanup card default; animated images exempt
like the transparency check). Default stays off; test added.
- Dead weight: PlaceholderView (zero refs) and the permanently-disabled
'Export failed logs (CSV — v2)' menu stub deleted; stale docs fixed
(celery queue docstring, threshold comment citing retired tasks, ml
package docstring, HeadsCard 'replaces Camie' blurb).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
GpuAgentCard was hardcoded :open=true, HeadsCard opened whenever any head
existed, TagEvalCard whenever a persisted run existed — so a fresh Settings
load greeted the operator with several tiles already expanded. All three now
force-open only while their task is actually running (the #877 resurface
behavior on the busy-driven tiles is untouched).
MaintenanceTile additionally persists MANUAL expand/collapse per tile in
localStorage, so the section reloads the way the operator left it; a forced
open while a task runs stays transient and is never saved as a preference.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
Surfaces earned auto-apply + its observability in Settings → Tagging → Concept
heads:
- Auto-apply section: an on/off switch (writes head_auto_apply_enabled), the
precision-target + min-examples-to-fire tuning inputs, a Preview (dry-run →
"would apply N", per-concept chips) and Apply-now button, with live run state.
- "How auto-apply is landing": per-concept table from /api/heads/metrics —
applied volume, misfires, realized misfire rate (green/amber/red), and missed
(under-fires) — the signal to tune the precision target from.
store: autoApply(dryRun) / autoApplyStatus() / metrics(). Card polls the sweep
to completion, then refreshes counts + metrics. Completes the auto-apply task.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ttrj5P7upUTueSfoJcxEqa
The UI for the heads subsystem: Settings → Tagging → "Concept heads". Shows
head count, auto-apply-ready count, and last-trained; a Train/Retrain button
(one run at a time, polls while running, surfaces a failed run's error); an
empty state guiding the operator to tag first; and a per-concept table (name,
category, +tags, AP, P, R, auto-apply ⚡) sorted strongest-first so weak/under-
tagged concepts are obvious. Rehydrates status from GET /api/heads on mount so
it survives navigation. Pulls head_min_positives from ML settings for copy.
Slice C (swap the rail's suggestions to heads, remove Camie + centroid) is next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ttrj5P7upUTueSfoJcxEqa