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
- 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.
- Replace all ~16 sites in the 3 files with calls to it. Delete the divergent copies.
- 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.
- 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.
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(aSetof downloadedrepo_ids from/api/model/cached) is matched against catalog entries by ad-hoc string heuristics at ~16 call sites:gguf_sources-aware?static/js/cookbook-hwfit.jsdlDot, card-greying_downloaded, row re-mark, serve gate, …)static/js/cookbook.jsstatic/js/cookbook-diagnosis.jsThe copies disagree. Example, same file:
dlDot(the ● dot) checksname,nameShort, and everygguf_sourcesrepo -> handles a divergent quant repo._downloaded) checks onlyname+endsWith('/' + short)-> misses the gguf repo.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 staticgguf_source. The downloadedrepo_idis then the discovered quant repo, which is not the catalogname. Every matcher copy that checks only the catalog name fails. Upstream hits this less because it leans on staticgguf_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 downloada551417e 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:
gguf_sources(the data gap our discovery feature answers).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
isModelDownloaded(model, cachedIds)doing the complete match:name(full+short), everygguf_sourcesrepo (full+short), andquant_repo/repo_id, normalized to a quant-independent base identity.gguf_sourcesbut != catalogname->true. This single test would have caught all prior regressions._cachedModelIds.has(/.some(outside the canonical predicate, so a divergent copy cannot be reintroduced. This is what ends the cycle permanently.Classification
cookbook*.js), branch fromupstream-mirror.docs/fork/plans/model-downloaded-detection-consolidation.md.