audit-g5 final + ML threshold default + kebab menu fix #54

Merged
bvandeusen merged 5 commits from dev into main 2026-06-02 19:09:50 -04:00
Owner

Summary

Three pieces, all CI-green on dev:

G5.1 — Centroid version: DB is truth (ecac6c4)

CentroidService now reads MLSettings.embedder_model_version (the DB row tag_and_embed already writes from) for both the centroid model-version stamp and the drift-detection comparison. By construction the centroid stamp now matches the embedding stamp; list_drifted no longer silently misses the env-vs-DB drift case the audit flagged.

embedder.py keeps MODEL_VERSION as an env-driven constant for the actual model loader — that's a different concern (which weights are loaded) from the version stamp that gets persisted alongside data.

G5.4 — Modal as overlay (ecac6c4)

The image modal is now Pinia-only state. Previously the URL↔modal sync in GalleryView and ArtistGalleryTab leaked the modal across route changes (RouterLink to /artist/<slug> left the modal mounted on top of the new route) and re-opened it on history back/forward with stale ?image=N entries.

  • openImage() just calls modal.open(id) — no URL push
  • GalleryView.closeImage (dead code) deleted
  • App.vue watches route.name and closes the modal on every route change — auto-fixes RouterLink-in-modal and back/forward
  • Backward-compat: ?image=N is still honored on initial mount as a one-shot deep-link opener, then router.replace strips the query so the URL doesn't re-trigger and no extra history entry is added. Existing bookmarks / shared URLs keep working.

This closes the last two findings from the 2026-06-02 multi-system drift audit.

ML suggestion threshold default 0.50 → 0.70 (1fd594b + b181d77)

Operator-flagged 2026-06-02 — the 0.50 default (set on 2026-06-01) surfaces too many low-confidence picks in the modal's Suggestions rail. 0.70 keeps the rail signal-rich while still showing more than the original 0.95 (which hid almost everything).

Alembic 0033 updates the singleton row conditionally — only rows still at the old 0.50 default flip to 0.70. Operators who tuned to some other value via Settings → ML keep their pick.

Settings UI (MLThresholdSliders.vue) already exposes both sliders, so further tuning continues to work without a deploy.

Kebab menus weren't opening (8326e54 + 0fbb19d)

Operator-flagged 2026-06-02. Both modal kebabs were broken:

  • TagPanel chip kebab had @click.stop directly on the v-icon activator. In Vue 3, an explicit @click on the same element as v-bind="props" overrides the spread onClick — so Vuetify's activator handler never fired. Menu never opened.
  • SuggestionItem kebab didn't have the same override issue but for consistency now follows the same shape.

Fix: each kebab is now wrapped in a <span @click.stop>. The v-icon / v-btn receives Vuetify's onClick cleanly and opens the menu; the bubbling click then reaches the span where stopPropagation absorbs it before it can affect a parent (the v-chip's close button in TagPanel's case).

Test Plan

  • Open an image in the modal, click the kebab (⋮) on an applied tag chip — confirm the menu opens with "Rename…"; clicking it opens the rename dialog
  • Same image, click the kebab on a suggestion row — confirm the menu opens with "Treat as alias for…" and "Dismiss for this image"
  • Confirm that clicking the kebab does NOT trigger the chip's × close
  • Open an image in the modal, click a RouterLink (e.g. to /artist/<slug> from ProvenancePanel) — confirm the modal closes and the route navigates
  • Open /gallery?image=42 directly — confirm modal opens, then the URL becomes plain /gallery (query stripped); confirm browser back-button doesn't re-open
  • Visit Settings → ML — confirm both sliders default to 0.70 on a fresh install; existing operators with 0.50 see 0.70 after the migration runs; operators who tuned to e.g. 0.65 keep 0.65

Audit reference

