diff --git a/apps/server/src/provider/model-manifest.json b/apps/server/src/provider/model-manifest.json index b7531c21c981..d9a1bc0f388e 100644 --- a/apps/server/src/provider/model-manifest.json +++ b/apps/server/src/provider/model-manifest.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-09-24T18:40:00Z", + "updatedAt": "2026-09-28T19:22:00Z", "compatibility": [ { "driver": "codex", @@ -64,7 +64,7 @@ "gpt-daybreak-blue-latest", "gpt-daybreak-red-latest" ], - "claudeAgent": ["claude-fable-5-1", "claude-opus-5-5", "claude-sonnet-5"], + "claudeAgent": ["claude-fable-5-1", "claude-opus-5-5", "claude-sonnet-5-5"], "antigravity": ["gemini-3.8-flash-high", "gemini-3.8-flash-medium", "gemini-3.8-flash-low"] }, "providers": { @@ -495,6 +495,42 @@ "claudeCode": {} } }, + "sonnet-5-5": { + "capabilities": { + "optionDescriptors": [ + { + "id": "effort", + "label": "Reasoning", + "type": "select", + "options": [ + { "id": "low", "label": "Low" }, + { "id": "medium", "label": "Medium", "isDefault": true }, + { "id": "high", "label": "High" }, + { "id": "xhigh", "label": "Extra High" }, + { "id": "max", "label": "Max" }, + { "id": "ultrathink", "label": "Ultrathink" } + ], + "promptInjectedValues": ["ultrathink"] + }, + { + "id": "contextWindow", + "label": "Context Window", + "type": "select", + "options": [ + { "id": "200k", "label": "200k", "isDefault": true }, + { "id": "1m", "label": "1M" } + ] + } + ] + }, + "adapter": { + "claudeCode": { + "effortMap": { "ultrathink": null }, + "modelSuffixes": { "contextWindow": { "1m": "[1m]" } }, + "contextWindowTokens": { "200k": 200000, "1m": 1000000 } + } + } + }, "sonnet-5": { "capabilities": { "optionDescriptors": [ @@ -731,11 +767,20 @@ "status": "legacy", "profile": "opus-4-5" }, + { + "slug": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "aliases": ["sonnet-5.5", "claude-sonnet-5.5"], + "status": "current", + "badge": "new", + "profile": "sonnet-5-5", + "adapter": { "claudeCode": { "minVersion": "2.1.284" } } + }, { "slug": "claude-sonnet-5", "name": "Claude Sonnet 5", "aliases": ["sonnet", "sonnet-5", "claude-sonnet-5.0", "claude-sonnet-5-0"], - "status": "current", + "status": "legacy", "profile": "sonnet-5" }, { diff --git a/apps/web/src/shortcutModifierState.test.ts b/apps/web/src/shortcutModifierState.test.ts index 4ef4b3b6bf2d..20b63220072d 100644 --- a/apps/web/src/shortcutModifierState.test.ts +++ b/apps/web/src/shortcutModifierState.test.ts @@ -1,8 +1,13 @@ -import { describe, expect, it } from "vite-plus/test"; +// @vitest-environment jsdom + +import { act, createElement } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { areShortcutModifierStatesEqual, shortcutModifierStateAfterKeyboardEvent, + useShortcutModifierState, type ShortcutModifierState, } from "./shortcutModifierState"; @@ -13,6 +18,67 @@ const emptyState = (): ShortcutModifierState => ({ shiftKey: false, }); +describe("useShortcutModifierState", () => { + let root: Root; + let container: HTMLDivElement; + + beforeEach(() => { + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); + container = document.createElement("div"); + document.body.append(container); + root = createRoot(container); + }); + + afterEach(async () => { + await act(async () => root.unmount()); + container.remove(); + vi.unstubAllGlobals(); + }); + + it.each(["keyup", "paste", "blur"] as const)( + "does not render for unchanged modifiers after %s resets the state", + async (reset) => { + const render = vi.fn(); + function Consumer() { + render(useShortcutModifierState()); + return null; + } + await act(async () => { + root.render(createElement(Consumer)); + }); + const press = async (type: "keydown" | "keyup", key: string, shiftKey = false) => { + await act(async () => { + window.dispatchEvent(new KeyboardEvent(type, { key, shiftKey })); + }); + }; + + await press("keydown", "Shift", true); + expect(render).toHaveBeenLastCalledWith({ ...emptyState(), shiftKey: true }); + if (reset === "keyup") { + await press("keyup", "Shift"); + } else { + await act(async () => { + window.dispatchEvent(new Event(reset)); + }); + } + expect(render).toHaveBeenLastCalledWith(emptyState()); + render.mockClear(); + + for (const key of "typing") { + await press("keydown", key); + await press("keyup", key); + } + await act(async () => { + window.dispatchEvent(new Event("paste")); + window.dispatchEvent(new Event("blur")); + }); + expect(render).not.toHaveBeenCalled(); + await press("keydown", "Shift", true); + expect(render).toHaveBeenCalledExactlyOnceWith({ ...emptyState(), shiftKey: true }); + }, + ); +}); + function keyboardEventLike(type: "keydown" | "keyup", init: Partial): KeyboardEvent { return { type, diff --git a/apps/web/src/shortcutModifierState.ts b/apps/web/src/shortcutModifierState.ts index 3abeeaa3e8aa..15d6e0a1bcae 100644 --- a/apps/web/src/shortcutModifierState.ts +++ b/apps/web/src/shortcutModifierState.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; export interface ShortcutModifierState { metaKey: boolean; @@ -28,10 +28,18 @@ export function areShortcutModifierStatesEqual( export function useShortcutModifierState(): ShortcutModifierState { const [state, setState] = useState(EMPTY_SHORTCUT_MODIFIER_STATE); + const stateRef = useRef(EMPTY_SHORTCUT_MODIFIER_STATE); useEffect(() => { + const updateState = (next: ShortcutModifierState) => { + // Even a no-op state dispatch can cost work in the sidebar's large tree. + // Ordinary typing must return before dispatching a React update. + if (areShortcutModifierStatesEqual(stateRef.current, next)) return; + stateRef.current = next; + setState(next); + }; const onKeyboardEvent = (event: KeyboardEvent) => { - setState((current) => shortcutModifierStateAfterKeyboardEvent(current, event)); + updateState(shortcutModifierStateAfterKeyboardEvent(stateRef.current, event)); }; // Dictation tools (Wispr Flow) paste with a synthetic ⌘V whose Meta keyup // never reaches the page, so the tracked state stays "⌘ held" forever and @@ -39,11 +47,7 @@ export function useShortcutModifierState(): ShortcutModifierState { // treat it like a blur and reset. A physically held modifier re-registers // on the next real key event. const onResetEvent = () => { - setState((current) => - areShortcutModifierStatesEqual(current, EMPTY_SHORTCUT_MODIFIER_STATE) - ? current - : EMPTY_SHORTCUT_MODIFIER_STATE, - ); + updateState(EMPTY_SHORTCUT_MODIFIER_STATE); }; window.addEventListener("keydown", onKeyboardEvent, true);