authorgravatar for 126423069+Marve10s@users.noreply.github.comIbrahim Elkamali <126423069+Marve10s@users.noreply.github.com> 2026-03-28 20:38:29+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-28 11:38:29-07:00
log61f983098be44316944e77e3bc81538a6a8fb0e9
tree585b5ef2e9dbeb07ba43cb17372ef4b4b53f598b
parent32c1f98a02d5cf8e42d70dbd65822f9b08151024
signature Signed by PGP key B5690EEEBB952194

fix(web): allow switching away from Ultrathink without manual prompt editing (#1307)


3 files changed, 60 insertions(+), 11 deletions(-)

apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx+22-3
......@@ -220,7 +220,7 @@ describe("CompactComposerControlsMenu", () => {
220220 });
221221 });
222222
223 it("shows prompt-controlled Ultrathink messaging with disabled effort controls", async () => {
223 it("shows prompt-controlled Ultrathink state with selectable effort controls", async () => {
224224 await using _ = await mountMenu({
225225 modelSelection: {
226226 provider: "claudeAgent",
......@@ -235,8 +235,27 @@ describe("CompactComposerControlsMenu", () => {
235235 await vi.waitFor(() => {
236236 const text = document.body.textContent ?? "";
237237 expect(text).toContain("Effort");
238 expect(text).toContain("Remove Ultrathink from the prompt to change effort.");
239 expect(text).not.toContain("Fallback Effort");
238 expect(text).not.toContain("ultrathink");
239 });
240 });
241
242 it("warns when ultrathink appears in prompt body text", async () => {
243 await using _ = await mountMenu({
244 modelSelection: {
245 provider: "claudeAgent",
246 model: "claude-opus-4-6",
247 options: { effort: "high" },
248 },
249 prompt: "Ultrathink:\nplease ultrathink about this problem",
250 });
251
252 await page.getByLabelText("More composer controls").click();
253
254 await vi.waitFor(() => {
255 const text = document.body.textContent ?? "";
256 expect(text).toContain(
257 'Your prompt contains "ultrathink" in the text. Remove it to change effort.',
258 );
240259 });
241260 });
242261});
apps/web/src/components/chat/TraitsPicker.browser.tsx+19-3
......@@ -296,7 +296,7 @@ describe("TraitsPicker (Claude)", () => {
296296 });
297297 });
298298
299 it("shows prompt-controlled Ultrathink state with disabled effort controls", async () => {
299 it("shows prompt-controlled Ultrathink state with selectable effort controls", async () => {
300300 await using _ = await mountClaudePicker({
301301 model: "claude-opus-4-6",
302302 options: { effort: "high" },
......@@ -312,8 +312,24 @@ describe("TraitsPicker (Claude)", () => {
312312 await vi.waitFor(() => {
313313 const text = document.body.textContent ?? "";
314314 expect(text).toContain("Effort");
315 expect(text).toContain("Remove Ultrathink from the prompt to change effort.");
316 expect(text).not.toContain("Fallback Effort");
315 expect(text).not.toContain("ultrathink");
316 });
317 });
318
319 it("warns when ultrathink appears in prompt body text", async () => {
320 await using _ = await mountClaudePicker({
321 model: "claude-opus-4-6",
322 options: { effort: "high" },
323 prompt: "Ultrathink:\nplease ultrathink about this problem",
324 });
325
326 await page.getByRole("button").click();
327
328 await vi.waitFor(() => {
329 const text = document.body.textContent ?? "";
330 expect(text).toContain(
331 'Your prompt contains "ultrathink" in the text. Remove it to change effort.',
332 );
317333 });
318334 });
319335
apps/web/src/components/chat/TraitsPicker.tsx+19-5
......@@ -120,6 +120,10 @@ function getSelectedTraits(
120120 caps.promptInjectedEffortLevels.length > 0 &&
121121 isClaudeUltrathinkPrompt(prompt);
122122
123 // Check if "ultrathink" appears in the body text (not just our prefix)
124 const ultrathinkInBodyText =
125 ultrathinkPromptControlled && isClaudeUltrathinkPrompt(prompt.replace(/^Ultrathink:\s*/i, ""));
126
123127 return {
124128 caps,
125129 effort,
......@@ -130,6 +134,7 @@ function getSelectedTraits(
130134 contextWindow,
131135 defaultContextWindow,
132136 ultrathinkPromptControlled,
137 ultrathinkInBodyText,
133138 };
134139}
135140
......@@ -176,12 +181,12 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
176181 contextWindow,
177182 defaultContextWindow,
178183 ultrathinkPromptControlled,
184 ultrathinkInBodyText,
179185 } = getSelectedTraits(provider, models, model, prompt, modelOptions, allowPromptInjectedEffort);
180186 const defaultEffort = getDefaultEffort(caps);
181187
182188 const handleEffortChange = useCallback(
183189 (value: string) => {
184 if (ultrathinkPromptControlled) return;
185190 if (!value) return;
186191 const nextOption = effortLevels.find((option) => option.value === value);
187192 if (!nextOption) return;
......@@ -193,6 +198,11 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
193198 onPromptChange(nextPrompt);
194199 return;
195200 }
201 if (ultrathinkInBodyText) return;
202 if (ultrathinkPromptControlled) {
203 const stripped = prompt.replace(/^Ultrathink:\s*/i, "");
204 onPromptChange(stripped);
205 }
196206 const effortKey = provider === "codex" ? "reasoningEffort" : "effort";
197207 updateModelOptions(
198208 buildNextOptions(provider, modelOptions, { [effortKey]: nextOption.value }),
......@@ -200,6 +210,7 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
200210 },
201211 [
202212 ultrathinkPromptControlled,
213 ultrathinkInBodyText,
203214 modelOptions,
204215 onPromptChange,
205216 updateModelOptions,
......@@ -220,17 +231,20 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
220231 <>
221232 <MenuGroup>
222233 <div className="px-2 pt-1.5 pb-1 font-medium text-muted-foreground text-xs">Effort</div>
223 {ultrathinkPromptControlled ? (
234 {ultrathinkInBodyText ? (
224235 <div className="px-2 pb-1.5 text-muted-foreground/80 text-xs">
225 Remove Ultrathink from the prompt to change effort.
236 Your prompt contains &quot;ultrathink&quot; in the text. Remove it to change effort.
226237 </div>
227238 ) : null}
228 <MenuRadioGroup value={effort} onValueChange={handleEffortChange}>
239 <MenuRadioGroup
240 value={ultrathinkPromptControlled ? "ultrathink" : effort}
241 onValueChange={handleEffortChange}
242 >
229243 {effortLevels.map((option) => (
230244 <MenuRadioItem
231245 key={option.value}
232246 value={option.value}
233 disabled={ultrathinkPromptControlled}
247 disabled={ultrathinkInBodyText}
234248 >
235249 {option.label}
236250 {option.value === defaultEffort ? " (default)" : ""}