From 704662422ccc04dafca22a84fcf25b4ea2157697 Mon Sep 17 00:00:00 2001 From: Pixel Perfect Date: Sat, 25 Jul 2026 00:05:11 -0700 Subject: [PATCH] fix(web): show Codex fast mode as a bolt --- .../src/components/chat/TraitsPicker.test.ts | 61 ++++++++++++++++--- apps/web/src/components/chat/TraitsPicker.tsx | 42 ++++++++----- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/apps/web/src/components/chat/TraitsPicker.test.ts b/apps/web/src/components/chat/TraitsPicker.test.ts index 93157490090..b14bb63ca13 100644 --- a/apps/web/src/components/chat/TraitsPicker.test.ts +++ b/apps/web/src/components/chat/TraitsPicker.test.ts @@ -16,6 +16,22 @@ function fastModeDescriptor( return { id: "fastMode", label: "Fast Mode", type: "boolean", currentValue }; } +function serviceTierDescriptor( + currentValue: "default" | "priority" | "flex", +): Extract { + return { + id: "serviceTier", + label: "Service Tier", + type: "select", + options: [ + { id: "default", label: "Standard", isDefault: true }, + { id: "priority", label: "Fast" }, + { id: "flex", label: "Flex" }, + ], + currentValue, + }; +} + const EFFORT = selectDescriptor( "effort", [ @@ -33,30 +49,58 @@ const CONTEXT_WINDOW = selectDescriptor( "1m", ); -function display(descriptors: ReadonlyArray, fastModeEnabled: boolean) { +function display(descriptors: ReadonlyArray) { return buildTraitsTriggerDisplay({ descriptors, primarySelectDescriptorId: "effort", ultrathinkPromptControlled: false, - fastModeEnabled, }); } describe("buildTraitsTriggerDisplay", () => { it("omits fast mode from the label entirely when it is off", () => { - expect(display([EFFORT, fastModeDescriptor(false), CONTEXT_WINDOW], false)).toEqual({ + expect(display([EFFORT, fastModeDescriptor(false), CONTEXT_WINDOW])).toEqual({ label: "High · 1M", showFastModeIcon: false, }); }); it("shows the bolt instead of a text label when fast mode is on", () => { - expect(display([EFFORT, fastModeDescriptor(true), CONTEXT_WINDOW], true)).toEqual({ + expect(display([EFFORT, fastModeDescriptor(true), CONTEXT_WINDOW])).toEqual({ label: "High · 1M", showFastModeIcon: true, }); }); + it("treats Codex standard and fast service tiers as fast mode states", () => { + expect(display([EFFORT, serviceTierDescriptor("default")])).toEqual({ + label: "High", + showFastModeIcon: false, + }); + expect(display([EFFORT, serviceTierDescriptor("priority")])).toEqual({ + label: "High", + showFastModeIcon: true, + }); + }); + + it("keeps other Codex service tiers in the label", () => { + expect(display([EFFORT, serviceTierDescriptor("flex")])).toEqual({ + label: "High · Flex", + showFastModeIcon: false, + }); + }); + + it("keeps the Codex service tier readable when it is the only trait", () => { + expect(display([serviceTierDescriptor("default")])).toEqual({ + label: "Standard", + showFastModeIcon: false, + }); + expect(display([serviceTierDescriptor("priority")])).toEqual({ + label: "Fast", + showFastModeIcon: false, + }); + }); + it("keeps non-fastMode booleans as text labels", () => { const thinking: Extract = { id: "thinking", @@ -64,18 +108,18 @@ describe("buildTraitsTriggerDisplay", () => { type: "boolean", currentValue: true, }; - expect(display([EFFORT, thinking], false)).toEqual({ + expect(display([EFFORT, thinking])).toEqual({ label: "High · Thinking On", showFastModeIcon: false, }); }); it("falls back to a text label when fast mode is the only trait", () => { - expect(display([fastModeDescriptor(true)], true)).toEqual({ + expect(display([fastModeDescriptor(true)])).toEqual({ label: "Fast", showFastModeIcon: false, }); - expect(display([fastModeDescriptor(false)], false)).toEqual({ + expect(display([fastModeDescriptor(false)])).toEqual({ label: "Normal", showFastModeIcon: false, }); @@ -94,7 +138,7 @@ describe("buildTraitsTriggerDisplay", () => { { id: "high", label: "High" }, ], }; - expect(display([unresolved], false)).toEqual({ label: "", showFastModeIcon: false }); + expect(display([unresolved])).toEqual({ label: "", showFastModeIcon: false }); }); it("still renders the prompt-controlled ultrathink label alongside the bolt", () => { @@ -103,7 +147,6 @@ describe("buildTraitsTriggerDisplay", () => { descriptors: [EFFORT, fastModeDescriptor(true)], primarySelectDescriptorId: "effort", ultrathinkPromptControlled: true, - fastModeEnabled: true, }), ).toEqual({ label: "Ultrathink", showFastModeIcon: true }); }); diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index d48fe484eb6..ad73062d3a5 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -133,8 +133,6 @@ function getSelectedTraits( : getDescriptorStringValue(primarySelectDescriptor)) ?? null; const thinkingEnabled = typeof thinkingDescriptor?.currentValue === "boolean" ? thinkingDescriptor.currentValue : null; - const fastModeEnabled = - typeof fastModeDescriptor?.currentValue === "boolean" ? fastModeDescriptor.currentValue : false; const contextWindow = getDescriptorStringValue(contextWindowDescriptor); const selectedAgent = getDescriptorStringValue(agentDescriptor); const selectedAgentLabel = agentDescriptor @@ -153,7 +151,6 @@ function getSelectedTraits( thinkingDescriptor, effort, thinkingEnabled, - fastModeEnabled, contextWindow, ultrathinkPromptControlled, ultrathinkInBodyText, @@ -381,24 +378,40 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ /** * Build the traits trigger's text label plus whether the fast-mode bolt should - * render. Fast mode is a lightning bolt when on and nothing at all when off — - * "Normal" is the near-universal case and isn't worth the horizontal space. The - * one exception is when fast mode is the only trait, where a bare bolt (or bare - * chevron) would leave the trigger unreadable. + * render. Claude and Cursor expose fast mode as a boolean, while Codex exposes + * it through the Standard/Fast service tiers. In either form, fast mode is a + * lightning bolt when on and nothing at all when off. The one exception is when + * fast mode is the only trait, where a bare bolt (or bare chevron) would leave + * the trigger unreadable. */ export function buildTraitsTriggerDisplay(input: { descriptors: ReadonlyArray; primarySelectDescriptorId: string | null; ultrathinkPromptControlled: boolean; - fastModeEnabled: boolean; }): { label: string; showFastModeIcon: boolean } { - let hasFastMode = false; + let fastModeFallbackLabel: string | null = null; + let fastModeEnabled = false; const labels: Array = []; for (const descriptor of input.descriptors) { if (descriptor.id === "fastMode" && descriptor.type === "boolean") { - hasFastMode = true; + fastModeEnabled = descriptor.currentValue === true; + fastModeFallbackLabel = fastModeEnabled ? "Fast" : "Normal"; continue; } + if (descriptor.id === "serviceTier" && descriptor.type === "select") { + const currentOption = + descriptor.options.find((option) => option.id === descriptor.currentValue) ?? + descriptor.options.find((option) => option.isDefault); + const isFastTier = + currentOption?.id === "fast" || currentOption?.label.toLowerCase() === "fast"; + const isStandardTier = + currentOption?.id === "default" || currentOption?.label.toLowerCase() === "standard"; + if (isFastTier || isStandardTier) { + fastModeEnabled = isFastTier; + fastModeFallbackLabel = currentOption.label; + continue; + } + } const label = input.ultrathinkPromptControlled && descriptor.id === input.primarySelectDescriptorId ? "Ultrathink" @@ -413,10 +426,10 @@ export function buildTraitsTriggerDisplay(input: { // Only fall back to text when fast mode is genuinely the sole trait. Keying // off an empty label list alone would also catch descriptors that resolved to // no label at all, printing a bogus "Normal" for a model without fast mode. - if (labels.length === 0 && hasFastMode) { - return { label: input.fastModeEnabled ? "Fast" : "Normal", showFastModeIcon: false }; + if (labels.length === 0 && fastModeFallbackLabel !== null) { + return { label: fastModeFallbackLabel, showFastModeIcon: false }; } - return { label: labels.join(" · "), showFastModeIcon: input.fastModeEnabled }; + return { label: labels.join(" · "), showFastModeIcon: fastModeEnabled }; } export const TraitsPicker = memo(function TraitsPicker({ @@ -433,7 +446,7 @@ export const TraitsPicker = memo(function TraitsPicker({ ...persistence }: TraitsMenuContentProps & TraitsPersistence) { const [isMenuOpen, setIsMenuOpen] = useState(false); - const { descriptors, primarySelectDescriptor, ultrathinkPromptControlled, fastModeEnabled } = + const { descriptors, primarySelectDescriptor, ultrathinkPromptControlled } = getTraitsSectionVisibility({ provider, models, @@ -459,7 +472,6 @@ export const TraitsPicker = memo(function TraitsPicker({ descriptors, primarySelectDescriptorId: primarySelectDescriptor?.id ?? null, ultrathinkPromptControlled, - fastModeEnabled, }); const fastModeIcon = showFastModeIcon ? ( <>