Skip to content

Recurring regression: installed models don't grey out — duplicated downloaded-detection matcher (~16 sites, 3 files) #121

Description

@jdmanring

Summary

Installed local models do not reliably grey out in the Cookbook "What Fits?" catalog after download — a regression that has been "fixed" at least three times (75ff98b8, 15d2666f, a551417e) and keeps coming back. The root cause is not any single matching rule; it is that the "is this catalog model downloaded?" decision is reimplemented ~16 times across 3 files with divergent logic, and only 3 of those copies handle the auto-discovered-GGUF-quant case. There is no single source of truth, so each fix patches a subset of the copies and the next render path (or sync, or refactor) re-exposes an unfixed one.

Root cause

_cachedModelIds (a Set of downloaded repo_ids from /api/model/cached) is matched against catalog entries by ad-hoc string heuristics at ~16 call sites:

File match sites gguf_sources-aware?
static/js/cookbook-hwfit.js 13+ (dlDot, card-greying _downloaded, row re-mark, serve gate, …) only 3 sites
static/js/cookbook.js 3 0
static/js/cookbook-diagnosis.js 1 0

The copies disagree. Example, same file:

  • dlDot (the ● dot) checks name, nameShort, and every gguf_sources repo -> handles a divergent quant repo.
  • Card greying (_downloaded) checks only name + endsWith('/' + short) -> misses the gguf repo.
  • Row re-mark checks name + endsWith -> also misses it.

So the dot lights up but the name never greys: "not all of them grey out."

Why the fork makes it worse (the trigger)

The fork's quality-scored GGUF discovery (fix/gguf-quality-scored) auto-resolves a community quant repo (e.g. bartowski/Meta-Llama-3.1-8B-GGUF) when the catalog model has no static gguf_source. The downloaded repo_id is then the discovered quant repo, which is not the catalog name. Every matcher copy that checks only the catalog name fails. Upstream hits this less because it leans on static gguf_sources; the discovery feature makes the divergence the common case here.

Evidence of the cycle

  • 75ff98b8 fix(cookbook): cached dot missing after download; wrong for GGUF repos (#25)
  • 15d2666f fix(cookbook): ... names not darkening after download
  • a551417e fix(hwfit): darken dot for auto-discovered GGUF repos **in both code paths**

"in both code paths" is the tell: the author knew there were duplicate decisions and fixed two, but there are ~16 and no canonical one.

Upstream landscape (researched)

Related but none cover this client-side matcher:

  • Issue #4049 - server cache becomes unaware of installed models after update (server-side; adjacent).
  • Issue #2342 - auto-discovered models without gguf_sources (the data gap our discovery feature answers).
  • PRs: #3076 (191 missing GGUF sources), #2368 (warn on missing source), #2219/#2993 (cached-model serve/scan), #368 (gguf-only downloads, merged).
  • Discussion: "No GGUF source configured for Cookbook models".

All address data (missing sources) or server cache; the duplicated client downloaded-detection predicate is unreported upstream. The duplication lives in shared static/js/cookbook*.js, so the fix is upstream-candidate.

How other projects avoid this

A textbook single-source-of-truth / canonical-identity pattern: package managers (apt/pip) answer "is X installed?" from one installed-set query, never re-derived per view; Ollama/LM Studio/Jan match installed models by a canonical identity (digest/base repo), not a display name. Durable shape: normalize both sides to a canonical, quant-independent model identity and compare in one predicate; lock it with a test.

Proposed durable fix

  1. One canonical predicate isModelDownloaded(model, cachedIds) doing the complete match: name (full+short), every gguf_sources repo (full+short), and quant_repo/repo_id, normalized to a quant-independent base identity.
  2. Replace all ~16 sites in the 3 files with calls to it. Delete the divergent copies.
  3. Regression-locking test for the better-quant case: installed repo in gguf_sources but != catalog name -> true. This single test would have caught all prior regressions.
  4. Lint/audit guard that fails CI on any raw _cachedModelIds.has(/.some( outside the canonical predicate, so a divergent copy cannot be reintroduced. This is what ends the cycle permanently.

Classification

  • upstream-candidate (shared cookbook*.js), branch from upstream-mirror.
  • Plan: docs/fork/plans/model-downloaded-detection-consolidation.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingupstream-candidateBug or feature suitable for contributing to upstream

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions