authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-30 13:03:29-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-30 13:03:29-07:00
log792c7dd127865fc542a453ad944b94ae85ead10a
tree4b1e8bee80d726d63663783533685669d2725fbe
parent35be904f2fc40aa6d7a42778b6895e8274f3097f
signature Signed by PGP key B5690EEEBB952194

fix(web): show double bolts for Codex Ultrafast (#14479)


4 files changed, 95 insertions(+), 37 deletions(-)

apps/web/src/components/Icons.tsx+7
...@@ -2,6 +2,13 @@ import React, { type SVGProps, useId } from "react";...@@ -2,6 +2,13 @@ import React, { type SVGProps, useId } from "react";
2import { cn } from "~/lib/utils";2import { cn } from "~/lib/utils";
3export type Icon = React.FC<SVGProps<SVGSVGElement>>;3export type Icon = React.FC<SVGProps<SVGSVGElement>>;
44
5export const UltrafastIcon: Icon = (props) => (
6 <svg viewBox="0 0 24 24" fill="currentColor" {...props}>
7 <path d="m17 2-10 12h7l-1 8 10-12h-7l1-8Z" opacity="0.4" />
8 <path d="m11 2-10 12h7l-1 8 10-12h-7l1-8Z" />
9 </svg>
10);
11
5export const FinderIcon: Icon = (props) => (12export const FinderIcon: Icon = (props) => (
6 <svg viewBox="0 0 24 24" fill="none" {...props}>13 <svg viewBox="0 0 24 24" fill="none" {...props}>
7 <rect x="2" y="2" width="20" height="20" rx="4" fill="#36A9F5" />14 <rect x="2" y="2" width="20" height="20" rx="4" fill="#36A9F5" />
apps/web/src/components/chat/ComposerControl.tsx+3-3
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1import { mergeProps } from "@base-ui/react/merge-props";1import { mergeProps } from "@base-ui/react/merge-props";
2import { Select as SelectPrimitive } from "@base-ui/react/select";2import { Select as SelectPrimitive } from "@base-ui/react/select";
3import { useRender } from "@base-ui/react/use-render";3import { useRender } from "@base-ui/react/use-render";
4import type { ComponentProps } from "react";4import type { ComponentProps, ComponentType, SVGProps } from "react";
5import { ChevronDownIcon, type LucideIcon } from "lucide-react";5import { ChevronDownIcon } from "lucide-react";
66
7import { cn } from "~/lib/utils";7import { cn } from "~/lib/utils";
8import { Separator } from "../ui/separator";8import { Separator } from "../ui/separator";
...@@ -52,7 +52,7 @@ export function ComposerControlIcon({...@@ -52,7 +52,7 @@ export function ComposerControlIcon({
52 opticalSize = "default",52 opticalSize = "default",
53 size = "sm",53 size = "sm",
54}: {54}: {
55 icon: LucideIcon;55 icon: ComponentType<SVGProps<SVGSVGElement>>;
56 className?: string | undefined;56 className?: string | undefined;
57 opticalSize?: "default" | "large";57 opticalSize?: "default" | "large";
58 size?: ComposerControlSize;58 size?: ComposerControlSize;
apps/web/src/components/chat/TraitsPicker.test.ts+57-13
...@@ -17,7 +17,7 @@ function fastModeDescriptor(...@@ -17,7 +17,7 @@ function fastModeDescriptor(
17}17}
1818
19function serviceTierDescriptor(19function serviceTierDescriptor(
20 currentValue: "default" | "priority" | "flex",20 currentValue: "default" | "priority" | "ultrafast" | "flex",
21): Extract<ProviderOptionDescriptor, { type: "select" }> {21): Extract<ProviderOptionDescriptor, { type: "select" }> {
22 return {22 return {
23 id: "serviceTier",23 id: "serviceTier",
...@@ -26,6 +26,7 @@ function serviceTierDescriptor(...@@ -26,6 +26,7 @@ function serviceTierDescriptor(
26 options: [26 options: [
27 { id: "default", label: "Standard", isDefault: true },27 { id: "default", label: "Standard", isDefault: true },
28 { id: "priority", label: "Fast" },28 { id: "priority", label: "Fast" },
29 { id: "ultrafast", label: "Ultrafast" },
29 { id: "flex", label: "Flex" },30 { id: "flex", label: "Flex" },
30 ],31 ],
31 currentValue,32 currentValue,
...@@ -64,43 +65,86 @@ describe("buildTraitsTriggerDisplay", () => {...@@ -64,43 +65,86 @@ describe("buildTraitsTriggerDisplay", () => {
64 it("omits fast mode from the label entirely when it is off", () => {65 it("omits fast mode from the label entirely when it is off", () => {
65 expect(display([EFFORT, fastModeDescriptor(false), CONTEXT_WINDOW])).toEqual({66 expect(display([EFFORT, fastModeDescriptor(false), CONTEXT_WINDOW])).toEqual({
66 label: "High · 1M",67 label: "High · 1M",
67 showFastModeIcon: false,68 speedIcon: null,
68 });69 });
69 });70 });
7071
71 it("shows the bolt instead of a text label when fast mode is on", () => {72 it("shows the bolt instead of a text label when fast mode is on", () => {
72 expect(display([EFFORT, fastModeDescriptor(true), CONTEXT_WINDOW])).toEqual({73 expect(display([EFFORT, fastModeDescriptor(true), CONTEXT_WINDOW])).toEqual({
73 label: "High · 1M",74 label: "High · 1M",
74 showFastModeIcon: true,75 speedIcon: "fast",
75 });76 });
76 });77 });
7778
78 it("treats Codex standard and fast service tiers as fast mode states", () => {79 it("treats Codex standard and fast service tiers as fast mode states", () => {
79 expect(display([EFFORT, serviceTierDescriptor("default")])).toEqual({80 expect(display([EFFORT, serviceTierDescriptor("default")])).toEqual({
80 label: "High",81 label: "High",
81 showFastModeIcon: false,82 speedIcon: null,
82 });83 });
83 expect(display([EFFORT, serviceTierDescriptor("priority")])).toEqual({84 expect(display([EFFORT, serviceTierDescriptor("priority")])).toEqual({
84 label: "High",85 label: "High",
85 showFastModeIcon: true,86 speedIcon: "fast",
87 });
88 });
89
90 it("uses a distinct double bolt for Codex Ultrafast", () => {
91 expect(display([EFFORT, serviceTierDescriptor("ultrafast")])).toEqual({
92 label: "High",
93 speedIcon: "ultrafast",
94 });
95 });
96
97 it("uses Ultrafast without requiring a Fast tier", () => {
98 const descriptor = serviceTierDescriptor("ultrafast");
99 expect(
100 display([
101 EFFORT,
102 { ...descriptor, options: descriptor.options.filter(({ id }) => id !== "priority") },
103 ]),
104 ).toEqual({
105 label: "High",
106 speedIcon: "ultrafast",
86 });107 });
87 });108 });
88109
89 it("keeps other Codex service tiers in the label", () => {110 it("keeps other Codex service tiers in the label", () => {
90 expect(display([EFFORT, serviceTierDescriptor("flex")])).toEqual({111 expect(display([EFFORT, serviceTierDescriptor("flex")])).toEqual({
91 label: "High · Flex",112 label: "High · Flex",
92 showFastModeIcon: false,113 speedIcon: null,
114 });
115 });
116
117 it("keeps Standard as text for models without speed tiers", () => {
118 const descriptor = serviceTierDescriptor("default");
119 const nonSpeedDescriptor = {
120 ...descriptor,
121 options: descriptor.options.filter(({ id }) => id === "default" || id === "flex"),
122 };
123 expect(display([EFFORT, nonSpeedDescriptor])).toEqual({
124 label: "High · Standard",
125 speedIcon: null,
126 });
127 expect(display([nonSpeedDescriptor])).toEqual({
128 label: "Standard",
129 speedIcon: null,
93 });130 });
94 });131 });
95132
96 it("keeps the Codex service tier readable when it is the only trait", () => {133 it("keeps the Codex service tier readable when it is the only trait", () => {
97 expect(display([serviceTierDescriptor("default")])).toEqual({134 expect(display([serviceTierDescriptor("default")])).toEqual({
98 label: "Standard",135 label: "Standard",
99 showFastModeIcon: false,136 speedIcon: null,
100 });137 });
101 expect(display([serviceTierDescriptor("priority")])).toEqual({138 expect(display([serviceTierDescriptor("priority")])).toEqual({
102 label: "Fast",139 label: "Fast",
103 showFastModeIcon: false,140 speedIcon: null,
141 });
142 });
143
144 it("keeps Ultrafast readable when it is the only trait", () => {
145 expect(display([serviceTierDescriptor("ultrafast")])).toEqual({
146 label: "Ultrafast",
147 speedIcon: null,
104 });148 });
105 });149 });
106150
...@@ -113,18 +157,18 @@ describe("buildTraitsTriggerDisplay", () => {...@@ -113,18 +157,18 @@ describe("buildTraitsTriggerDisplay", () => {
113 };157 };
114 expect(display([EFFORT, thinking])).toEqual({158 expect(display([EFFORT, thinking])).toEqual({
115 label: "High · Thinking On",159 label: "High · Thinking On",
116 showFastModeIcon: false,160 speedIcon: null,
117 });161 });
118 });162 });
119163
120 it("falls back to a text label when fast mode is the only trait", () => {164 it("falls back to a text label when fast mode is the only trait", () => {
121 expect(display([fastModeDescriptor(true)])).toEqual({165 expect(display([fastModeDescriptor(true)])).toEqual({
122 label: "Fast",166 label: "Fast",
123 showFastModeIcon: false,167 speedIcon: null,
124 });168 });
125 expect(display([fastModeDescriptor(false)])).toEqual({169 expect(display([fastModeDescriptor(false)])).toEqual({
126 label: "Normal",170 label: "Normal",
127 showFastModeIcon: false,171 speedIcon: null,
128 });172 });
129 });173 });
130174
...@@ -141,7 +185,7 @@ describe("buildTraitsTriggerDisplay", () => {...@@ -141,7 +185,7 @@ describe("buildTraitsTriggerDisplay", () => {
141 { id: "high", label: "High" },185 { id: "high", label: "High" },
142 ],186 ],
143 };187 };
144 expect(display([unresolved])).toEqual({ label: "", showFastModeIcon: false });188 expect(display([unresolved])).toEqual({ label: "", speedIcon: null });
145 });189 });
146190
147 it("still renders the prompt-controlled ultrathink label alongside the bolt", () => {191 it("still renders the prompt-controlled ultrathink label alongside the bolt", () => {
...@@ -152,7 +196,7 @@ describe("buildTraitsTriggerDisplay", () => {...@@ -152,7 +196,7 @@ describe("buildTraitsTriggerDisplay", () => {
152 primarySelectDescriptorId: "reasoningEffort",196 primarySelectDescriptorId: "reasoningEffort",
153 ultrathinkPromptControlled: true,197 ultrathinkPromptControlled: true,
154 }),198 }),
155 ).toEqual({ label: "Ultrathink", showFastModeIcon: true });199 ).toEqual({ label: "Ultrathink", speedIcon: "fast" });
156 });200 });
157});201});
158202
apps/web/src/components/chat/TraitsPicker.tsx+28-21
...@@ -17,6 +17,7 @@ import {...@@ -17,6 +17,7 @@ import {
17} from "@t3tools/shared/model";17} from "@t3tools/shared/model";
18import { memo, useCallback } from "react";18import { memo, useCallback } from "react";
19import { BrainIcon, ZapIcon } from "lucide-react";19import { BrainIcon, ZapIcon } from "lucide-react";
20import { UltrafastIcon } from "../Icons";
20import {21import {
21 Menu,22 Menu,
22 MenuGroup,23 MenuGroup,
...@@ -474,26 +475,22 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({...@@ -474,26 +475,22 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
474});475});
475476
476/**477/**
477 * Build the traits trigger's text label plus whether the fast-mode bolt should478 * Fast mode uses one bolt; Codex Ultrafast uses two. Keep a text label when
478 * render. Claude and Cursor expose fast mode as a boolean, while Codex exposes479 * speed is the only trait so the trigger remains readable.
479 * it through the Standard/Fast service tiers. In either form, fast mode is a
480 * lightning bolt when on and nothing at all when off. The one exception is when
481 * fast mode is the only trait, where a bare bolt (or bare chevron) would leave
482 * the trigger unreadable.
483 */480 */
484export function buildTraitsTriggerDisplay(input: {481export function buildTraitsTriggerDisplay(input: {
485 provider: ProviderDriverKind;482 provider: ProviderDriverKind;
486 descriptors: ReadonlyArray<ProviderOptionDescriptor>;483 descriptors: ReadonlyArray<ProviderOptionDescriptor>;
487 primarySelectDescriptorId: string | null;484 primarySelectDescriptorId: string | null;
488 ultrathinkPromptControlled: boolean;485 ultrathinkPromptControlled: boolean;
489}): { label: string; showFastModeIcon: boolean } {486}): { label: string; speedIcon: "fast" | "ultrafast" | null } {
490 let fastModeFallbackLabel: string | null = null;487 let fastModeFallbackLabel: string | null = null;
491 let fastModeEnabled = false;488 let speedIcon: "fast" | "ultrafast" | null = null;
492 const labels: Array<string> = [];489 const labels: Array<string> = [];
493 for (const descriptor of input.descriptors) {490 for (const descriptor of input.descriptors) {
494 if (descriptor.id === "fastMode" && descriptor.type === "boolean") {491 if (descriptor.id === "fastMode" && descriptor.type === "boolean") {
495 fastModeEnabled = descriptor.currentValue === true;492 speedIcon = descriptor.currentValue === true ? "fast" : null;
496 fastModeFallbackLabel = fastModeEnabled ? "Fast" : "Normal";493 fastModeFallbackLabel = speedIcon ? "Fast" : "Normal";
497 continue;494 continue;
498 }495 }
499 if (496 if (
...@@ -503,11 +500,20 @@ export function buildTraitsTriggerDisplay(input: {...@@ -503,11 +500,20 @@ export function buildTraitsTriggerDisplay(input: {
503 ) {500 ) {
504 const currentValue = getProviderOptionCurrentValue(descriptor);501 const currentValue = getProviderOptionCurrentValue(descriptor);
505 const fastTier = descriptor.options.find(({ label }) => label === "Fast");502 const fastTier = descriptor.options.find(({ label }) => label === "Fast");
506 if (fastTier && (currentValue === "default" || currentValue === fastTier.id)) {503 const ultrafastTier = descriptor.options.find(({ label }) => label === "Ultrafast");
507 fastModeEnabled = currentValue === fastTier.id;504 if (
505 ((fastTier || ultrafastTier) && currentValue === "default") ||
506 (fastTier && currentValue === fastTier.id) ||
507 (ultrafastTier && currentValue === ultrafastTier.id)
508 ) {
509 speedIcon =
510 ultrafastTier && currentValue === ultrafastTier.id
511 ? "ultrafast"
512 : fastTier && currentValue === fastTier.id
513 ? "fast"
514 : null;
508 fastModeFallbackLabel =515 fastModeFallbackLabel =
509 descriptor.options.find(({ id }) => id === currentValue)?.label ??516 descriptor.options.find(({ id }) => id === currentValue)?.label ?? "Normal";
510 (fastModeEnabled ? "Fast" : "Normal");
511 continue;517 continue;
512 }518 }
513 }519 }
...@@ -526,9 +532,9 @@ export function buildTraitsTriggerDisplay(input: {...@@ -526,9 +532,9 @@ export function buildTraitsTriggerDisplay(input: {
526 // off an empty label list alone would also catch descriptors that resolved to532 // off an empty label list alone would also catch descriptors that resolved to
527 // no label at all, printing a bogus "Normal" for a model without fast mode.533 // no label at all, printing a bogus "Normal" for a model without fast mode.
528 if (labels.length === 0 && fastModeFallbackLabel !== null) {534 if (labels.length === 0 && fastModeFallbackLabel !== null) {
529 return { label: fastModeFallbackLabel, showFastModeIcon: false };535 return { label: fastModeFallbackLabel, speedIcon: null };
530 }536 }
531 return { label: labels.join(" · "), showFastModeIcon: fastModeEnabled };537 return { label: labels.join(" · "), speedIcon };
532}538}
533539
534export const TraitsPicker = memo(function TraitsPicker({540export const TraitsPicker = memo(function TraitsPicker({
...@@ -577,17 +583,18 @@ export const TraitsPicker = memo(function TraitsPicker({...@@ -577,17 +583,18 @@ export const TraitsPicker = memo(function TraitsPicker({
577 return null;583 return null;
578 }584 }
579585
580 const { label: triggerLabel, showFastModeIcon } = buildTraitsTriggerDisplay({586 const { label: triggerLabel, speedIcon } = buildTraitsTriggerDisplay({
581 provider,587 provider,
582 descriptors,588 descriptors,
583 primarySelectDescriptorId: primarySelectDescriptor?.id ?? null,589 primarySelectDescriptorId: primarySelectDescriptor?.id ?? null,
584 ultrathinkPromptControlled,590 ultrathinkPromptControlled,
585 });591 });
586 const accessibleLabel = showFastModeIcon ? `${triggerLabel}, Fast mode on` : triggerLabel;592 const speedLabel = speedIcon === "ultrafast" ? "Ultrafast mode on" : "Fast mode on";
587 const fastModeIcon = showFastModeIcon ? (593 const accessibleLabel = speedIcon ? `${triggerLabel}, ${speedLabel}` : triggerLabel;
594 const fastModeIcon = speedIcon ? (
588 <>595 <>
589 <ComposerControlIcon596 <ComposerControlIcon
590 icon={ZapIcon}597 icon={speedIcon === "ultrafast" ? UltrafastIcon : ZapIcon}
591 size={size}598 size={size}
592 className={cn(599 className={cn(
593 "fill-current opacity-80",600 "fill-current opacity-80",
...@@ -598,7 +605,7 @@ export const TraitsPicker = memo(function TraitsPicker({...@@ -598,7 +605,7 @@ export const TraitsPicker = memo(function TraitsPicker({
598 : "text-foreground",605 : "text-foreground",
599 )}606 )}
600 />607 />
601 <span className="sr-only">Fast mode on</span>608 <span className="sr-only">{speedLabel}</span>
602 </>609 </>
603 ) : null;610 ) : null;
604611