This closes the FC integrity-audit drift remediation plan (Scribe #551). All 30 prioritized findings from wf_bbe3fdb1-e62 are now shipped to main across 6 PRs (#49, #50, #51, #52, #53, this one).

🤖 Generated with Claude Code

## Summary Three pieces, all CI-green on dev: ### G5.1 — Centroid version: DB is truth (ecac6c4) `CentroidService` now reads `MLSettings.embedder_model_version` (the DB row `tag_and_embed` already writes from) for both the centroid model-version stamp and the drift-detection comparison. By construction the centroid stamp now matches the embedding stamp; `list_drifted` no longer silently misses the env-vs-DB drift case the audit flagged. `embedder.py` keeps `MODEL_VERSION` as an env-driven constant for the actual model loader — that's a different concern (which weights are loaded) from the version stamp that gets persisted alongside data. ### G5.4 — Modal as overlay (ecac6c4) The image modal is now Pinia-only state. Previously the URL↔modal sync in `GalleryView` and `ArtistGalleryTab` leaked the modal across route changes (RouterLink to `/artist/<slug>` left the modal mounted on top of the new route) and re-opened it on history back/forward with stale `?image=N` entries. - `openImage()` just calls `modal.open(id)` — no URL push - `GalleryView.closeImage` (dead code) deleted - `App.vue` watches `route.name` and closes the modal on every route change — auto-fixes RouterLink-in-modal and back/forward - **Backward-compat**: `?image=N` is still honored on initial mount as a one-shot deep-link opener, then `router.replace` strips the query so the URL doesn't re-trigger and no extra history entry is added. Existing bookmarks / shared URLs keep working. This closes the last two findings from the 2026-06-02 multi-system drift audit. ### ML suggestion threshold default 0.50 → 0.70 (1fd594b + b181d77) Operator-flagged 2026-06-02 — the 0.50 default (set on 2026-06-01) surfaces too many low-confidence picks in the modal's Suggestions rail. 0.70 keeps the rail signal-rich while still showing more than the original 0.95 (which hid almost everything). Alembic 0033 updates the singleton row conditionally — only rows still at the old 0.50 default flip to 0.70. Operators who tuned to some other value via Settings → ML keep their pick. Settings UI (`MLThresholdSliders.vue`) already exposes both sliders, so further tuning continues to work without a deploy. ### Kebab menus weren't opening (8326e54 + 0fbb19d) Operator-flagged 2026-06-02. Both modal kebabs were broken: - **TagPanel chip kebab** had `@click.stop` directly on the v-icon activator. In Vue 3, an explicit `@click` on the same element as `v-bind="props"` overrides the spread `onClick` — so Vuetify's activator handler never fired. Menu never opened. - **SuggestionItem kebab** didn't have the same override issue but for consistency now follows the same shape. Fix: each kebab is now wrapped in a `<span @click.stop>`. The v-icon / v-btn receives Vuetify's onClick cleanly and opens the menu; the bubbling click then reaches the span where stopPropagation absorbs it before it can affect a parent (the v-chip's close button in TagPanel's case). ## Test Plan - [ ] Open an image in the modal, click the kebab (⋮) on an applied tag chip — confirm the menu opens with "Rename…"; clicking it opens the rename dialog - [ ] Same image, click the kebab on a suggestion row — confirm the menu opens with "Treat as alias for…" and "Dismiss for this image" - [ ] Confirm that clicking the kebab does NOT trigger the chip's `×` close - [ ] Open an image in the modal, click a RouterLink (e.g. to `/artist/<slug>` from ProvenancePanel) — confirm the modal closes and the route navigates - [ ] Open `/gallery?image=42` directly — confirm modal opens, then the URL becomes plain `/gallery` (query stripped); confirm browser back-button doesn't re-open - [ ] Visit Settings → ML — confirm both sliders default to 0.70 on a fresh install; existing operators with 0.50 see 0.70 after the migration runs; operators who tuned to e.g. 0.65 keep 0.65 ## Audit reference This closes the FC integrity-audit drift remediation plan (Scribe #551). All 30 prioritized findings from `wf_bbe3fdb1-e62` are now shipped to main across 6 PRs (#49, #50, #51, #52, #53, this one). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 5 commits 2026-06-02 19:09:44 -04:00
fix(audit-g5): centroid version DB-as-truth + modal as overlay
CI / lint (push) Successful in 3s
CI / backend-lint-and-test (push) Successful in 24s
CI / frontend-build (push) Successful in 27s
CI / intimp (push) Successful in 3m49s
CI / intapi (push) Successful in 7m57s
CI / intcore (push) Successful in 8m46s
ecac6c4bda
Closes the last two findings from the 2026-06-02 audit (G5.1 + G5.4).

G5.1 — Centroid version no longer drifts:

CentroidService now reads MLSettings.embedder_model_version (the DB
row tag_and_embed already writes from) for both the centroid model-
version stamp and the drift-detection comparison. Previously the
centroid sites imported MODEL_VERSION from env, so the version stamped
on centroids could disagree with the version stamped on the embeddings
they were built from. By construction those now match, so list_drifted
won't silently miss the env-vs-DB drift case.

embedder.py keeps MODEL_VERSION as an env-driven constant for the
actual model loader — that's a different concern (which weights are
loaded) from the version-stamp that gets persisted alongside data.

