Skip to content

feat(web): show fast mode as a bolt instead of a "Normal" label#4488

Merged
t3dotgg merged 2 commits into
mainfrom
t3code/hide-normal-mode-indicator
Jul 25, 2026
Merged

feat(web): show fast mode as a bolt instead of a "Normal" label#4488
t3dotgg merged 2 commits into
mainfrom
t3code/hide-normal-mode-indicator

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Jul 24, 2026

Copy link
Copy Markdown
Member

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.

before after (fast off) after (fast on)
High · Normal · 1M High · 1M High · 1M
image

Changes

  • fastMode no longer contributes to the trigger's text label.
  • When fast mode is on, a ZapIcon renders at the start of the trigger, with an sr-only "Fast mode on" — the icon is now the only signal, so screen readers need the text.
  • Label-building moved into an exported pure function buildTraitsTriggerDisplay so 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/Normal text 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: providerOptionsConfigurationLabel already drops boolean traits when they're off, so it never showed "Normal".

Testing

  • pnpm --filter @t3tools/web typecheck — clean
  • pnpm -w run lint — clean, no new warnings on this file
  • 15 tests pass across the new TraitsPicker.test.ts and existing composerProviderState.test.tsx

Not 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.5 on 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 ZapIcon bolt (plus sr-only “Fast mode on”) ahead of labels like High · 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

  • Introduces buildTraitsTriggerDisplay in TraitsPicker.tsx, a pure function that builds the trigger label and a showFastModeIcon flag from provider option descriptors.
  • When fast mode is on, the trigger renders a ZapIcon (bolt) with an sr-only 'Fast mode on' label instead of the word 'Normal' or 'Fast'.
  • 'Fast'/'Normal' text is only used as a fallback when fast mode is the sole active trait; 'Ultrathink' is used when ultrathinkPromptControlled matches the primary select.
  • Behavioral Change: triggers that previously showed 'Fast' or 'Normal' text alongside other traits will now show only the bolt icon; triggers with no other traits and no fast mode will be blank.
📊 Macroscope summarized 364966c. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

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>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a417ed8-ae4b-4f6d-a448-4e840f0267c6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/hide-normal-mode-indicator

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Jul 24, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread apps/web/src/components/chat/TraitsPicker.tsx
@macroscopeapp

macroscopeapp Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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>
Comment on lines +416 to +418
if (labels.length === 0 && hasFastMode) {
return { label: input.fastModeEnabled ? "Fast" : "Normal", showFastModeIcon: false };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Suggested change
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`.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@t3dotgg
t3dotgg merged commit 5719e8a into main Jul 25, 2026
16 checks passed
@t3dotgg
t3dotgg deleted the t3code/hide-normal-mode-indicator branch July 25, 2026 02:28
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Jul 25, 2026
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…dotgg#4488)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 5719e8a)
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Jul 25, 2026
…4309pingdotgg#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant