fix: the stats bar's controls were dead — pointer-events + button affordance

Live-driving the real browser caught it (the user's report: "they don't
work as buttons"): the stats row sits OUTSIDE the composer shell's
pointer-events-auto div, inside the sticky scroll wrapper's
pointer-events-none — every control on the row was unclickable, including
the pre-existing stats-detail popover button. The row now re-enables
pointer-events itself.

Also, per the user's sizing note: bar text 10px → 11px with a touch more
breathing room, and the model + thinking controls gain quiet pill
affordance (rounded-full border, card bg, ▾ chevron) — still muted by
design, but visibly buttons.

Verified live end-to-end in the browser: click opens the model menu ABOVE
(366 rows, search autofocused), "qwen flash" narrows to 5, picking
qwen3.8-flash persists + flips the label, the thinking menu shows exactly
that model's supported levels (Off/Minimal/Low/Medium/High), Medium
persists on the record via PUT, labels update. Test session settings
restored after the drive. Gates 350/350.
This commit is contained in:
amosblomqvist
2026-09-12 06:11:23 +03:00
parent de0c286311
commit 953b22be57
3 changed files with 16 additions and 7 deletions
+6 -4
View File
@@ -157,9 +157,10 @@ export function ModelControl({
}} }}
aria-expanded={open === "model"} aria-expanded={open === "model"}
title="Conversation model — applies next turn" title="Conversation model — applies next turn"
className="flex items-center gap-1 transition-colors hover:text-foreground" className="flex items-center gap-1.5 rounded-full border border-border/60 bg-card/70 px-2.5 py-1 transition-colors hover:border-border hover:bg-muted"
> >
<span>{effectiveId ? prettyModel(effectiveId) : "—"}</span> <span className="text-card-foreground">{effectiveId ? prettyModel(effectiveId) : "—"}</span>
<span aria-hidden className="text-[9px] text-muted-foreground">▾</span>
</button> </button>
<span aria-hidden>·</span> <span aria-hidden>·</span>
<button <button
@@ -171,11 +172,12 @@ export function ModelControl({
}} }}
aria-expanded={open === "thinking"} aria-expanded={open === "thinking"}
title="Thinking level — applies next turn" title="Thinking level — applies next turn"
className="flex items-center gap-1 transition-colors hover:text-foreground" className="flex items-center gap-1.5 rounded-full border border-border/60 bg-card/70 px-2.5 py-1 transition-colors hover:border-border hover:bg-muted"
> >
<span> <span className="text-card-foreground">
{effectiveLevel === undefined ? "Thinking —" : `Thinking: ${LEVEL_LABEL[effectiveLevel] ?? effectiveLevel}`} {effectiveLevel === undefined ? "Thinking —" : `Thinking: ${LEVEL_LABEL[effectiveLevel] ?? effectiveLevel}`}
</span> </span>
<span aria-hidden className="text-[9px] text-muted-foreground">▾</span>
</button> </button>
{open === "model" && ( {open === "model" && (
+5 -1
View File
@@ -44,7 +44,11 @@ export function StatsBar({
return ( return (
<div <div
data-testid="stats-bar" data-testid="stats-bar"
className="relative flex items-center justify-between gap-3 px-3 pt-1 text-[10px] text-muted-foreground" // pointer-events-auto: the sticky composer wrapper is pointer-events-none
// (so its transparent gradient area never blocks scrolling) — this row
// must re-enable events or every control on it is dead (the model-
// control wave caught this live: buttons rendered, clicks never landed).
className="pointer-events-auto relative flex items-center justify-between gap-3 px-3 pt-1.5 text-[11px] text-muted-foreground"
> >
<button <button
type="button" type="button"
+5 -2
View File
@@ -35,8 +35,11 @@ describe("stats bar", () => {
assert.match(html, /data-testid="stats-context">—</); assert.match(html, /data-testid="stats-context">—</);
assert.match(html, /data-testid="stats-cost">—</); assert.match(html, /data-testid="stats-cost">—</);
// no turns, no settings, catalog not loaded → both control labels "—" // no turns, no settings, catalog not loaded → both control labels "—"
assert.match(html, /data-testid="stats-model"[^>]*><span>—<\/span>/); assert.match(html, /data-testid="stats-model"[^>]*>\s*<span[^>]*>—<\/span>/);
assert.match(html, /Thinking —</); assert.match(html, /Thinking —</);
// the control reads as a button: pill border + dropdown chevron
assert.match(html, /data-testid="stats-model"[^>]*rounded-full[^>]*>/);
assert.match(html, /▾/);
}); });
it("folds context + cost from the turns log (core/prices, no local math)", () => { it("folds context + cost from the turns log (core/prices, no local math)", () => {
@@ -51,7 +54,7 @@ describe("stats bar", () => {
assert.match(html, /data-testid="stats-cost">\$0\.0020</); assert.match(html, /data-testid="stats-cost">\$0\.0020</);
// model button label falls back to the last turn's actual model // model button label falls back to the last turn's actual model
// (the catalog + settings are what drive it once loaded) // (the catalog + settings are what drive it once loaded)
assert.match(html, /data-testid="stats-model"[^>]*><span>Glm 5\.3 Flash<\/span>/); assert.match(html, /data-testid="stats-model"[^>]*>\s*<span[^>]*>Glm 5\.3 Flash<\/span>/);
}); });
it("shows — for cost when every turn is unpriceable, never a fake $0", () => { it("shows — for cost when every turn is unpriceable, never a fake $0", () => {