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:
@@ -59,7 +59,7 @@ describe("SettingsDialog (the designed surface, SSR)", () => {
|
||||
});
|
||||
|
||||
describe("AgentSection (thin SSR)", () => {
|
||||
it("renders the three model SLOT rows (slots-only, owner ruling 2026-09-20), Default-triggered dropdowns", () => {
|
||||
it("renders the three model SLOT rows (slots-only, fix wave: unpicked reads —, no Default option)", () => {
|
||||
const html = renderToStaticMarkup(<AgentSection agent={undefined} models={undefined} onSectionChange={() => {}} onModelsChange={() => {}} />);
|
||||
for (const slot of ["strong", "cheap", "vision"]) {
|
||||
assert.ok(html.includes(`data-testid="slot-row-${slot}"`), slot);
|
||||
@@ -70,7 +70,12 @@ describe("AgentSection (thin SSR)", () => {
|
||||
// the roster hint rides every row
|
||||
assert.ok(html.includes("Prober + Teacher") || slot !== "strong", "strong roster");
|
||||
}
|
||||
assert.equal((html.match(/>Default<\/span>/g) ?? []).length, 6, "three slots × two Default triggers");
|
||||
// Fix wave: no Default anywhere — an unpicked slot reads "—"
|
||||
// (the owner's no-factory-fallback language), and no menu
|
||||
// carries a Default row (menu-default testid is retired).
|
||||
assert.equal((html.match(/>Default<\/span>/g) ?? []).length, 0, "no Default trigger");
|
||||
assert.ok(!html.includes('data-testid="menu-default"'), "no Default row");
|
||||
assert.equal((html.match(/>—<\/span>/g) ?? []).length, 6, "three slots × two — triggers");
|
||||
// the per-role rows are GONE (slots-only)
|
||||
for (const role of ["researcher", "demoBuilder", "visualMaker"]) {
|
||||
assert.ok(!html.includes(`data-testid="role-row-${role}"`), `${role} retired`);
|
||||
|
||||
@@ -24,12 +24,10 @@ function turn(over: Partial<TurnUsage> = {}): TurnUsage {
|
||||
};
|
||||
}
|
||||
|
||||
const SETTINGS_STUB = (_s: ChatSettings) => {};
|
||||
|
||||
describe("stats bar", () => {
|
||||
it("renders — everywhere when the record has no turns yet (doctrine)", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<StatsBar turns={[]} chatId="chat_x" onSettingsSaved={SETTINGS_STUB} />,
|
||||
<StatsBar turns={[]} />,
|
||||
);
|
||||
assert.match(html, /data-testid="stats-bar"/);
|
||||
assert.match(html, /data-testid="stats-context">—</);
|
||||
@@ -47,7 +45,7 @@ describe("stats bar", () => {
|
||||
|
||||
it("folds context + cost from the turns log (core/prices, no local math)", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<StatsBar turns={[turn(), turn()]} chatId="chat_x" onSettingsSaved={SETTINGS_STUB} />,
|
||||
<StatsBar turns={[turn(), turn()]} />,
|
||||
);
|
||||
// last turn's last call: 10k in + 1k out = 11k; pi catalog limit
|
||||
// 1_048_576 → "1M" (tables regenerated from pi-ai's catalog)
|
||||
@@ -55,14 +53,15 @@ describe("stats bar", () => {
|
||||
// pi catalog price 0.075/0.25 per 1M: each turn 10k×0.075 + 1k×0.25
|
||||
// = $0.001 → two = $0.0020
|
||||
assert.match(html, /data-testid="stats-cost">\$0\.0020</);
|
||||
// model button label falls back to the last turn's actual model
|
||||
// (the catalog + settings are what drive it once loaded)
|
||||
assert.match(html, /data-testid="stats-model"[^>]*>\s*<span[^>]*>Glm 5\.3 Flash<\/span>/);
|
||||
// model button label: SSR (record fetch not run yet) reads "—" —
|
||||
// the strong slot arrives client-side via GET /api/settings; the
|
||||
// turn log below still folds the ACTUAL models billed
|
||||
assert.match(html, /data-testid="stats-model"[^>]*>\s*<span[^>]*>—<\/span>/);
|
||||
});
|
||||
|
||||
it("shows — for cost when every turn is unpriceable, never a fake $0", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<StatsBar turns={[turn({ model: "nope/unknown" })]} chatId="chat_x" onSettingsSaved={SETTINGS_STUB} />,
|
||||
<StatsBar turns={[turn({ model: "nope/unknown" })]} />,
|
||||
);
|
||||
assert.match(html, /data-testid="stats-cost">—</);
|
||||
// unknown model also kills the context limit
|
||||
|
||||
Reference in New Issue
Block a user