From ec133b6f70a7f70fee56a8ba95bf69bb11a038dd Mon Sep 17 00:00:00 2001 From: Pixel Perfect Date: Sun, 25 Oct 2026 23:27:49 -0700 Subject: [PATCH 1/6] fix(web): match effort gauge position to reasoning level --- apps/web/src/components/chat/TraitsPicker.tsx | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index f6d099dfbb059..3952d023801ab 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -24,7 +24,7 @@ import { normalizeModelSlug, } from "@t3tools/shared/model"; import { memo } from "react"; -import { BrainIcon, GaugeIcon } from "lucide-react"; +import { BrainIcon, Icon } from "lucide-react"; import { Menu, MenuGroup, @@ -115,6 +115,32 @@ function DefaultBadge() { ); } +function EffortGaugeIcon({ + descriptor, + value, + size, +}: { + descriptor: Extract | null; + value: string | null; + size: ComposerControlSize; +}) { + const levels = descriptor?.options.filter((option) => option.id !== "default") ?? []; + const index = levels.findIndex((option) => option.id === value); + // Spread each model's levels clockwise from 8 o'clock to 4:30. + const angle = index >= 0 && levels.length > 1 ? -120 + (index / (levels.length - 1)) * 255 : 45; + + return ( + + ); +} + function getSelectedTraits( provider: ProviderDriverKind, models: ReadonlyArray, @@ -531,7 +557,7 @@ export const TraitsPicker = memo(function TraitsPicker({ }) { const composerFloatingLayerProps = useComposerMenuProps(); const [isMenuOpen, setIsMenuOpen] = useComposerMenuState(hidden || disabled); - const { descriptors, primarySelectDescriptor, ultrathinkPromptControlled } = + const { descriptors, selectDescriptors, primarySelectDescriptor, ultrathinkPromptControlled } = getTraitsSectionVisibility({ provider, models, @@ -555,14 +581,23 @@ export const TraitsPicker = memo(function TraitsPicker({ return null; } + const modelSelection = + instanceId && model ? { instanceId, model, options: modelOptions ?? [] } : null; const { label: triggerLabel } = buildTraitsTriggerDisplay({ provider, descriptors, primarySelectDescriptorId: primarySelectDescriptor?.id ?? null, ultrathinkPromptControlled, - modelSelection: instanceId && model ? { instanceId, model, options: modelOptions ?? [] } : null, + modelSelection, reportedModelSelection, }); + const reasoningDescriptor = + selectDescriptors.find((descriptor) => REASONING_SELECT_DESCRIPTOR_IDS.has(descriptor.id)) ?? + null; + const effort = + ultrathinkPromptControlled && reasoningDescriptor?.id === primarySelectDescriptor?.id + ? "ultrathink" + : getDescriptorStringValue(reasoningDescriptor, modelSelection, reportedModelSelection); const isCodexStyle = provider === "codex"; return ( @@ -601,7 +636,7 @@ export const TraitsPicker = memo(function TraitsPicker({ )} > - + {triggerLabel} @@ -610,7 +645,7 @@ export const TraitsPicker = memo(function TraitsPicker({ ) : ( <> - + {triggerLabel} From b962aa04de99ff07a473bcf369957e9c3471cad4 Mon Sep 17 00:00:00 2001 From: Pixel Perfect Date: Sun, 25 Oct 2026 23:40:20 -0700 Subject: [PATCH 2/6] fix(web): preserve Lucide effort gauge geometry --- apps/web/src/components/chat/TraitsPicker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index 3952d023801ab..dfcfa76696d22 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -131,12 +131,12 @@ function EffortGaugeIcon({ return ( ); } From a9e7817c7305eb260f2643d3344262d397d0ba96 Mon Sep 17 00:00:00 2001 From: Pixel Perfect Date: Mon, 26 Oct 2026 00:16:00 -0700 Subject: [PATCH 3/6] fix(web): reflect boolean thinking in effort gauge --- apps/web/src/components/chat/TraitsPicker.tsx | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index dfcfa76696d22..f59eb512e27b4 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -120,14 +120,24 @@ function EffortGaugeIcon({ value, size, }: { - descriptor: Extract | null; - value: string | null; + descriptor: ProviderOptionDescriptor | null; + value: string | boolean | null; size: ComposerControlSize; }) { - const levels = descriptor?.options.filter((option) => option.id !== "default") ?? []; + const levels = + descriptor?.type === "select" + ? descriptor.options.filter((option) => option.id !== "default") + : []; const index = levels.findIndex((option) => option.id === value); - // Spread each model's levels clockwise from 8 o'clock to 4:30. - const angle = index >= 0 && levels.length > 1 ? -120 + (index / (levels.length - 1)) * 255 : 45; + // Select levels span 8–4:30; boolean On matches Extra High in the six-level gauge. + const angle = + typeof value === "boolean" + ? value + ? 33 + : -120 + : index >= 0 && levels.length > 1 + ? -120 + (index / (levels.length - 1)) * 255 + : 45; return ( REASONING_SELECT_DESCRIPTOR_IDS.has(descriptor.id)) ?? + descriptors.find( + (descriptor) => + descriptor.type === "boolean" && REASONING_SELECT_DESCRIPTOR_IDS.has(descriptor.id), + ) ?? null; const effort = ultrathinkPromptControlled && reasoningDescriptor?.id === primarySelectDescriptor?.id ? "ultrathink" - : getDescriptorStringValue(reasoningDescriptor, modelSelection, reportedModelSelection); + : reasoningDescriptor?.type === "boolean" + ? (reasoningDescriptor.currentValue ?? null) + : getDescriptorStringValue(reasoningDescriptor, modelSelection, reportedModelSelection); const isCodexStyle = provider === "codex"; return ( From 18a7d62eb73c5ebd7bfab11614961da092799f45 Mon Sep 17 00:00:00 2001 From: Pixel Perfect Date: Mon, 26 Oct 2026 00:17:54 -0700 Subject: [PATCH 4/6] fix(web): keep effort needle within gauge arc --- apps/web/src/components/chat/TraitsPicker.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index f59eb512e27b4..971b926e24971 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -129,14 +129,14 @@ function EffortGaugeIcon({ ? descriptor.options.filter((option) => option.id !== "default") : []; const index = levels.findIndex((option) => option.id === value); - // Select levels span 8–4:30; boolean On matches Extra High in the six-level gauge. + // Select levels span the arc's 8–4 endpoints; boolean On matches six-level Extra High. const angle = typeof value === "boolean" ? value - ? 33 + ? 24 : -120 : index >= 0 && levels.length > 1 - ? -120 + (index / (levels.length - 1)) * 255 + ? -120 + (index / (levels.length - 1)) * 240 : 45; return ( From a343178c56684b1f83f47c08541c0a4c1fb055ee Mon Sep 17 00:00:00 2001 From: Pixel Perfect Date: Mon, 26 Oct 2026 13:43:03 -0700 Subject: [PATCH 5/6] fix(web): order effort gauge levels by meaning --- apps/web/src/components/chat/TraitsPicker.tsx | 56 ++++++++++------ .../chat/composerProviderState.test.tsx | 67 ++++++++++++++++++- 2 files changed, 103 insertions(+), 20 deletions(-) diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index 971b926e24971..f38660ba0295c 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -115,6 +115,39 @@ function DefaultBadge() { ); } +const EFFORT_GAUGE_RANKS = new Map([ + ["none", 0], + ["off", 0], + ["minimal", 1], + ["low", 2], + ["medium", 3], + ["high", 4], + ["xhigh", 5], + ["ultracode", 5], + ["max", 6], + ["ultra", 7], +]); + +export function getEffortGaugeAngle( + descriptor: ProviderOptionDescriptor | null, + value: string | boolean | null, +) { + // Boolean On keeps the six-level Extra High position. + if (typeof value === "boolean") return value ? 24 : -120; + if (descriptor?.type !== "select" || value === null) return 45; + + const levels = [ + ...new Set( + descriptor.options + .filter((option) => !descriptor.promptInjectedValues?.includes(option.id)) + .map((option) => EFFORT_GAUGE_RANKS.get(option.id)) + .filter((rank) => rank !== undefined), + ), + ].toSorted((left, right) => left - right); + const index = levels.indexOf(EFFORT_GAUGE_RANKS.get(value) ?? -1); + return index >= 0 && levels.length > 1 ? -120 + (index / (levels.length - 1)) * 240 : 45; +} + function EffortGaugeIcon({ descriptor, value, @@ -124,20 +157,7 @@ function EffortGaugeIcon({ value: string | boolean | null; size: ComposerControlSize; }) { - const levels = - descriptor?.type === "select" - ? descriptor.options.filter((option) => option.id !== "default") - : []; - const index = levels.findIndex((option) => option.id === value); - // Select levels span the arc's 8–4 endpoints; boolean On matches six-level Extra High. - const angle = - typeof value === "boolean" - ? value - ? 24 - : -120 - : index >= 0 && levels.length > 1 - ? -120 + (index / (levels.length - 1)) * 240 - : 45; + const angle = getEffortGaugeAngle(descriptor, value); return ( ({ id, value })); } +describe("effort gauge", () => { + it("orders effort by meaning when a provider lists levels backwards", () => { + const descriptor = selectDescriptor( + "reasoningEffort", + ["xhigh", "high", "medium", "low"].map((id) => ({ id, label: id })), + ); + + const angles = ["low", "medium", "high", "xhigh"].map((value) => + getEffortGaugeAngle(descriptor, value), + ); + + for (let index = 1; index < angles.length; index++) { + expect(angles[index]).toBeGreaterThan(angles[index - 1]!); + } + }); + + it("spreads a sparse model's supported levels across the full arc", () => { + const descriptor = selectDescriptor( + "variant", + ["max", "high"].map((id) => ({ id, label: id })), + ); + + expect(getEffortGaugeAngle(descriptor, "high")).toBe(-120); + expect(getEffortGaugeAngle(descriptor, "max")).toBe(120); + }); + + it("gives Ultracode the same position as Xhigh without adding another level", () => { + const descriptor = selectDescriptor( + "effort", + ["low", "medium", "high", "xhigh", "ultracode", "max"].map((id) => ({ id, label: id })), + ); + const nativeOnly = { + ...descriptor, + options: descriptor.options.filter((option) => option.id !== "ultracode"), + }; + + expect(getEffortGaugeAngle(descriptor, "ultracode")).toBe( + getEffortGaugeAngle(descriptor, "xhigh"), + ); + expect(getEffortGaugeAngle(descriptor, "high")).toBe(getEffortGaugeAngle(nativeOnly, "high")); + }); + + it("does not let a prompt-only Ultrathink choice extend the effort scale", () => { + const descriptor = selectDescriptor( + "effort", + ["low", "high", "ultrathink"].map((id) => ({ id, label: id })), + ["ultrathink"], + ); + + expect(getEffortGaugeAngle(descriptor, "high")).toBe(120); + expect(getEffortGaugeAngle(descriptor, "ultrathink")).toBe(45); + }); + + it("keeps unknown variants static and out of the effort scale", () => { + const descriptor = selectDescriptor( + "variant", + ["default", "low", "high", "custom"].map((id) => ({ id, label: id })), + ); + + expect(getEffortGaugeAngle(descriptor, "custom")).toBe(45); + expect(getEffortGaugeAngle(descriptor, "default")).toBe(45); + expect(getEffortGaugeAngle(descriptor, "high")).toBe(120); + }); +}); + describe("getComposerProviderState", () => { it("uses descriptor defaults for display without dispatching them as overrides", () => { const state = getComposerProviderState({ From a02920496fdad598326a9f6bde59ce361a0770be Mon Sep 17 00:00:00 2001 From: Pixel Perfect Date: Mon, 26 Oct 2026 13:49:48 -0700 Subject: [PATCH 6/6] docs(web): explain native effort gauge semantics --- apps/web/src/components/chat/TraitsPicker.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index f38660ba0295c..7cc4fab81ce11 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -628,6 +628,7 @@ export const TraitsPicker = memo(function TraitsPicker({ descriptor.type === "boolean" && REASONING_SELECT_DESCRIPTOR_IDS.has(descriptor.id), ) ?? null; + // Ultrathink only changes the prompt; the gauge measures native effort. const effort = reasoningDescriptor?.type === "boolean" ? (reasoningDescriptor.currentValue ?? null)