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(...@@ -16,6 +16,22 @@ function fastModeDescriptor(
16 return { id: "fastMode", label: "Fast Mode", type: "boolean", currentValue };16 return { id: "fastMode", label: "Fast Mode", type: "boolean", currentValue };
17}17}
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
19const EFFORT = selectDescriptor(35const EFFORT = selectDescriptor(
20 "reasoningEffort",36 "reasoningEffort",
21 [37 [
...@@ -59,26 +75,35 @@ describe("buildTraitsTriggerDisplay", () => {...@@ -59,26 +75,35 @@ describe("buildTraitsTriggerDisplay", () => {
59 });75 });
60 });76 });
6177
62 it("renders Codex's Standard and Fast service tiers as fast mode", () => {78 it("treats Codex standard and fast service tiers as fast mode states", () => {
63 const serviceTier = selectDescriptor(79 expect(display([EFFORT, serviceTierDescriptor("default")])).toEqual({
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({
73 label: "High",80 label: "High",
74 showFastModeIcon: false,81 showFastModeIcon: false,
75 });82 });
76 expect(display([EFFORT, { ...serviceTier, currentValue: "priority" }])).toEqual({83 expect(display([EFFORT, serviceTierDescriptor("priority")])).toEqual({
77 label: "High",84 label: "High",
78 showFastModeIcon: true,85 showFastModeIcon: true,
79 });86 });
80 });87 });
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
82 it("keeps non-fastMode booleans as text labels", () => {107 it("keeps non-fastMode booleans as text labels", () => {
83 const thinking: Extract<ProviderOptionDescriptor, { type: "boolean" }> = {108 const thinking: Extract<ProviderOptionDescriptor, { type: "boolean" }> = {
84 id: "thinking",109 id: "thinking",
apps/web/src/components/chat/TraitsPicker.tsx+12-9
...@@ -396,10 +396,11 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({...@@ -396,10 +396,11 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
396396
397/**397/**
398 * Build the traits trigger's text label plus whether the fast-mode bolt should398 * 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 —399 * render. Claude and Cursor expose fast mode as a boolean, while Codex exposes
400 * "Normal" is the near-universal case and isn't worth the horizontal space. The400 * it through the Standard/Fast service tiers. In either form, fast mode is a
401 * one exception is when fast mode is the only trait, where a bare bolt (or bare401 * lightning bolt when on and nothing at all when off. The one exception is when
402 * chevron) would leave the trigger unreadable.402 * fast mode is the only trait, where a bare bolt (or bare chevron) would leave
403 * the trigger unreadable.
403 */404 */
404export function buildTraitsTriggerDisplay(input: {405export function buildTraitsTriggerDisplay(input: {
405 provider: ProviderDriverKind;406 provider: ProviderDriverKind;
...@@ -407,13 +408,13 @@ export function buildTraitsTriggerDisplay(input: {...@@ -407,13 +408,13 @@ export function buildTraitsTriggerDisplay(input: {
407 primarySelectDescriptorId: string | null;408 primarySelectDescriptorId: string | null;
408 ultrathinkPromptControlled: boolean;409 ultrathinkPromptControlled: boolean;
409}): { label: string; showFastModeIcon: boolean } {410}): { label: string; showFastModeIcon: boolean } {
410 let hasFastMode = false;411 let fastModeFallbackLabel: string | null = null;
411 let fastModeEnabled = false;412 let fastModeEnabled = false;
412 const labels: Array<string> = [];413 const labels: Array<string> = [];
413 for (const descriptor of input.descriptors) {414 for (const descriptor of input.descriptors) {
414 if (descriptor.id === "fastMode" && descriptor.type === "boolean") {415 if (descriptor.id === "fastMode" && descriptor.type === "boolean") {
415 hasFastMode = true;
416 fastModeEnabled = descriptor.currentValue === true;416 fastModeEnabled = descriptor.currentValue === true;
417 fastModeFallbackLabel = fastModeEnabled ? "Fast" : "Normal";
417 continue;418 continue;
418 }419 }
419 if (420 if (
...@@ -424,8 +425,10 @@ export function buildTraitsTriggerDisplay(input: {...@@ -424,8 +425,10 @@ export function buildTraitsTriggerDisplay(input: {
424 const currentValue = getProviderOptionCurrentValue(descriptor);425 const currentValue = getProviderOptionCurrentValue(descriptor);
425 const fastTier = descriptor.options.find(({ label }) => label === "Fast");426 const fastTier = descriptor.options.find(({ label }) => label === "Fast");
426 if (fastTier && (currentValue === "default" || currentValue === fastTier.id)) {427 if (fastTier && (currentValue === "default" || currentValue === fastTier.id)) {
427 hasFastMode = true;
428 fastModeEnabled = currentValue === fastTier.id;428 fastModeEnabled = currentValue === fastTier.id;
429 fastModeFallbackLabel =
430 descriptor.options.find(({ id }) => id === currentValue)?.label ??
431 (fastModeEnabled ? "Fast" : "Normal");
429 continue;432 continue;
430 }433 }
431 }434 }
...@@ -443,8 +446,8 @@ export function buildTraitsTriggerDisplay(input: {...@@ -443,8 +446,8 @@ export function buildTraitsTriggerDisplay(input: {
443 // Only fall back to text when fast mode is genuinely the sole trait. Keying446 // Only fall back to text when fast mode is genuinely the sole trait. Keying
444 // off an empty label list alone would also catch descriptors that resolved to447 // off an empty label list alone would also catch descriptors that resolved to
445 // no label at all, printing a bogus "Normal" for a model without fast mode.448 // no label at all, printing a bogus "Normal" for a model without fast mode.
446 if (labels.length === 0 && hasFastMode) {449 if (labels.length === 0 && fastModeFallbackLabel !== null) {
447 return { label: fastModeEnabled ? "Fast" : "Normal", showFastModeIcon: false };450 return { label: fastModeFallbackLabel, showFastModeIcon: false };
448 }451 }
449 return { label: labels.join(" · "), showFastModeIcon: fastModeEnabled };452 return { label: labels.join(" · "), showFastModeIcon: fastModeEnabled };
450}453}