Fix wave: scroll-lock without layout shift; every models write mirrors the stored record

Fix 1 (owner: "lock must not shift"): the settings dialog's scroll-lock no
longer toggles the content column to overflow-hidden (which hid the
scrollbar and shifted everything right). The column KEEPS overflow-y-auto —
scrollbar and widths stay exactly as they are — and while any MenuShell is
open, non-passive wheel + touchmove listeners on the column preventDefault
so it cannot scroll (owner-ruled mechanism: the scrollbar may stay,
scrolling must not happen, nothing may move). Native listeners via ref —
React attaches synthetic wheel/touch passively at the root.

Fix 2 (owner repro: xhigh pin survives a GLM swap on screen until reload):
every models-writing surface now mirrors SERVER TRUTH, never the request
body. The PUT already returns the full stored record with clamps applied —
settings-dialog write() and the composer's save() now setRecord/setModels
from the response, so a swap that rides an unsupported level shows the
clamped value in the same interaction: the row trigger, the open menu's
level list + checkmark, and the composer pill all read the stored record.

Live-verified the owner's exact repro: deepseek pinned extra high → GLM
swap via the slot menu → trigger/menu/pill showed the stored clamped level
immediately; and the lock cycle: scroll 300 → blocked while open → 300
after release, zero positional shift at engage.
This commit is contained in:
amosblomqvist
2026-09-22 22:45:11 +03:00
parent 5e92d0e73e
commit f80b1a9d2c
2 changed files with 44 additions and 5 deletions
+8 -2
View File
@@ -1,7 +1,7 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import type { ModelCatalogEntry, ModelCatalogResponse, ModelsSettings } from "@/src/core";
import type { ModelCatalogEntry, ModelCatalogResponse, ModelsSettings, UserSettings } from "@/src/core";
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
import { prettyModel } from "@/components/usage-format";
import { ModelMenuList, ThinkingMenuList } from "@/components/settings/model-menu";
@@ -172,7 +172,13 @@ export function ModelControl() {
setError(body.error ?? `settings rejected (${res.status})`);
return;
}
setModels(next);
// Server truth, never the request body (fix wave): a pick
// that rides an unsupported level lands CLAMPED in the record
// — the pill must show the stored value in this interaction.
const stored = (
(await res.json().catch(() => null)) as { settings?: UserSettings } | null
)?.settings?.models;
setModels(stored ?? next);
setOpen(null);
notifySettingsSaved("models");
} catch {