Skip to content
Merged
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
51 changes: 48 additions & 3 deletions apps/server/src/provider/model-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"updatedAt": "2026-09-24T18:40:00Z",
"updatedAt": "2026-09-28T19:22:00Z",
"compatibility": [
{
"driver": "codex",
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -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"
},
{
Expand Down
68 changes: 67 additions & 1 deletion apps/web/src/shortcutModifierState.test.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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>): KeyboardEvent {
return {
type,
Expand Down
18 changes: 11 additions & 7 deletions apps/web/src/shortcutModifierState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";

export interface ShortcutModifierState {
metaKey: boolean;
Expand Down Expand Up @@ -28,22 +28,26 @@ 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
// the thread jump hints stick on screen. A paste is never jump intent, so
// 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);
Expand Down
Loading