authorgravatar for me@pixp.ccAlex <me@pixp.cc> 2026-08-22 13:07:56-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-22 16:07:56-04:00
logdedcd99a9d16240327ce763b885b326aff607bdb
tree2c3a9a46a7be27672ac7762034cac825d5d70d2f
parentd9c1732b2791f762bce06e4898cdb6679ce24961
signature Signed by PGP key B5690EEEBB952194

fix(web): keep Codex service tier labels readable (#4503)


2 files changed, 49 insertions(+), 21 deletions(-)

apps/web/src/components/chat/TraitsPicker.test.ts+37-12
......@@ -16,6 +16,22 @@ function fastModeDescriptor(
1616 return { id: "fastMode", label: "Fast Mode", type: "boolean", currentValue };
1717}
1818
19function serviceTierDescriptor(
20 currentValue: "default" | "priority" | "flex",
21): Extract<ProviderOptionDescriptor, { type: "select" }> {
22 return {
23 id: "serviceTier",
24 label: "Service Tier",
25 type: "select",
26 options: [
27 { id: "default", label: "Standard", isDefault: true },
28 { id: "priority", label: "Fast" },
29 { id: "flex", label: "Flex" },
30 ],
31 currentValue,
32 };
33}
34
1935const EFFORT = selectDescriptor(
2036 "reasoningEffort",
2137 [
......@@ -59,26 +75,35 @@ describe("buildTraitsTriggerDisplay", () => {
5975 });
6076 });
6177
62 it("renders Codex's Standard and Fast service tiers as fast mode", () => {
63 const serviceTier = selectDescriptor(
64 "serviceTier",
65 [
66 { id: "default", label: "Standard", isDefault: true },
67 { id: "priority", label: "Fast" },
68 ],
69 "default",
70 );
71
72 expect(display([EFFORT, serviceTier])).toEqual({
78 it("treats Codex standard and fast service tiers as fast mode states", () => {
79 expect(display([EFFORT, serviceTierDescriptor("default")])).toEqual({
7380 label: "High",
7481 showFastModeIcon: false,
7582 });
76 expect(display([EFFORT, { ...serviceTier, currentValue: "priority" }])).toEqual({
83 expect(display([EFFORT, serviceTierDescriptor("priority")])).toEqual({
7784 label: "High",
7885 showFastModeIcon: true,
7986 });
8087 });
8188
89 it("keeps other Codex service tiers in the label", () => {
90 expect(display([EFFORT, serviceTierDescriptor("flex")])).toEqual({
91 label: "High · Flex",
92 showFastModeIcon: false,
93 });
94 });
95
96 it("keeps the Codex service tier readable when it is the only trait", () => {
97 expect(display([serviceTierDescriptor("default")])).toEqual({
98 label: "Standard",
99 showFastModeIcon: false,
100 });
101 expect(display([serviceTierDescriptor("priority")])).toEqual({
102 label: "Fast",
103 showFastModeIcon: false,
104 });
105 });
106
82107 it("keeps non-fastMode booleans as text labels", () => {
83108 const thinking: Extract<ProviderOptionDescriptor, { type: "boolean" }> = {
84109 id: "thinking",
apps/web/src/components/chat/TraitsPicker.tsx+12-9
......@@ -396,10 +396,11 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
396396
397397/**
398398 * Build the traits trigger's text label plus whether the fast-mode bolt should
399 * render. Fast mode is a lightning bolt when on and nothing at all when off —
400 * "Normal" is the near-universal case and isn't worth the horizontal space. The
401 * one exception is when fast mode is the only trait, where a bare bolt (or bare
402 * chevron) would leave the trigger unreadable.
399 * render. Claude and Cursor expose fast mode as a boolean, while Codex exposes
400 * it through the Standard/Fast service tiers. In either form, fast mode is a
401 * lightning bolt when on and nothing at all when off. The one exception is when
402 * fast mode is the only trait, where a bare bolt (or bare chevron) would leave
403 * the trigger unreadable.
403404 */
404405export function buildTraitsTriggerDisplay(input: {
405406 provider: ProviderDriverKind;
......@@ -407,13 +408,13 @@ export function buildTraitsTriggerDisplay(input: {
407408 primarySelectDescriptorId: string | null;
408409 ultrathinkPromptControlled: boolean;
409410}): { label: string; showFastModeIcon: boolean } {
410 let hasFastMode = false;
411 let fastModeFallbackLabel: string | null = null;
411412 let fastModeEnabled = false;
412413 const labels: Array<string> = [];
413414 for (const descriptor of input.descriptors) {
414415 if (descriptor.id === "fastMode" && descriptor.type === "boolean") {
415 hasFastMode = true;
416416 fastModeEnabled = descriptor.currentValue === true;
417 fastModeFallbackLabel = fastModeEnabled ? "Fast" : "Normal";
417418 continue;
418419 }
419420 if (
......@@ -424,8 +425,10 @@ export function buildTraitsTriggerDisplay(input: {
424425 const currentValue = getProviderOptionCurrentValue(descriptor);
425426 const fastTier = descriptor.options.find(({ label }) => label === "Fast");
426427 if (fastTier && (currentValue === "default" || currentValue === fastTier.id)) {
427 hasFastMode = true;
428428 fastModeEnabled = currentValue === fastTier.id;
429 fastModeFallbackLabel =
430 descriptor.options.find(({ id }) => id === currentValue)?.label ??
431 (fastModeEnabled ? "Fast" : "Normal");
429432 continue;
430433 }
431434 }
......@@ -443,8 +446,8 @@ export function buildTraitsTriggerDisplay(input: {
443446 // Only fall back to text when fast mode is genuinely the sole trait. Keying
444447 // off an empty label list alone would also catch descriptors that resolved to
445448 // no label at all, printing a bogus "Normal" for a model without fast mode.
446 if (labels.length === 0 && hasFastMode) {
447 return { label: fastModeEnabled ? "Fast" : "Normal", showFastModeIcon: false };
449 if (labels.length === 0 && fastModeFallbackLabel !== null) {
450 return { label: fastModeFallbackLabel, showFastModeIcon: false };
448451 }
449452 return { label: labels.join(" · "), showFastModeIcon: fastModeEnabled };
450453}