From 92bd4f670571d84dcf935f8e3aecc9c423d45a15 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 30 Sep 2026 12:49:57 -0700 Subject: [PATCH 1/2] fix(web): show double bolts for Codex Ultrafast --- apps/web/src/components/Icons.tsx | 7 +++ .../src/components/chat/ComposerControl.tsx | 6 +-- .../src/components/chat/TraitsPicker.test.ts | 54 ++++++++++++++----- apps/web/src/components/chat/TraitsPicker.tsx | 49 +++++++++-------- 4 files changed, 79 insertions(+), 37 deletions(-) diff --git a/apps/web/src/components/Icons.tsx b/apps/web/src/components/Icons.tsx index b511f1f1b922..246eb082f28a 100644 --- a/apps/web/src/components/Icons.tsx +++ b/apps/web/src/components/Icons.tsx @@ -2,6 +2,13 @@ import React, { type SVGProps, useId } from "react"; import { cn } from "~/lib/utils"; export type Icon = React.FC>; +export const UltrafastIcon: Icon = (props) => ( + + + + +); + export const FinderIcon: Icon = (props) => ( diff --git a/apps/web/src/components/chat/ComposerControl.tsx b/apps/web/src/components/chat/ComposerControl.tsx index e079106d5ee3..ce6d03987934 100644 --- a/apps/web/src/components/chat/ComposerControl.tsx +++ b/apps/web/src/components/chat/ComposerControl.tsx @@ -1,8 +1,8 @@ import { mergeProps } from "@base-ui/react/merge-props"; import { Select as SelectPrimitive } from "@base-ui/react/select"; import { useRender } from "@base-ui/react/use-render"; -import type { ComponentProps } from "react"; -import { ChevronDownIcon, type LucideIcon } from "lucide-react"; +import type { ComponentProps, ComponentType, SVGProps } from "react"; +import { ChevronDownIcon } from "lucide-react"; import { cn } from "~/lib/utils"; import { Separator } from "../ui/separator"; @@ -52,7 +52,7 @@ export function ComposerControlIcon({ opticalSize = "default", size = "sm", }: { - icon: LucideIcon; + icon: ComponentType>; className?: string | undefined; opticalSize?: "default" | "large"; size?: ComposerControlSize; diff --git a/apps/web/src/components/chat/TraitsPicker.test.ts b/apps/web/src/components/chat/TraitsPicker.test.ts index dbe97cdd54e0..0b0769bb2729 100644 --- a/apps/web/src/components/chat/TraitsPicker.test.ts +++ b/apps/web/src/components/chat/TraitsPicker.test.ts @@ -17,7 +17,7 @@ function fastModeDescriptor( } function serviceTierDescriptor( - currentValue: "default" | "priority" | "flex", + currentValue: "default" | "priority" | "ultrafast" | "flex", ): Extract { return { id: "serviceTier", @@ -26,6 +26,7 @@ function serviceTierDescriptor( options: [ { id: "default", label: "Standard", isDefault: true }, { id: "priority", label: "Fast" }, + { id: "ultrafast", label: "Ultrafast" }, { id: "flex", label: "Flex" }, ], currentValue, @@ -64,43 +65,70 @@ describe("buildTraitsTriggerDisplay", () => { it("omits fast mode from the label entirely when it is off", () => { expect(display([EFFORT, fastModeDescriptor(false), CONTEXT_WINDOW])).toEqual({ label: "High · 1M", - showFastModeIcon: false, + speedIcon: null, }); }); it("shows the bolt instead of a text label when fast mode is on", () => { expect(display([EFFORT, fastModeDescriptor(true), CONTEXT_WINDOW])).toEqual({ label: "High · 1M", - showFastModeIcon: true, + speedIcon: "fast", }); }); it("treats Codex standard and fast service tiers as fast mode states", () => { expect(display([EFFORT, serviceTierDescriptor("default")])).toEqual({ label: "High", - showFastModeIcon: false, + speedIcon: null, }); expect(display([EFFORT, serviceTierDescriptor("priority")])).toEqual({ label: "High", - showFastModeIcon: true, + speedIcon: "fast", + }); + }); + + it("uses a distinct double bolt for Codex Ultrafast", () => { + expect(display([EFFORT, serviceTierDescriptor("ultrafast")])).toEqual({ + label: "High", + speedIcon: "ultrafast", + }); + }); + + it("uses Ultrafast without requiring a Fast tier", () => { + const descriptor = serviceTierDescriptor("ultrafast"); + expect( + display([ + EFFORT, + { ...descriptor, options: descriptor.options.filter(({ id }) => id !== "priority") }, + ]), + ).toEqual({ + label: "High", + speedIcon: "ultrafast", }); }); it("keeps other Codex service tiers in the label", () => { expect(display([EFFORT, serviceTierDescriptor("flex")])).toEqual({ label: "High · Flex", - showFastModeIcon: false, + speedIcon: null, }); }); it("keeps the Codex service tier readable when it is the only trait", () => { expect(display([serviceTierDescriptor("default")])).toEqual({ label: "Standard", - showFastModeIcon: false, + speedIcon: null, }); expect(display([serviceTierDescriptor("priority")])).toEqual({ label: "Fast", - showFastModeIcon: false, + speedIcon: null, + }); + }); + + it("keeps Ultrafast readable when it is the only trait", () => { + expect(display([serviceTierDescriptor("ultrafast")])).toEqual({ + label: "Ultrafast", + speedIcon: null, }); }); @@ -113,18 +141,18 @@ describe("buildTraitsTriggerDisplay", () => { }; expect(display([EFFORT, thinking])).toEqual({ label: "High · Thinking On", - showFastModeIcon: false, + speedIcon: null, }); }); it("falls back to a text label when fast mode is the only trait", () => { expect(display([fastModeDescriptor(true)])).toEqual({ label: "Fast", - showFastModeIcon: false, + speedIcon: null, }); expect(display([fastModeDescriptor(false)])).toEqual({ label: "Normal", - showFastModeIcon: false, + speedIcon: null, }); }); @@ -141,7 +169,7 @@ describe("buildTraitsTriggerDisplay", () => { { id: "high", label: "High" }, ], }; - expect(display([unresolved])).toEqual({ label: "", showFastModeIcon: false }); + expect(display([unresolved])).toEqual({ label: "", speedIcon: null }); }); it("still renders the prompt-controlled ultrathink label alongside the bolt", () => { @@ -152,7 +180,7 @@ describe("buildTraitsTriggerDisplay", () => { primarySelectDescriptorId: "reasoningEffort", ultrathinkPromptControlled: true, }), - ).toEqual({ label: "Ultrathink", showFastModeIcon: true }); + ).toEqual({ label: "Ultrathink", speedIcon: "fast" }); }); }); diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index 78c70152710d..179c72f11f5f 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -17,6 +17,7 @@ import { } from "@t3tools/shared/model"; import { memo, useCallback } from "react"; import { BrainIcon, ZapIcon } from "lucide-react"; +import { UltrafastIcon } from "../Icons"; import { Menu, MenuGroup, @@ -474,26 +475,22 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ }); /** - * Build the traits trigger's text label plus whether the fast-mode bolt should - * 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. + * Fast mode uses one bolt; Codex Ultrafast uses two. Keep a text label when + * speed is the only trait so the trigger remains readable. */ export function buildTraitsTriggerDisplay(input: { provider: ProviderDriverKind; descriptors: ReadonlyArray; primarySelectDescriptorId: string | null; ultrathinkPromptControlled: boolean; -}): { label: string; showFastModeIcon: boolean } { +}): { label: string; speedIcon: "fast" | "ultrafast" | null } { let fastModeFallbackLabel: string | null = null; - let fastModeEnabled = false; + let speedIcon: "fast" | "ultrafast" | null = null; const labels: Array = []; for (const descriptor of input.descriptors) { if (descriptor.id === "fastMode" && descriptor.type === "boolean") { - fastModeEnabled = descriptor.currentValue === true; - fastModeFallbackLabel = fastModeEnabled ? "Fast" : "Normal"; + speedIcon = descriptor.currentValue === true ? "fast" : null; + fastModeFallbackLabel = speedIcon ? "Fast" : "Normal"; continue; } if ( @@ -503,11 +500,20 @@ export function buildTraitsTriggerDisplay(input: { ) { const currentValue = getProviderOptionCurrentValue(descriptor); const fastTier = descriptor.options.find(({ label }) => label === "Fast"); - if (fastTier && (currentValue === "default" || currentValue === fastTier.id)) { - fastModeEnabled = currentValue === fastTier.id; + const ultrafastTier = descriptor.options.find(({ label }) => label === "Ultrafast"); + if ( + currentValue === "default" || + (fastTier && currentValue === fastTier.id) || + (ultrafastTier && currentValue === ultrafastTier.id) + ) { + speedIcon = + ultrafastTier && currentValue === ultrafastTier.id + ? "ultrafast" + : fastTier && currentValue === fastTier.id + ? "fast" + : null; fastModeFallbackLabel = - descriptor.options.find(({ id }) => id === currentValue)?.label ?? - (fastModeEnabled ? "Fast" : "Normal"); + descriptor.options.find(({ id }) => id === currentValue)?.label ?? "Normal"; continue; } } @@ -526,9 +532,9 @@ export function buildTraitsTriggerDisplay(input: { // 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 && fastModeFallbackLabel !== null) { - return { label: fastModeFallbackLabel, showFastModeIcon: false }; + return { label: fastModeFallbackLabel, speedIcon: null }; } - return { label: labels.join(" · "), showFastModeIcon: fastModeEnabled }; + return { label: labels.join(" · "), speedIcon }; } export const TraitsPicker = memo(function TraitsPicker({ @@ -577,17 +583,18 @@ export const TraitsPicker = memo(function TraitsPicker({ return null; } - const { label: triggerLabel, showFastModeIcon } = buildTraitsTriggerDisplay({ + const { label: triggerLabel, speedIcon } = buildTraitsTriggerDisplay({ provider, descriptors, primarySelectDescriptorId: primarySelectDescriptor?.id ?? null, ultrathinkPromptControlled, }); - const accessibleLabel = showFastModeIcon ? `${triggerLabel}, Fast mode on` : triggerLabel; - const fastModeIcon = showFastModeIcon ? ( + const speedLabel = speedIcon === "ultrafast" ? "Ultrafast mode on" : "Fast mode on"; + const accessibleLabel = speedIcon ? `${triggerLabel}, ${speedLabel}` : triggerLabel; + const fastModeIcon = speedIcon ? ( <> - Fast mode on + {speedLabel} ) : null; From 3d64830e1ff32af9da9ccd37caa48d065b9d9179 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 30 Sep 2026 12:53:51 -0700 Subject: [PATCH 2/2] fix(web): keep standard labels for non-speed Codex tiers --- .../web/src/components/chat/TraitsPicker.test.ts | 16 ++++++++++++++++ apps/web/src/components/chat/TraitsPicker.tsx | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/chat/TraitsPicker.test.ts b/apps/web/src/components/chat/TraitsPicker.test.ts index 0b0769bb2729..eae8d5a24fbb 100644 --- a/apps/web/src/components/chat/TraitsPicker.test.ts +++ b/apps/web/src/components/chat/TraitsPicker.test.ts @@ -114,6 +114,22 @@ describe("buildTraitsTriggerDisplay", () => { }); }); + it("keeps Standard as text for models without speed tiers", () => { + const descriptor = serviceTierDescriptor("default"); + const nonSpeedDescriptor = { + ...descriptor, + options: descriptor.options.filter(({ id }) => id === "default" || id === "flex"), + }; + expect(display([EFFORT, nonSpeedDescriptor])).toEqual({ + label: "High · Standard", + speedIcon: null, + }); + expect(display([nonSpeedDescriptor])).toEqual({ + label: "Standard", + speedIcon: null, + }); + }); + it("keeps the Codex service tier readable when it is the only trait", () => { expect(display([serviceTierDescriptor("default")])).toEqual({ label: "Standard", diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index 179c72f11f5f..0dbfc3ad531d 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -502,7 +502,7 @@ export function buildTraitsTriggerDisplay(input: { const fastTier = descriptor.options.find(({ label }) => label === "Fast"); const ultrafastTier = descriptor.options.find(({ label }) => label === "Ultrafast"); if ( - currentValue === "default" || + ((fastTier || ultrafastTier) && currentValue === "default") || (fastTier && currentValue === fastTier.id) || (ultrafastTier && currentValue === ultrafastTier.id) ) {