dev→main: gallery similarity search (Phase 3) + UI/mobile polish #63

Merged
bvandeusen merged 5 commits from dev into main 2026-06-04 11:21:26 -04:00
Owner

CI-green dev batch (HEAD 4fd6d4c, run #494 green). Adds alembic 0036 (HNSW index — builds over existing embeddings on deploy, ~30–60s table lock).

Gallery — visual "more like this" (Phase 3)

  • GalleryService.similar() ranks images by pgvector cosine distance to a source image's precomputed SigLIP embedding (no query-time ML). Composes with the Phase-1/2 scope filters; bounded top-N; 404 on missing source, empty for un-embedded.
  • GET /api/gallery/similar, has_embedding on image-detail, alembic 0036 HNSW vector_cosine_ops index.
  • Modal Related strip (non-blocking: deferred, single-flighted, collapses on empty/slow/error, hidden when no embedding) + "See all similar" gallery takeover with a clearable "Similar to #id" chip.

UI polish

  • Showcase reveal cadence doubled (80→160ms) for a slower, more deliberate cascade.
  • Gallery filter bar restyled to the exact TopNav gradiated-obsidian frost so the two read as one continuous piece of chrome.

Mobile responsiveness

  • TopNav: nav links fold into a hamburger menu <768px; brand text hides <480px (was a colliding 7-link flex row).
  • Primary path: BulkEditorPanel min(320px,90vw); GalleryFilterBar search full-width row <600px; GalleryFacetPanel date inputs stack <480px; ArtistsView grid minmax(min(440px,100%),1fr); GalleryView hides the timeline strip <600px.
  • Posts & Subscriptions filter bars: wrap + full-width fields on phones.

🤖 Generated with Claude Code

CI-green dev batch (HEAD `4fd6d4c`, run #494 green). Adds alembic **0036** (HNSW index — builds over existing embeddings on deploy, ~30–60s table lock). ## Gallery — visual "more like this" (Phase 3) - `GalleryService.similar()` ranks images by pgvector cosine distance to a source image's precomputed SigLIP embedding (no query-time ML). Composes with the Phase-1/2 scope filters; bounded top-N; 404 on missing source, empty for un-embedded. - `GET /api/gallery/similar`, `has_embedding` on image-detail, alembic 0036 HNSW `vector_cosine_ops` index. - Modal **Related strip** (non-blocking: deferred, single-flighted, collapses on empty/slow/error, hidden when no embedding) + "See all similar" gallery takeover with a clearable "Similar to #id" chip. ## UI polish - Showcase reveal cadence doubled (80→160ms) for a slower, more deliberate cascade. - Gallery filter bar restyled to the **exact** TopNav gradiated-obsidian frost so the two read as one continuous piece of chrome. ## Mobile responsiveness - **TopNav**: nav links fold into a hamburger menu <768px; brand text hides <480px (was a colliding 7-link flex row). - Primary path: BulkEditorPanel `min(320px,90vw)`; GalleryFilterBar search full-width row <600px; GalleryFacetPanel date inputs stack <480px; ArtistsView grid `minmax(min(440px,100%),1fr)`; GalleryView hides the timeline strip <600px. - Posts & Subscriptions filter bars: wrap + full-width fields on phones. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 5 commits 2026-06-04 11:21:19 -04:00
feat(gallery): visual 'more like this' search (Phase 3 backend)
CI / lint (push) Successful in 2s
CI / backend-lint-and-test (push) Successful in 11s
CI / frontend-build (push) Successful in 28s
CI / integration (push) Successful in 2m57s
79cd1234e2
GalleryService.similar() ranks images by pgvector cosine distance to a source
image's precomputed SigLIP embedding — no query-time ML inference. Composes
with the Phase-1/2 scope filters (AND) but replaces the date sort (always
nearest-first, bounded top-N, no cursor). Returns None for a missing source
(→404), [] for a source with no embedding (video / pending ML); excludes self
and NULL-embedding rows. New GET /api/gallery/similar?similar_to=<id>&limit=N.
Image-detail payload gains has_embedding so the UI can hide the surface.

Alembic 0036 adds an HNSW vector_cosine_ops index on siglip_embedding (1152<2000
dims) so the search is sub-50ms ANN instead of a full scan; one-time ~30-60s
build over existing embeddings on deploy. Shared _gallery_images/_image_json
helpers de-dup the scroll/similar builders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(gallery): visual 'more like this' UI (Phase 3 frontend)
CI / lint (push) Successful in 2s
CI / backend-lint-and-test (push) Successful in 10s
CI / frontend-build (push) Successful in 22s
CI / integration (push) Successful in 2m57s
21a73cd1dc
Modal 'Related' strip (RelatedStrip.vue) — top-12 similar thumbs, fetched on
its own DEFERRED, single-flighted path (200ms after the modal is up) so it
never blocks or slows the modal; collapses silently on empty/slow/error and is
hidden when the image has no embedding (has_embedding flag). 'See all similar'
closes the modal and navigates the gallery to ?similar_to=<id>.

Gallery store: similar_to filter field + loadSimilar() (ranked, hasMore=false,
no timeline); applyFilterFromQuery routes similar-mode to /similar with the
scope filters composed; cloneFilter/filterToQuery carry similar_to. Filter bar:
clearable 'Similar to #id' chip, sort hidden in similar-mode; timeline sidebar
hidden too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(ui): double showcase cadence + filter bar matches TopNav frost
CI / lint (push) Successful in 2s
CI / backend-lint-and-test (push) Successful in 12s
CI / frontend-build (push) Successful in 17s
CI / integration (push) Successful in 2m56s
0497394710
- Showcase reveal cadence 80ms -> 160ms (slower, more deliberate one-at-a-time
  cascade per operator). Bump showcase.spec timer advances to cover 60x160ms.
- Gallery filter bar now uses the EXACT gradiated-obsidian frost + blur as
  TopNav (was a flat rgba(...,0.55) block), so the two read as one continuous
  piece of chrome with images visibly scrolling under both; the nav's
  transparent bottom edge against the bar's opaque top leaves a faint seam that
  separates them at the very top of scroll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(ui): mobile responsiveness — nav hamburger + primary-path fixes
CI / lint (push) Successful in 3s
CI / backend-lint-and-test (push) Successful in 12s
CI / frontend-build (push) Successful in 29s
CI / integration (push) Successful in 2m57s
304e8aa878
The top nav packed brand + health + pipeline chip + ~7 inline links + an
action slot into one flex row, colliding/overflowing on phones (operator:
'almost unusable'). Below 768px the links now fold into a hamburger v-menu;
below 480px the brand text hides (glyph still brands). Plus the primary
browsing path:
- BulkEditorPanel: fixed 320px -> min(320px, 90vw) so it can't swallow the screen.
- GalleryFilterBar: <600px gives search its own full-width row (its 200px
  min-width was jamming the wrapping bar); sort grows.
- GalleryFacetPanel: <480px wraps groups + lets the side-by-side date inputs
  grow full-width.
- ArtistsView grid: minmax(min(440px,100%),1fr) so a card never overflows
  (single column on phones).
- GalleryView: hide the year/month timeline strip <600px.
ImageViewer already stacks its side panel below the image <900px (left as-is).

Secondary surfaces (Posts/Subscriptions filter bars, SubscriptionsTab table,
SeriesReader, PostCard) still need a mobile pass — follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(ui): mobile pass 2 — Posts & Subscriptions filter bars
CI / lint (push) Successful in 2s
CI / backend-lint-and-test (push) Successful in 12s
CI / frontend-build (push) Successful in 34s
CI / integration (push) Successful in 2m58s
4fd6d4cc29
PostsFilterBar didn't wrap and its artist/platform fields had inline
min-widths (240/180px) a media query can't override → horizontal overflow on
phones. Moved widths to classes, added flex-wrap, and <600px each field takes
a full-width row. SubscriptionsTab's status/search inline max-widths likewise
moved to classes; <600px they go full-width and the v-spacer is dropped so the
search isn't shoved around.

Verified as already-fine (sweep false positives, no change): PostCard (default
body is a stacked column; only goes row at container >=800px), SeriesReaderView
(already has a <=768px block: nav drawer 150px, quick-nav stacks). The
subscriptions v-data-table scrolls horizontally within its own wrapper, so it
doesn't widen the page — a true mobile card layout is a larger follow-up if
wanted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bvandeusen merged commit 9430a9d9c3 into main 2026-06-04 11:21:26 -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#63