Repository navigation
Move the provider model lists into a checked-in JSON catalog - #23
Merged
Merged
Conversation
added 2 commits
August 26, 2026 14:00
The node:test TypeScript loader hands every resolved local import to ts.transpileModule, including `.json` ones. TypeScript cannot emit for JSON input and throws "Debug Failure. Output generation failed", so any module that imports a JSON file was untestable, and the failure surfaced as a crashed test file rather than a readable error. The app compiles with resolveJsonModule and the bundler parses JSON imports directly, so do the same here: JSON.parse the file instead of transpiling it.
Every provider's model picker was a hardcoded ModelOption[] in the renderer store, so shipping a model — one string — meant editing renderer source, type-checking and rebuilding. The arrays also could not say which releases a model belongs to: a build whose CLI never shipped a model still offered it, and a retired model stayed in the picker forever. The twelve arrays now live in src/shared/modelCatalog.json, ported verbatim, and each entry carries inclusive minAppVersion/maxAppVersion bounds. Every ported entry has null for both, so this build offers exactly what it offered before. The catalog is imported at BUILD time — no fs, no network, offline-safe — and modelsForProvider filters it by the running app version, which electron-vite already inlines into the renderer as __APP_VERSION__ for the update badge (esbuild folds the accessor down to `return "0.4.5"`, so no round trip to main is involved). An unparseable bound or version is ignored rather than hiding the model: a picker that silently loses every model is far worse than one offering a model the CLI cannot run, and the command field stays editable either way. Adding a model is now a one-line JSON edit, and a release can introduce or retire one without touching TypeScript. The comments that explained why each list looked the way it did are kept above the import, since JSON cannot carry them.
|
✅ Evidence received. Before and after are both attached. Thanks — this is what makes a PR reviewable in one pass. |
# Conflicts: # src/renderer/src/store/config.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Every provider's model picker was a hardcoded
ModelOption[]in the renderer store, soshipping a model — one string — meant editing renderer source, type-checking and
rebuilding. The arrays also could not say which releases a model belongs to: a build
whose CLI never shipped a model still offered it, and a retired model stayed in the
picker forever.
The twelve arrays now live in
src/shared/modelCatalog.json, ported verbatim, each entrycarrying inclusive
minAppVersion/maxAppVersionbounds. Every ported entry hasnullfor both, so this build offers exactly what it offered before — the version fields are the
mechanism, not a behaviour change. The catalog is imported at build time (no fs, no
network, offline-safe) and
modelsForProviderfilters it by the running app version, whichelectron-vite already inlines into the renderer as
__APP_VERSION__for the update badge.Adding a model is now a one-line JSON edit.
Type of change
Evidence
No visible UI change: the pickers offer the same models, in the same order, with the same
labels. The evidence is the test suite going red → green, plus proof that the built bundle
really carries the version into the filter.
Reproduce both halves:
Before
The catalog-backed API does not exist, so the version-bound assertions fail. The three
tests that do pass are the ones pinning today's picker output — they pass against the
hardcoded arrays, which is what makes them a faithful record of what shipped:
After
Same file, same assertions — the picker-output tests still pass (so nothing a user sees
changed), and the version filter now works:
How I tested it
npm run typecheck— 0 errors (node + web).npm run test:focused— 560 of 560 pass; the base commit is 552 of 552, and the8 new tests are the whole difference.
npm run build— succeeds; bundle checked as above.minAppVersioncheck, dropping themaxAppVersioncheck, editing one catalog label, and making the version accessorignore
__APP_VERSION__each turn the matching test red.Checklist
npm run typecheckpasses.npm run test:focusedpasses.npm run buildsucceeds.commented-out code, or unrelated formatting churn in it.
DESIGN.md/tokens.ts— no ad-hoc colors, spacing,or fonts. (No UI in this change.)
ATTRIBUTION.md. (No art.)How the catalog works & how to maintain it
The provider model lists live in
src/shared/modelCatalog.jsonand are importedat build time.
modelsForProvider(provider)returns that provider's list,filtered at runtime by the running app version.
There are two independent "version" concepts — they do not map to each other:
"version"is the file schema version. Nothing reads it today;it exists so that if the file's structure ever changes, code can branch on
it. Bump it only when the shape of the JSON changes — never for adding or
removing a model.
minAppVersion/maxAppVersionare the app-version bounds(inclusive;
null= unbounded). A model is shown only when the running appversion falls within its bounds. Every model currently ships
null/null, sothe picker is identical to the previous hardcoded lists.
Add a new model
Add an entry under the provider in
modelCatalog.json:{ "id": "gpt-6-nova", "label": "GPT-6 Nova", "minAppVersion": null, "maxAppVersion": null }If the model only works from a future release, set
minAppVersionto thatrelease (e.g.
"0.5.0") so users on older builds — whose CLI can't run it —don't see it. Otherwise leave both bounds
null.Retire an old model
Set
maxAppVersionto the last release that should still offer it:{ "id": "gpt-5.6-sol", "label": "GPT-5.6 Sol", "minAppVersion": null, "maxAppVersion": "0.4.9" }Builds newer than
0.4.9stop offering it, while users still on0.4.xkeepseeing it. To remove a model everywhere at once (all versions), just delete
its entry instead.