Fix wave chunk A: no Default option, composer = strong slot, catalog revalidation

Item 1 — no Default in the slot selectors (owner, fix wave): a
"default" model is not a thing in this app. Picking Default used to
delete the slot and re-arm the first-run gate — incoherent. Now the
model menus carry NO clear row at all (a slot holds a pick or reads
"—"), the thinking menus carry a "—" row (menu-none) that clears only
the thinking pin, and the hint line is the roster only — the resolved
factory pin is never named. Master's amendment confirmed: the
factory-pin hint is SUPERSEDED entirely (Platform removes the pins).

Item 2 — the composer control IS the strong slot (owner: "one and the
same"): ModelControl (model-picker.tsx) reads the raw models section
(GET /api/settings) and the federated catalog, and writes
PUT /api/settings/models via composeSlotModel — the same seam the
settings Agent section and SetupDialog use. Props retired: chatId,
settings, onSaved, fallbackModelId, noDefaultModel — the control is
self-sufficient and identical on every surface (composer, home dock,
/chat/new). The per-chat model PUT is gone from the UI; legacy
per-chat overrides keep resolving server-side. StatsBar/DockOverlay
plumbing slimmed accordingly; chat.tsx drops its per-chat settings
state and initialSettings prop.

Item 3 — the composer catalog cache no longer freezes per page load:
fetchCatalog(force) invalidates, and every model-menu open revalidates
in the background (stale-while-revalidate — cached list shows
instantly, the fresh federation swaps in). Owner's repro verified live:
a key saved mid-session widens the menu without a reload.

Item 4 — stale doc comments fixed: model-catalog.ts's "OpenRouter
catalog"/"multi-provider future" (Platform's file — mechanical,
flagged for review) and model-picker's retired "config-pinned"/
per-chat-PUT narrative.

Live-verified on 3001: unpicked slots read "—" everywhere; model
menus have no clear row; the "—" thinking row clears the pin but
keeps the model; composer pick writes strong and preserves cheap/
vision siblings; thinking pill writes strong.thinkingLevel; groq key
added mid-session appears in the open menu without reload. Caught in
live verification: the record fetch was mapping the WHOLE settings
record into the models section (picks never wrote) — fixed to read
body.settings.models.

Gates: typecheck, lint, 807/807, build.
This commit is contained in:
amosblomqvist
2026-09-22 16:40:31 +03:00
parent d85d8a58a0
commit f6626f1703
10 changed files with 157 additions and 180 deletions
+10 -7
View File
@@ -1,17 +1,20 @@
/**
* The model catalog's server surface (model-control wave, 2026-09-12):
* pi-ai's shipped OpenRouter catalog projected to client-safe DTOs for the
* picker (GET /api/models), plus the validation gate for per-chat settings
* (PUT /api/chats/[id]/settings).
* The model catalog's server surface (model-control wave, 2026-09-12;
* federated since the model-slots wave, 2026-09-20): pi-ai's catalog for
* every KEYED provider, projected to client-safe DTOs for the picker (GET
* /api/models), plus the validation gate for settings writes (PUT
* /api/settings/* — the per-chat settings PUT is retired from the UI;
* legacy records' per-chat overrides still resolve server-side).
*
* pi-ai owns ALL the data: the catalog (id/name/cost/context/modality/
* thinkingLevelMap) and the per-model thinking truth
* (getSupportedThinkingLevels / clampThinkingLevel). This module only
* projects and validates — no second registry to drift out of sync.
*
* Multi-provider future: `getModels()` is called per configured provider,
* so a second provider (pi-ai ships ~30) appears in the picker by adding
* it to piModels() in src/agent/models.ts — nothing here changes.
* Federation: `getModels()` is called per keyed provider (federated, the
* model-slots wave), so a provider appears in the picker exactly when its
* key is present — piModels() in src/agent/models.ts owns the provider
* set; nothing here changes.
*/
import { getSupportedThinkingLevels, type Api, type Model } from "@earendil-works/pi-ai";