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
2 changes: 2 additions & 0 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6652,6 +6652,8 @@ export default function ChatView(props: ChatViewProps) {
previewFocus: isPreviewFocused(),
previewOpen: previewPanelOpen,
modelPickerOpen: composerRef.current?.isModelPickerOpen() ?? false,
isWeb: !isElectron,
isDesktop: isElectron,
}),
[composerRef, previewPanelOpen, terminalUiState.terminalOpen],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ describe.each([
terminalOpen: false,
previewFocus: false,
previewOpen: false,
isWeb: true,
isDesktop: false,
})}
/>,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,15 @@ describe("KeybindingsSettings.logic", () => {
const options = buildWhenVariableOptions();

expect(options).toEqual(
expect.arrayContaining(["terminalFocus", "terminalOpen", "modelPickerOpen", "true", "false"]),
expect.arrayContaining([
"terminalFocus",
"terminalOpen",
"isWeb",
"isDesktop",
"modelPickerOpen",
"true",
"false",
]),
);
expect(options).not.toContain("customModeActive");
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ export interface KeybindingRow {
export type WhenVariableOption = string;
export type KeybindingCommandOption = KeybindingCommand;

const CORE_WHEN_VARIABLES = ["terminalFocus", "terminalOpen", "true", "false"] as const;
const CORE_WHEN_VARIABLES = [
"terminalFocus",
"terminalOpen",
"isWeb",
"isDesktop",
"true",
"false",
] as const;

const DEFAULT_WHEN_VARIABLES = new Set<string>(CORE_WHEN_VARIABLES);
for (const binding of DEFAULT_RESOLVED_KEYBINDINGS) {
Expand Down
63 changes: 62 additions & 1 deletion apps/web/src/keybindings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,43 @@ describe("thread navigation helpers", () => {
}),
);
});

it("keeps default thread jumps off the web so the browser can switch tabs", () => {
const input = event({ key: "1", metaKey: true });
assert.isNull(
resolveShortcutCommand(input, DEFAULT_RESOLVED_KEYBINDINGS, {
platform: "MacIntel",
context: { isDesktop: false },
}),
);
assert.strictEqual(
resolveShortcutCommand(input, DEFAULT_RESOLVED_KEYBINDINGS, {
platform: "MacIntel",
context: { isDesktop: true },
}),
"thread.jump.1",
);
assert.isFalse(
shouldShowThreadJumpHintsForModifiers(
event({ metaKey: true }),
DEFAULT_RESOLVED_KEYBINDINGS,
{
platform: "MacIntel",
context: { isDesktop: false },
},
),
);
assert.isTrue(
shouldShowThreadJumpHintsForModifiers(
event({ metaKey: true }),
DEFAULT_RESOLVED_KEYBINDINGS,
{
platform: "MacIntel",
context: { isDesktop: true },
},
),
);
});
});

describe("model picker navigation helpers", () => {
Expand All @@ -546,6 +583,30 @@ describe("model picker navigation helpers", () => {
assert.strictEqual(modelPickerJumpIndexFromCommand("modelPicker.jump.3"), 2);
assert.isNull(modelPickerJumpIndexFromCommand("thread.jump.1"));
});

it("keeps default model jumps off the web even while the picker is open", () => {
const input = event({ key: "3", metaKey: true });
assert.isNull(
resolveShortcutCommand(input, DEFAULT_RESOLVED_KEYBINDINGS, {
platform: "MacIntel",
context: { isDesktop: false, modelPickerOpen: true },
}),
);
assert.strictEqual(
resolveShortcutCommand(input, DEFAULT_RESOLVED_KEYBINDINGS, {
platform: "MacIntel",
context: { isDesktop: true, modelPickerOpen: true },
}),
"modelPicker.jump.3",
);
assert.strictEqual(
resolveShortcutCommand(input, DEFAULT_RESOLVED_KEYBINDINGS, {
platform: "MacIntel",
context: { isDesktop: true, modelPickerOpen: false },
}),
"thread.jump.3",
);
});
});

describe("chat/editor shortcuts", () => {
Expand Down Expand Up @@ -1106,7 +1167,7 @@ describe("composer and pull request shortcuts", () => {
}
});

it.each(["terminalOpen", "previewFocus", "previewOpen", "modelPickerOpen"])(
it.each(["terminalOpen", "previewFocus", "previewOpen", "modelPickerOpen", "isWeb", "isDesktop"])(
"honors custom PR shortcut conditions for %s",
(condition) => {
const bindings = compileResolvedKeybindingsConfig([
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/keybindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type ModelPickerJumpKeybindingCommand,
type ThreadJumpKeybindingCommand,
} from "@t3tools/contracts";
import { isElectron } from "./env";
import { isMacPlatform } from "./lib/utils";

export interface ShortcutEventLike {
Expand All @@ -33,6 +34,8 @@ export interface ShortcutMatchContext {
terminalOpen: boolean;
previewFocus: boolean;
previewOpen: boolean;
isWeb: boolean;
isDesktop: boolean;
[key: string]: boolean;
}

Expand Down Expand Up @@ -144,6 +147,8 @@ function resolveContext(options: ShortcutMatchOptions | undefined): ShortcutMatc
terminalOpen: false,
previewFocus: false,
previewOpen: false,
isWeb: !isElectron,
isDesktop: isElectron,
...options?.context,
};
}
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/routes/_chat.pull-requests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ function getShortcutContext() {
previewFocus: false,
previewOpen: false,
modelPickerOpen: false,
isWeb: !isElectron,
isDesktop: isElectron,
};
}

Expand Down
9 changes: 8 additions & 1 deletion docs/user/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,14 @@ Join modifiers and a key with `+`, such as `mod+shift+d` or `ctrl+l`.
## When conditions

Available context keys are `terminalFocus`, `terminalOpen`, `previewFocus`,
`previewOpen`, and `modelPickerOpen`. Unknown keys evaluate to `false`.
`previewOpen`, `modelPickerOpen`, `isWeb`, and `isDesktop`. `isWeb` is true in a
browser tab. `isDesktop` is true in the desktop app. Unknown keys evaluate to
`false`.

`mod+1` through `mod+9` jump to the first nine threads, and to models while the
model picker is open. Those defaults use `isDesktop` so they do not steal the
browser's tab-switch shortcuts. Remove that condition in Settings if you want
the same jumps in a browser.

Combine keys with `!` for not, `&&` for and, `||` for or, and parentheses:

Expand Down
3 changes: 2 additions & 1 deletion packages/shared/src/keybindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,12 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray<KeybindingRule> = [
...THREAD_JUMP_KEYBINDING_COMMANDS.map((command, index) => ({
key: `mod+${index + 1}`,
command,
when: "isDesktop",
})),
...MODEL_PICKER_JUMP_KEYBINDING_COMMANDS.map((command, index) => ({
key: `mod+${index + 1}`,
command,
when: "modelPickerOpen",
when: "modelPickerOpen && isDesktop",
})),
];

Expand Down
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading