Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions apps/web/src/components/chat/ClaudeTraitsPicker.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ describe("ClaudeTraitsPicker", () => {
}
});

it("shows prompt-controlled Ultrathink state with disabled effort controls", async () => {
it("shows prompt-controlled Ultrathink state with selectable effort controls", async () => {
const mounted = await mountPicker({
effort: "high",
model: "claude-opus-4-6",
Expand All @@ -166,8 +166,7 @@ describe("ClaudeTraitsPicker", () => {
await vi.waitFor(() => {
const text = document.body.textContent ?? "";
expect(text).toContain("Effort");
expect(text).toContain("Remove Ultrathink from the prompt to change effort.");
expect(text).not.toContain("Fallback Effort");
expect(text).not.toContain("Remove Ultrathink from the prompt to change effort.");
});
} finally {
await mounted.cleanup();
Expand Down
17 changes: 9 additions & 8 deletions apps/web/src/components/chat/ClaudeTraitsPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ export const ClaudeTraitsMenuContent = memo(function ClaudeTraitsMenuContentImpl

const handleEffortChange = useCallback(
(value: ClaudeCodeEffort) => {
if (ultrathinkPromptControlled) return;
if (!value) return;
const nextEffort = options.find((option) => option === value);
if (!nextEffort) return;
Expand All @@ -119,6 +118,10 @@ export const ClaudeTraitsMenuContent = memo(function ClaudeTraitsMenuContentImpl
onPromptChange(nextPrompt);
return;
}
if (ultrathinkPromptControlled) {
const stripped = prompt.replace(/\bultrathink\b:?\s*/i, "");
Copy link
Contributor

Choose a reason for hiding this comment

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

Strip regex doesn't fully clear detection, causing state inconsistency

Low Severity

The stripping regex /\bultrathink\b:?\s*/i only removes the first occurrence of "ultrathink" (no g flag), but isClaudeUltrathinkPrompt detects it anywhere via /\bultrathink\b/i. If a prompt contains "ultrathink" more than once — e.g. the UI-added Ultrathink: prefix plus a natural occurrence in the body text — stripping removes only the prefix while the remaining occurrence keeps ultrathinkPromptControlled true on the next render. The radio group then still displays "Ultrathink" as selected, but setProviderModelOptions already changed the stored effort, creating an inconsistent UI/state.

Fix in Cursor Fix in Web

Copy link
Contributor Author

@Marve10s Marve10s Mar 24, 2026

Choose a reason for hiding this comment

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

The g flag deletes all instances of "ultrathink" from the user prompt.

Example

Input prompt: "Ultrathink: please ultrathink about this problem"

Without g flag

When switching away from ultrathink:

  • Strip result: "please ultrathink about this problem"
  • isClaudeUltrathinkPrompt still detects "ultrathink" → UI stays on "Ultrathink" (Bugbot's inconsistency)
  • User's natural text is preserved

With g flag

Same prompt: "Ultrathink: please ultrathink about this problem"

When switching away from ultrathink:

  • Strip result: "please about this problem"
  • isClaudeUltrathinkPrompt returns false → UI correctly switches
  • User's sentence is now mangled

I still think current version is better, we only targeting ultrathink that t3 code provides in the prompt, not written by user manually.

onPromptChange(stripped);
}
setProviderModelOptions(
threadId,
PROVIDER,
Expand Down Expand Up @@ -151,14 +154,12 @@ export const ClaudeTraitsMenuContent = memo(function ClaudeTraitsMenuContentImpl
<>
<MenuGroup>
<div className="px-2 pt-1.5 pb-1 font-medium text-muted-foreground text-xs">Effort</div>
{ultrathinkPromptControlled ? (
<div className="px-2 pb-1.5 text-muted-foreground/80 text-xs">
Remove Ultrathink from the prompt to change effort.
</div>
) : null}
<MenuRadioGroup value={effort} onValueChange={handleEffortChange}>
<MenuRadioGroup
value={ultrathinkPromptControlled ? "ultrathink" : effort}
onValueChange={handleEffortChange}
>
{options.map((option) => (
<MenuRadioItem key={option} value={option} disabled={ultrathinkPromptControlled}>
<MenuRadioItem key={option} value={option}>
{CLAUDE_EFFORT_LABELS[option]}
{option === defaultReasoningEffort ? " (default)" : ""}
</MenuRadioItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe("CompactComposerControlsMenu", () => {
}
});

it("shows prompt-controlled Ultrathink messaging with disabled effort controls", async () => {
it("shows prompt-controlled Ultrathink state with selectable effort controls", async () => {
const mounted = await mountMenu({
model: "claude-opus-4-6",
prompt: "Ultrathink:\nInvestigate this",
Expand All @@ -176,8 +176,7 @@ describe("CompactComposerControlsMenu", () => {
await vi.waitFor(() => {
const text = document.body.textContent ?? "";
expect(text).toContain("Effort");
expect(text).toContain("Remove Ultrathink from the prompt to change effort.");
expect(text).not.toContain("Fallback Effort");
expect(text).not.toContain("Remove Ultrathink from the prompt to change effort.");
});
} finally {
await mounted.cleanup();
Expand Down