G5.4 — Modal is a Pinia-only overlay:

The previous URL↔modal sync in GalleryView and ArtistGalleryTab
leaked the modal across route changes (RouterLink to /artist/<slug>
left the modal mounted on top of the new route) and re-opened it
on history back/forward with stale ?image=N entries.

Now: openImage() just calls modal.open(id) — no URL push.
GalleryView's dead closeImage helper is deleted. A route.name
watcher in App.vue closes the modal whenever the route changes,
which auto-fixes RouterLink-in-modal and back/forward.

Backward-compat: ?image=N is still honored on initial mount as a
one-shot deep-link opener, then router.replace strips the query so
the URL doesn't re-trigger and no extra history entry is added.
Existing bookmarks / shared URLs keep working; new opens stay
Pinia-only.
chore(ml): suggestion_threshold default 0.50 → 0.70
CI / lint (push) Successful in 4s
CI / backend-lint-and-test (push) Successful in 28s
CI / frontend-build (push) Successful in 29s
CI / intimp (push) Successful in 3m44s
CI / intapi (push) Failing after 8m16s
CI / intcore (push) Successful in 8m43s
1fd594baaf
Operator-flagged 2026-06-02 — the 0.50 default (set on 2026-06-01)
surfaces too many low-confidence picks in the modal's Suggestions
rail. 0.70 keeps the rail signal-rich while still showing more than
the original 0.95 (which hid almost everything).

Alembic 0033 updates the singleton row conditionally — only rows
still at the old 0.50 default flip to 0.70. Operators who tuned to
some other value via Settings → ML keep their pick.

Settings UI already exposes both sliders (MLThresholdSliders.vue),
so further tuning continues to work without a deploy.
fix(modal): kebab menus weren't opening (chip + suggestion rows)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 25s
CI / backend-lint-and-test (push) Successful in 25s
CI / intimp (push) Successful in 3m40s
CI / intapi (push) Failing after 8m41s
CI / intcore (push) Successful in 10m0s
8326e5447a
Operator-flagged 2026-06-02. Both kebab activators were broken:

- TagPanel chip kebab had `@click.stop` directly on the v-icon
  activator. In Vue 3, an explicit @click on the same element as
  `v-bind="props"` overrides the spread onClick — so Vuetify's
  activator handler never fired. Menu never opened.

- SuggestionItem kebab didn't have @click.stop, but for consistency
  and to make both kebabs follow the same shape, wrap it too.

The fix: each kebab is now wrapped in a `<span @click.stop>`. The
v-icon / v-btn receives Vuetify's onClick cleanly and opens the menu;
the bubbling click then reaches the span where stopPropagation
absorbs it before it can affect a parent (the v-chip's close button
in TagPanel's case).
fix(modal): TagPanel kebab — apply the wrapping-span fix that the prior commit missed
CI / lint (push) Successful in 3s
CI / backend-lint-and-test (push) Successful in 37s
CI / frontend-build (push) Successful in 26s
CI / intimp (push) Successful in 3m59s
CI / intapi (push) Failing after 8m56s
CI / intcore (push) Successful in 10m0s
0fbb19dc24
The previous commit (8326e54) updated the CSS but the structural
edit to the v-menu wrapper didn't take. Re-applying so the chip
kebab actually opens.
fix(test): default suggestion_threshold_general now 0.70 (alembic 0033)
CI / lint (push) Successful in 3s
CI / backend-lint-and-test (push) Successful in 17s
CI / frontend-build (push) Successful in 26s
CI / intimp (push) Successful in 3m49s
CI / intapi (push) Successful in 8m13s
CI / intcore (push) Successful in 7m6s
b181d779fe
bvandeusen merged commit 9cf6b2d363 into main 2026-06-02 19:09:50 -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#54