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