Skip to content

fix: union the authoritative vision-model list into VisionProviderPicker (#4157) - #4264

Merged
atomantic merged 2 commits into
mainfrom
claim/issue-4157
Aug 15, 2026
Merged

fix: union the authoritative vision-model list into VisionProviderPicker (#4157)#4264
atomantic merged 2 commits into
mainfrom
claim/issue-4157

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

VisionProviderPicker (the universe builder's image-describe, style-reference and corrective-reference pickers) filtered local backends with the client-side id regex alone, so an installed VLM from a family the regex predates was hidden — the picker showed a "no vision-capable model installed" blocker next to an empty dropdown. Every other vision picker (AiAssignmentsTab, CreativeDirectorModelsDrawer, PromptFromMedia) already unions the server's authoritative per-provider set from useVisionModelIds; this one now passes it as visionLocalModelFilter's third argument too.

That set resolves after the first provider load, so the modelFilter's identity changes — which useProviderModels handled badly in two ways, both fixed here:

  • load depended on pickInitialModel (and so on modelFilter), so a filter change re-ran the whole api.getProviders() fetch for data it already had. The picker now reaches the async body through a ref, keeping load's identity stable.
  • hasSetInitialRef froze the auto-pick at whatever the first, blinder filter produced, so the authoritative list never got a say — selectedModel stayed '' while availableModels filled in. A new effect re-runs the initial pick when the filter's identity changes and the current selection is no longer in the list. It stands down permanently once the user picks or clears a model: '' alone can't distinguish a deliberate clear from "the filter matched nothing", so a userPickedModelRef latch carries that distinction. Scoped to filtered, non-allowDefault pickers, so the ~25 other consumers of the hook are untouched.

The picker also holds its "no vision model" blocker until the capability scan settles on a local backend (the visionPending idiom already used in StackerNews / CreativeDirectorModelsDrawer), so it no longer flashes the blocker mid-fetch, and it reports that window as loading to its callers.

PromptFromMedia passes a filter with the same changing identity and silently inherits both fixes.

Test plan

  • client/src/hooks/useProviderModels.test.js — new block for a modelFilter whose identity changes: re-picks once the filter widens; no second getProviders() call; leaves a deliberate clear and a user-picked model alone; re-arms after a provider change; allowDefault still keeps the empty-model sentinel.
  • client/src/components/universe/VisionProviderPicker.test.jsx — new file (the component had none): offers a VLM the id regex doesn't recognize once the server list lands, still blocks when the backend genuinely has none, doesn't flash the blocker mid-scan, leaves a cloud provider unfiltered, and explains the no-provider case.
  • Both suites were verified to FAIL against the unfixed source (3 hook tests, 2 picker tests) before the fix was restored.
  • VisionDescribeModal.test.jsx stubs useVisionModelIds — the picker now runs a capability scan on mount, which the suite would otherwise hit outside act().
  • Full client suite: cd client && npx vitest run → 646 files, 7877 tests passing (one unrelated QuotaBurn.test.jsx parallel-run flake, green in isolation).
  • npx biome lint --error-on-warnings clean on every touched file.

Closes #4157

…ker (#4157)

The universe builder's vision picker filtered local backends by the client id
regex alone, so an installed VLM from a family the regex predates was hidden and
the picker rendered a "no vision-capable model installed" blocker. Every other
vision picker already unions the server's per-provider capability set; this one
now does too.

That set arrives after the first provider load, which changes the `modelFilter`'s
identity — and `useProviderModels` handled that badly in two ways: `load`
depended on the filter, so a widened filter re-ran `api.getProviders()` for data
it already had; and `hasSetInitialRef` froze the auto-pick at whatever the first,
blinder filter produced, so the authoritative list never got a say. The hook now
re-runs its initial pick on a filter change without refetching, and stands down
permanently once the user picks or clears a model — `''` alone can't tell a
deliberate clear from "the filter matched nothing", so a latch does.

The picker also holds its blocker until the capability scan settles, so it no
longer flashes "no vision model" mid-fetch on a local backend.
@atomantic
atomantic merged commit da8abb9 into main Aug 15, 2026
7 checks passed
@atomantic
atomantic deleted the claim/issue-4157 branch August 15, 2026 04:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

VisionProviderPicker doesn't union authoritative vision-model list (gemma4 gap)

1 participant