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";
22import { cn } from "~/lib/utils";
33export 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
512export const FinderIcon: Icon = (props) => (
613 <svg viewBox="0 0 24 24" fill="none" {...props}>
714 <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 @@
11import { mergeProps } from "@base-ui/react/merge-props";
22import { Select as SelectPrimitive } from "@base-ui/react/select";
33import { useRender } from "@base-ui/react/use-render";
4import type { ComponentProps } from "react";
5import { ChevronDownIcon, type LucideIcon } from "lucide-react";
4import type { ComponentProps, ComponentType, SVGProps } from "react";
5import { ChevronDownIcon } from "lucide-react";
66
77import { cn } from "~/lib/utils";
88import { Separator } from "../ui/separator";
......@@ -52,7 +52,7 @@ export function ComposerControlIcon({
5252 opticalSize = "default",
5353 size = "sm",
5454}: {
55 icon: LucideIcon;
55 icon: ComponentType<SVGProps<SVGSVGElement>>;
5656 className?: string | undefined;
5757 opticalSize?: "default" | "large";
5858 size?: ComposerControlSize;
apps/web/src/components/chat/TraitsPicker.test.ts+57-13
......@@ -17,7 +17,7 @@ function fastModeDescriptor(
1717}
1818
1919function serviceTierDescriptor(
20 currentValue: "default" | "priority" | "flex",
20 currentValue: "default" | "priority" | "ultrafast" | "flex",
2121): Extract<ProviderOptionDescriptor, { type: "select" }> {
2222 return {
2323 id: "serviceTier",
......@@ -26,6 +26,7 @@ function serviceTierDescriptor(
2626 options: [
2727 { id: "default", label: "Standard", isDefault: true },
2828 { id: "priority", label: "Fast" },
29 { id: "ultrafast", label: "Ultrafast" },
2930 { id: "flex", label: "Flex" },
3031 ],
3132 currentValue,
......@@ -64,43 +65,86 @@ describe("buildTraitsTriggerDisplay", () => {
6465 it("omits fast mode from the label entirely when it is off", () => {
6566 expect(display([EFFORT, fastModeDescriptor(false), CONTEXT_WINDOW])).toEqual({
6667 label: "High · 1M",
67 showFastModeIcon: false,
68 speedIcon: null,
6869 });
6970 });
7071
7172 it("shows the bolt instead of a text label when fast mode is on", () => {
7273 expect(display([EFFORT, fastModeDescriptor(true), CONTEXT_WINDOW])).toEqual({
7374 label: "High · 1M",
74 showFastModeIcon: true,
75 speedIcon: "fast",
7576 });
7677 });
7778
7879 it("treats Codex standard and fast service tiers as fast mode states", () => {
7980 expect(display([EFFORT, serviceTierDescriptor("default")])).toEqual({
8081 label: "High",
81 showFastModeIcon: false,
82 speedIcon: null,
8283 });
8384 expect(display([EFFORT, serviceTierDescriptor("priority")])).toEqual({
8485 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",
86107 });
87108 });
88109
89110 it("keeps other Codex service tiers in the label", () => {
90111 expect(display([EFFORT, serviceTierDescriptor("flex")])).toEqual({
91112 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,
93130 });
94131 });
95132
96133 it("keeps the Codex service tier readable when it is the only trait", () => {
97134 expect(display([serviceTierDescriptor("default")])).toEqual({
98135 label: "Standard",
99 showFastModeIcon: false,
136 speedIcon: null,
100137 });
101138 expect(display([serviceTierDescriptor("priority")])).toEqual({
102139 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,
104148 });
105149 });
106150
......@@ -113,18 +157,18 @@ describe("buildTraitsTriggerDisplay", () => {
113157 };
114158 expect(display([EFFORT, thinking])).toEqual({
115159 label: "High · Thinking On",
116 showFastModeIcon: false,
160 speedIcon: null,
117161 });
118162 });
119163
120164 it("falls back to a text label when fast mode is the only trait", () => {
121165 expect(display([fastModeDescriptor(true)])).toEqual({
122166 label: "Fast",
123 showFastModeIcon: false,
167 speedIcon: null,
124168 });
125169 expect(display([fastModeDescriptor(false)])).toEqual({
126170 label: "Normal",
127 showFastModeIcon: false,
171 speedIcon: null,
128172 });
129173 });
130174
......@@ -141,7 +185,7 @@ describe("buildTraitsTriggerDisplay", () => {
141185 { id: "high", label: "High" },
142186 ],
143187 };
144 expect(display([unresolved])).toEqual({ label: "", showFastModeIcon: false });
188 expect(display([unresolved])).toEqual({ label: "", speedIcon: null });
145189 });
146190
147191 it("still renders the prompt-controlled ultrathink label alongside the bolt", () => {
......@@ -152,7 +196,7 @@ describe("buildTraitsTriggerDisplay", () => {
152196 primarySelectDescriptorId: "reasoningEffort",
153197 ultrathinkPromptControlled: true,
154198 }),
155 ).toEqual({ label: "Ultrathink", showFastModeIcon: true });
199 ).toEqual({ label: "Ultrathink", speedIcon: "fast" });
156200 });
157201});
158202
apps/web/src/components/chat/TraitsPicker.tsx+28-21
......@@ -17,6 +17,7 @@ import {
1717} from "@t3tools/shared/model";
1818import { memo, useCallback } from "react";
1919import { BrainIcon, ZapIcon } from "lucide-react";
20import { UltrafastIcon } from "../Icons";
2021import {
2122 Menu,
2223 MenuGroup,
......@@ -474,26 +475,22 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
474475});
475476
476477/**
477 * Build the traits trigger's text label plus whether the fast-mode bolt should
478 * render. Claude and Cursor expose fast mode as a boolean, while Codex exposes
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.
478 * Fast mode uses one bolt; Codex Ultrafast uses two. Keep a text label when
479 * speed is the only trait so the trigger remains readable.
483480 */
484481export function buildTraitsTriggerDisplay(input: {
485482 provider: ProviderDriverKind;
486483 descriptors: ReadonlyArray<ProviderOptionDescriptor>;
487484 primarySelectDescriptorId: string | null;
488485 ultrathinkPromptControlled: boolean;
489}): { label: string; showFastModeIcon: boolean } {
486}): { label: string; speedIcon: "fast" | "ultrafast" | null } {
490487 let fastModeFallbackLabel: string | null = null;
491 let fastModeEnabled = false;
488 let speedIcon: "fast" | "ultrafast" | null = null;
492489 const labels: Array<string> = [];
493490 for (const descriptor of input.descriptors) {
494491 if (descriptor.id === "fastMode" && descriptor.type === "boolean") {
495 fastModeEnabled = descriptor.currentValue === true;
496 fastModeFallbackLabel = fastModeEnabled ? "Fast" : "Normal";
492 speedIcon = descriptor.currentValue === true ? "fast" : null;
493 fastModeFallbackLabel = speedIcon ? "Fast" : "Normal";
497494 continue;
498495 }
499496 if (
......@@ -503,11 +500,20 @@ export function buildTraitsTriggerDisplay(input: {
503500 ) {
504501 const currentValue = getProviderOptionCurrentValue(descriptor);
505502 const fastTier = descriptor.options.find(({ label }) => label === "Fast");
506 if (fastTier && (currentValue === "default" || currentValue === fastTier.id)) {
507 fastModeEnabled = currentValue === fastTier.id;
503 const ultrafastTier = descriptor.options.find(({ label }) => label === "Ultrafast");
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;
508515 fastModeFallbackLabel =
509 descriptor.options.find(({ id }) => id === currentValue)?.label ??
510 (fastModeEnabled ? "Fast" : "Normal");
516 descriptor.options.find(({ id }) => id === currentValue)?.label ?? "Normal";
511517 continue;
512518 }
513519 }
......@@ -526,9 +532,9 @@ export function buildTraitsTriggerDisplay(input: {
526532 // off an empty label list alone would also catch descriptors that resolved to
527533 // no label at all, printing a bogus "Normal" for a model without fast mode.
528534 if (labels.length === 0 && fastModeFallbackLabel !== null) {
529 return { label: fastModeFallbackLabel, showFastModeIcon: false };
535 return { label: fastModeFallbackLabel, speedIcon: null };
530536 }
531 return { label: labels.join(" · "), showFastModeIcon: fastModeEnabled };
537 return { label: labels.join(" · "), speedIcon };
532538}
533539
534540export const TraitsPicker = memo(function TraitsPicker({
......@@ -577,17 +583,18 @@ export const TraitsPicker = memo(function TraitsPicker({
577583 return null;
578584 }
579585
580 const { label: triggerLabel, showFastModeIcon } = buildTraitsTriggerDisplay({
586 const { label: triggerLabel, speedIcon } = buildTraitsTriggerDisplay({
581587 provider,
582588 descriptors,
583589 primarySelectDescriptorId: primarySelectDescriptor?.id ?? null,
584590 ultrathinkPromptControlled,
585591 });
586 const accessibleLabel = showFastModeIcon ? `${triggerLabel}, Fast mode on` : triggerLabel;
587 const fastModeIcon = showFastModeIcon ? (
592 const speedLabel = speedIcon === "ultrafast" ? "Ultrafast mode on" : "Fast mode on";
593 const accessibleLabel = speedIcon ? `${triggerLabel}, ${speedLabel}` : triggerLabel;
594 const fastModeIcon = speedIcon ? (
588595 <>
589596 <ComposerControlIcon
590 icon={ZapIcon}
597 icon={speedIcon === "ultrafast" ? UltrafastIcon : ZapIcon}
591598 size={size}
592599 className={cn(
593600 "fill-current opacity-80",
......@@ -598,7 +605,7 @@ export const TraitsPicker = memo(function TraitsPicker({
598605 : "text-foreground",
599606 )}
600607 />
601 <span className="sr-only">Fast mode on</span>
608 <span className="sr-only">{speedLabel}</span>
602609 </>
603610 ) : null;
604611