feat(web): show fast mode as a bolt instead of a "Normal" label#4488
Conversation
The traits trigger spelled out the fast-mode state as text, so the near-universal off case burned horizontal space on the word "Normal". Drop fastMode from the trigger's text label entirely and render a lightning bolt only when it's on, matching how Codex presents it. The label-building logic moves into an exported pure function so it can be tested without mounting the menu. When fast mode is the only trait, fall back to the old Fast/Normal text so the trigger doesn't render as a bare bolt or bare chevron. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d505694. Configure here.
ApprovabilityVerdict: Needs human review An unresolved review comment identifies a logic bug in the fallback condition where descriptors with empty labels incorrectly trigger the sole-trait fallback, suppressing the bolt icon. This substantive issue warrants human review. You can customize Macroscope's approvability policy. Learn more. |
Keying the Fast/Normal fallback off an empty label list also caught the case where non-fastMode descriptors resolved to no label — a select with neither a currentValue nor an isDefault option. A model without fast mode at all would then print a bogus "Normal" where it used to stay blank. Track whether a fastMode descriptor was actually present and require it for the fallback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| if (labels.length === 0 && hasFastMode) { | ||
| return { label: input.fastModeEnabled ? "Fast" : "Normal", showFastModeIcon: false }; | ||
| } |
There was a problem hiding this comment.
🟡 Medium chat/TraitsPicker.tsx:416
When a model has fastMode plus another trait (e.g. a select descriptor) whose current label resolves to empty, buildTraitsTriggerDisplay incorrectly returns "Fast"/"Normal" and suppresses the bolt icon. The fallback labels.length === 0 && hasFastMode only checks that no label was produced — it does not confirm fast mode is the only descriptor. Any non-fast descriptor whose label is empty (a valid state covered by the new tests) triggers the sole-trait fallback, dropping the real trait's display and hiding the bolt. Consider checking the total descriptor count or the presence of non-fast descriptors instead of relying on labels.length.
| if (labels.length === 0 && hasFastMode) { | |
| return { label: input.fastModeEnabled ? "Fast" : "Normal", showFastModeIcon: false }; | |
| } | |
| // Only fall back to text when fast mode is genuinely the sole trait. | |
| const nonFastModeDescriptors = input.descriptors.filter( | |
| (descriptor) => !(descriptor.id === "fastMode" && descriptor.type === "boolean"), | |
| ); | |
| if (nonFastModeDescriptors.length === 0 && hasFastMode) { | |
| return { label: input.fastModeEnabled ? "Fast" : "Normal", showFastModeIcon: false }; | |
| } |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/chat/TraitsPicker.tsx around lines 416-418:
When a model has `fastMode` plus another trait (e.g. a select descriptor) whose current label resolves to empty, `buildTraitsTriggerDisplay` incorrectly returns `"Fast"`/`"Normal"` and suppresses the bolt icon. The fallback `labels.length === 0 && hasFastMode` only checks that no label was produced — it does not confirm fast mode is the only descriptor. Any non-fast descriptor whose label is empty (a valid state covered by the new tests) triggers the sole-trait fallback, dropping the real trait's display and hiding the bolt. Consider checking the total descriptor count or the presence of non-fast descriptors instead of relying on `labels.length`.
There was a problem hiding this comment.
I checked this one against the actual behavior and I'm going to decline the suggestion — it makes the case strictly worse.
The scenario is real: fastMode + a select whose label resolves to empty. But compare what the two versions render:
| current | suggested | |
|---|---|---|
| fast on | Fast |
`` (empty, bolt only) |
| fast off | Normal |
`` (empty, bare chevron) |
Since the other descriptor produced no label, there is nothing else to display. Gating on nonFastModeDescriptors.length rather than labels.length doesn't recover the missing trait — it just skips the fallback and emits an empty label. That's the degenerate trigger the fallback exists to prevent: with fast off you get a bare chevron with no text at all.
dropping the real trait's display and hiding the bolt
There is no real trait display to drop in this branch — labels is empty precisely because that descriptor resolved to nothing. Fast/Normal is a lossless description of the only state that actually resolved, and the bolt is redundant next to the word "Fast", which is why it's suppressed.
Verified both paths directly rather than reasoning from the diff: current returns {label: "Fast", showFastModeIcon: false}; the suggested condition returns {label: "", showFastModeIcon: true}.
Worth noting this is a doubly-degenerate state — a provider would have to ship a select with neither a currentValue nor an isDefault option. No provider in the repo does. Both versions are edge-case handling for something unreachable today; I'd rather the unreachable branch degrade to readable text than to an empty button.
## What's Changed * feat(web): show fast mode as a bolt instead of a "Normal" label by @t3dotgg in pingdotgg/t3code#4488 **Full Changelog**: pingdotgg/t3code@v0.0.29-nightly.20260725.898...v0.0.29-nightly.20260725.899 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.29-nightly.20260725.899
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…dotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a)
…4309–pingdotgg#4488) (#187) * fix(client-runtime): keep a warm thread un-settled despite a merged/closed PR (pingdotgg#4309) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 193e3c6) * Fix composer context strip alignment and glass shell (pingdotgg#4404) (cherry picked from commit 6ef7aa8) * Polish iOS git progress overlay with glass effects (pingdotgg#4387) (cherry picked from commit ce467da) * Improve composer glass fallbacks (pingdotgg#4406) Co-authored-by: codex <codex@users.noreply.github.com> (cherry picked from commit 67a7b1a) * feat(web): collapse large git diffs by default to make chat more readable (pingdotgg#4409) (cherry picked from commit 51672b6) * Stop new threads inheriting checkout/branch from viewed thread (pingdotgg#4411) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 2f41c07) * fix: tone down branch-mismatch banner (pingdotgg#4416) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit fc3f78f) * fix: Claude Code skills discoverable for the composer $ picker (pingdotgg#4414) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 6b9a598) * fix(web): keep settled threads reachable when opened directly (pingdotgg#4413) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit bb38c33) * feat(sidebar-v2): thread snoozing (pingdotgg#4311) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: maria <maria@kuuro.net> (cherry picked from commit 202e560) * Upgrade Clerk packages and Expo integration (pingdotgg#4440) (cherry picked from commit 5d17354) Co-authored-by: aaditagrawal <aaditagrawal@users.noreply.github.com> * Increase light-mode contrast for user message bubbles (pingdotgg#4441) (cherry picked from commit 15e875a) * Restore model picker layout and retain iterative test state (pingdotgg#4450) Co-authored-by: codex <codex@users.noreply.github.com> (cherry picked from commit f7cc776) * Color settled PR labels on hover (pingdotgg#4451) Co-authored-by: codex <codex@users.noreply.github.com> (cherry picked from commit a7ee309) * [codex] Fix glass hover compositing artifacts (pingdotgg#4446) Co-authored-by: codex <codex@users.noreply.github.com> (cherry picked from commit ece0508) * Add Claude Opus 5 model (pingdotgg#4472) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Theo Browne <me@t3.gg> (cherry picked from commit 41a430a) * feat(web): add collapse-all toggle to diff panel (pingdotgg#4475) (cherry picked from commit 38cfc25) * feat(web): show fast mode as a bolt instead of a "Normal" label (pingdotgg#4488) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5719e8a) * fix(sync): preserve Droid medium-access with Auto runtime mode Keep fork-only medium-access in RuntimeMode alongside upstream Auto. Update presentation maps and drop obsolete provider kind from slug helper. Co-authored-by: aaditagrawal <aaditagrawal@users.noreply.github.com> * fix(sync): restore fork release and runtime-mode adaptations Keep WSL node-pty on bullseye glibc, pin releases to the fork, and stop hard-requiring upstream relay/npm publish. Scope Auto vs Medium access by provider, normalize carried modes on new threads, and fix Claude/Droid type regressions from the restack. Co-authored-by: aaditagrawal <aaditagrawal@users.noreply.github.com> * fix(sync): refresh lockfile after Clerk upgrade restack Align patchedDependencies hashes (react-native-screens) and importer entries with the current workspace patches so frozen CI installs succeed. Co-authored-by: aaditagrawal <aaditagrawal@users.noreply.github.com> --------- Co-authored-by: Theo Browne <me@t3.gg> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: maria <maria@kuuro.net> Co-authored-by: aaditagrawal <aaditagrawal@users.noreply.github.com> Co-authored-by: tomlit <tomlit@gmail.com> Co-authored-by: jan <hi@4bs3nt.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com>

