MaintenancePanel hosts: backfill + centroid recompute trigger cards,
the five suggestion-threshold sliders (autosave on slider release),
the allowlist table (inline editable min_confidence, delete), and the
alias table (mapping display, delete). Wired as a third Settings tab,
ML settings loaded lazily when the tab opens.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Thin wrappers over the FC-2b API: ml (settings GET/PATCH, backfill +
recompute triggers), allowlist (list, threshold patch, remove with
optimistic local update), aliases (list, remove with optimistic update).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TagPanel now renders SuggestionsPanel below the current-tags area and
adds a per-chip kebab → Rename… (TagRenameDialog). modal store's
removeTag now also POSTs suggestions/dismiss after the delete so removing
an auto-applied tag records a per-image rejection and the allowlist
maintenance sweep won't re-apply it (closes the remove→re-apply loop
identified in the spec).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AliasPicker: debounced autocomplete scoped to the prediction's category
(artist/character map to tag kinds; copyright searches unscoped),
returns the chosen canonical tag id. RenameDialog: PATCH /api/tags/<id>,
surfaces 409 collisions with the FC-2c merge hint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Store: load per-image suggestions, accept (creates the tag first for
raw/creates_new_tag suggestions, then accepts by id), aliasAccept,
dismiss (client-side hide for raw tags), success toasts folded in.
Panel: people/sources groups always open, General collapsed by default;
alias picker dialog wired; shimmer skeleton while loading. SuggestionItem
shows score %, +new badge for raw tags, kebab menu. AliasPickerDialog
referenced (lands in Task 15).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AppSnackbar mounts once at the app root and exposes a window.__fcToast
function the stores call from error paths. This avoids prop-drilling a
toast emitter through every component while keeping the snackbar
component itself testable in isolation.
GalleryGrid renders shimmer-skeleton placeholders on initial load so the
first paint isn't empty. Skeleton uses the same auto-fill grid columns
as the real items so layout doesn't shift when content arrives.
Modal arrow nav now ignores text-entry elements so typing in the tag
autocomplete doesn't navigate prev/next. Small-screen breakpoint
(<600px) tightens gallery thumbnails to 120px min.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Teleport mounts the modal under <body> so it can break out of the
gallery container's stacking context. Body overflow is locked while open
so background scroll doesn't compete with pan/zoom.
Keyboard: left/right arrows navigate prev/next (preventDefault so they
don't also scroll the body), Esc closes, Tab is trapped within the
modal by browser default since the modal is focused on mount.
TagPanel renders kind-colored chips with the close (×) affordance for
removal, and hosts TagAutocomplete + FandomPicker for additions. Below
900px the panel drops below the media area instead of beside it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TagAutocomplete: kind selector + debounced (200ms) search + keyboard nav
(up/down/enter/esc). When the operator types a character that doesn't
exist, the create flow first opens FandomPicker so the new character tag
lands with a valid fandom_id (otherwise the API rejects it).
FandomPicker lists existing fandoms (cached after first load) with an
inline create-new affordance, so the operator never has to leave the modal
to manage fandom hierarchies.
Tag store also exposes a kind→icon→color mapping that the TagPanel reuses
in Task 22 — keeping chip colors visually distinct per kind without per-app
accent collisions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ImageCanvas mounts an <img> wrapped in the usePanZoom composable —
spreading its handlers onto the container element. Zoom resets when src
changes so prev/next nav doesn't carry zoom state into the next image.
VideoCanvas plays MP4/WebM/Ogg/MOV natively. Non-playable formats render
a friendly explainer with a download fallback, naming the actual MIME so
operators know what they're dealing with, and pointing forward at FC-2e
where transcoding lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
modal store loads image detail (with neighbors) on open, exposes goPrev/
goNext that re-fetch the next image, and implements optimistic tag
remove with rollback on API failure. createAndAdd is the bridge between
the autocomplete UI and a brand-new tag.
usePanZoom is a small composable: click-to-toggle 2x zoom centered on
click point, wheel-to-zoom up to 4x, drag-to-pan when zoomed. Pointer
events are used (touch + mouse + pen unified). Caller spreads handlers
onto the element it wants pannable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GalleryView wires the grid + timeline + modal together. The URL is the
source of truth for which image (if any) is open — clicking an item pushes
?image=N; closing the modal pops it. This makes deep links shareable and
the back button work intuitively.
Below 900px viewport, the timeline drops below the grid as a horizontal
strip so it doesn't compete with thumbnail space on mobile.
Modal store + ImageViewer are placeholders here; real implementations land
in Batch 5 (Tasks 19–22).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Grid uses CSS grid auto-fill with 160px minimums so it adapts naturally
from phone to large displays. Date headers render once per (year, month)
group; the API's date_groups payload tells us where to slot them without
client-side grouping. IntersectionObserver with rootMargin: 600px loads
the next page well before the sentinel reaches the viewport.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gallery store handles cursor pagination, date-group merging across pages,
tag filter, timeline buckets, and jump-to-month. Stale response detection
via an inflightId counter prevents out-of-order page additions when the
user toggles filters quickly.
GalleryItem is a square thumbnail card with hover/focus accent outline,
keyboard activation (Enter/Space → open), video badge for video MIME types,
and a broken-image fallback if the thumbnail URL fails to load.
EmptyState points operators to /settings?tab=import so the empty path is
self-guiding.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ImportTriggerPanel shows an active-batch progress indicator when scan_directory
is running, or a "Quick scan" button otherwise. Deep scan deferred to FC-2d
is called out in the panel text so it's not a surprise.
ImportFiltersForm autosaves on blur/change so there's no Submit button. The
transparency and single-color sliders gate on their respective switches.
ImportTaskList uses v-data-table-virtual for performance on long histories,
with status filter, retry-failed, and a clear-completed dialog with age
buckets. Status chips are color-coded via FabledDesignSystem semantic
tokens (success/warning/error/accent/info).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Overview tab shows total images/tags/storage/pending/failed using
Fraunces for the big numbers. Inline alert nudges the operator to the
Import tab when pending tasks exist. Polls /api/system/stats every 5s
while the tab is visible (pauses when the document is hidden).
Import tab references three placeholders (TriggerPanel/FiltersForm/
TaskList); real implementations land in Task 15.
Router replaces the placeholder view for /settings with SettingsView.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>