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:
@@ -388,12 +388,16 @@ export function AgentSection({
|
||||
.then((body: { prober: SegmentInfo[]; teacher: SegmentInfo[] }) => setPrompts(body));
|
||||
}
|
||||
|
||||
/** The pinned Default row — clears this control's override (absent key
|
||||
* = the factory pin). Same row language as the shared menu's list rows. */
|
||||
const defaultRow = (active: boolean, onPick: () => void) => (
|
||||
/** The "—" row — THINKING menus only: clears the thinking pin (the
|
||||
* slot keeps its model; the tier's resolved budget takes over). Model
|
||||
* menus carry no such row — unpicking a model would re-arm the
|
||||
* first-run gate, and a "default" model is not a thing in this app
|
||||
* (owner, fix wave: no factory fallback is ever presented as a
|
||||
* choice; an unpicked slot just reads "—"). */
|
||||
const noneRow = (active: boolean, onPick: () => void) => (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="menu-default"
|
||||
data-testid="menu-none"
|
||||
onClick={onPick}
|
||||
className={`flex w-full items-center justify-between rounded-lg px-2 py-1.5 text-left transition-colors hover:bg-muted ${
|
||||
active ? "bg-muted/60 text-card-foreground" : "text-card-foreground"
|
||||
@@ -514,10 +518,12 @@ export function AgentSection({
|
||||
const modelId = override?.model?.id ?? "";
|
||||
const provider = override?.model?.provider ?? "";
|
||||
const thinking = override?.thinkingLevel ?? "";
|
||||
// The tier's resolved pin (override → factory): the Default
|
||||
// hint names it; the thinking list resolves from its levels.
|
||||
// The tier's resolved pin is machinery only now (owner, fix
|
||||
// wave): the UI never names the factory fallback — the hint
|
||||
// carries the roster, the triggers read "—" when unpicked — but
|
||||
// the pin still governs the thinking level LIST when no override
|
||||
// names a model to read levels from.
|
||||
const pin = slots?.[slot];
|
||||
const defaultName = pin ? prettyModel(pin.model.id) : "config pin";
|
||||
// The model whose levels govern the thinking list: the
|
||||
// override, else the resolved pin (when it's in the federated
|
||||
// catalog); the standard ladder as the last fallback.
|
||||
@@ -532,7 +538,7 @@ export function AgentSection({
|
||||
<SettingsRow
|
||||
key={slot}
|
||||
label={SLOT_META[slot].label}
|
||||
hint={`${SLOT_META[slot].roster} · default: ${defaultName}`}
|
||||
hint={SLOT_META[slot].roster}
|
||||
testid={`slot-row-${slot}`}
|
||||
>
|
||||
{/* The anchor for the outside-click contract is the
|
||||
@@ -543,7 +549,7 @@ export function AgentSection({
|
||||
>
|
||||
<MenuTrigger
|
||||
value={modelId ? prettyModel(modelId) : ""}
|
||||
emptyLabel="Default"
|
||||
emptyLabel="—"
|
||||
ariaLabel={`${SLOT_META[slot].label} model`}
|
||||
testid={`slot-model-${slot}`}
|
||||
open={thisMenuOpen === "model"}
|
||||
@@ -552,7 +558,7 @@ export function AgentSection({
|
||||
/>
|
||||
<MenuTrigger
|
||||
value={thinking}
|
||||
emptyLabel="Default"
|
||||
emptyLabel="—"
|
||||
ariaLabel={`${SLOT_META[slot].label} thinking`}
|
||||
testid={`slot-thinking-${slot}`}
|
||||
open={thisMenuOpen === "thinking"}
|
||||
@@ -565,17 +571,10 @@ export function AgentSection({
|
||||
testid={`slot-model-menu-${slot}`}
|
||||
menuClass="flex w-[22rem] flex-col rounded-xl border border-border bg-card p-2 text-xs shadow-xl"
|
||||
>
|
||||
{defaultRow(
|
||||
modelId === "",
|
||||
() => {
|
||||
onModelsChange(composeSlotModel(models, slot, "", "", thinking));
|
||||
setOpenMenu(null);
|
||||
},
|
||||
)}
|
||||
<ModelMenuList
|
||||
models={slotCatalog ?? undefined}
|
||||
effectiveId={modelId || pin?.model.id}
|
||||
effectiveProvider={provider || pin?.model.provider}
|
||||
effectiveId={modelId}
|
||||
effectiveProvider={provider}
|
||||
onPick={(entry) => {
|
||||
onModelsChange(composeSlotModel(models, slot, entry.provider, entry.id, thinking));
|
||||
setOpenMenu(null);
|
||||
@@ -594,7 +593,7 @@ export function AgentSection({
|
||||
testid={`slot-thinking-menu-${slot}`}
|
||||
menuClass="flex w-44 flex-col gap-0.5 rounded-xl border border-border bg-card p-2 text-xs shadow-xl"
|
||||
>
|
||||
{defaultRow(
|
||||
{noneRow(
|
||||
thinking === "",
|
||||
() => {
|
||||
onModelsChange(composeSlotModel(models, slot, provider, modelId, ""));
|
||||
|
||||
Reference in New Issue
Block a user