The composer's traits trigger spelled out the fast-mode state as text, so the near-universal off case burned horizontal space on the word "Normal":
High · Normal · 1M.Now it works the way Codex does it — a lightning bolt when fast mode is on, and nothing at all when it's off.
High · Normal · 1MHigh · 1MHigh · 1MChanges
fastModeno longer contributes to the trigger's text label.ZapIconrenders at the start of the trigger, with ansr-only"Fast mode on" — the icon is now the only signal, so screen readers need the text.buildTraitsTriggerDisplayso it's testable without mounting the menu.Edge case
For models where fast mode is the only trait, dropping the text would leave a bare bolt or bare chevron with nothing to read. That case falls back to the old
Fast/Normaltext and skips the icon. Every model currently in the providers pairs fast mode with at least an effort select, so in practice you always get the icon path — the fallback just keeps the component from degrading if a provider ever exposes fast mode alone.Mobile is untouched:
providerOptionsConfigurationLabelalready drops boolean traits when they're off, so it never showed "Normal".Testing
pnpm --filter @t3tools/web typecheck— cleanpnpm -w run lint— clean, no new warnings on this fileTraitsPicker.test.tsand existingcomposerProviderState.test.tsxNot visually confirmed in a running browser — if the gap next to the label looks off, it's a one-line tweak to the
gap-1.5on the Codex-style span.🤖 Generated with Claude Code
Note
Low Risk
Composer UI and display logic only; behavior is covered by new unit tests with no auth or data changes.
Overview
The composer traits trigger no longer shows Fast/Normal in the joined label when fast mode sits alongside other traits. Off is silent; on adds a
ZapIconbolt (plussr-only“Fast mode on”) ahead of labels likeHigh · 1M.Label logic moves into exported
buildTraitsTriggerDisplay, with unit tests covering off/on, other booleans, ultrathink, fast-mode-only fallback text, and empty labels without bogus Normal. Codex-style triggers use slightly tighter gap and a truncating label span.Reviewed by Cursor Bugbot for commit 364966c. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Show a bolt icon instead of 'Fast'/'Normal' text for fast mode in the chat traits picker
buildTraitsTriggerDisplayin TraitsPicker.tsx, a pure function that builds the trigger label and ashowFastModeIconflag from provider option descriptors.ZapIcon(bolt) with an sr-only 'Fast mode on' label instead of the word 'Normal' or 'Fast'.ultrathinkPromptControlledmatches the primary select.📊 Macroscope summarized 364966c. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.