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.
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.
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).
## 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)
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.
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.
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).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Three pieces, all CI-green on dev:
G5.1 — Centroid version: DB is truth (
ecac6c4)CentroidServicenow readsMLSettings.embedder_model_version(the DB rowtag_and_embedalready 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_driftedno longer silently misses the env-vs-DB drift case the audit flagged.embedder.pykeepsMODEL_VERSIONas 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
GalleryViewandArtistGalleryTableaked 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=Nentries.openImage()just callsmodal.open(id)— no URL pushGalleryView.closeImage(dead code) deletedApp.vuewatchesroute.nameand closes the modal on every route change — auto-fixes RouterLink-in-modal and back/forward?image=Nis still honored on initial mount as a one-shot deep-link opener, thenrouter.replacestrips 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:
@click.stopdirectly on the v-icon activator. In Vue 3, an explicit@clickon the same element asv-bind="props"overrides the spreadonClick— so Vuetify's activator handler never fired. Menu never opened.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
×close/artist/<slug>from ProvenancePanel) — confirm the modal closes and the route navigates/gallery?image=42directly — confirm modal opens, then the URL becomes plain/gallery(query stripped); confirm browser back-button doesn't re-openAudit reference
This closes the FC integrity-audit drift remediation plan (Scribe #551). All 30 prioritized findings from
wf_bbe3fdb1-e62are now shipped to main across 6 PRs (#49, #50, #51, #52, #53, this one).🤖 Generated with Claude Code