e626d33a7c
* fix(web): make toggle-sidebar shortcut work on every route TOGGLE_SIDEBAR (Cmd/Ctrl+B) was handled only inside useEditorKeybinds, mounted just by the dockview desktop layout, so it was dead on Kanban, the task list, Office, Settings, etc. Lift it to a global app-root listener (useAppShortcuts, mounted in GlobalCommands) that honors user overrides via getShortcut and keeps the isEditableTarget guard, and drop the now-duplicate branch from useEditorKeybinds. Adds a hook unit test and a Playwright e2e covering Kanban, Settings, and the dockview session. * fix(web): hydrate userSettings into the root store on settings routes Each StateProvider creates its own store; the settings/general page mounts a nested one that the root-mounted GlobalCommands/AppSidebar can't see. The settings layout hydrated the root store but omitted userSettings, so override-driven global shortcuts (TOGGLE_SIDEBAR, Quick Chat) were dead on every /settings route. Hydrate userSettings there too, matching the kanban/tasks/office/github routes. * test(web): cover the macOS Cmd path; note the settings-layout catch trade-off Address review feedback: add a metaKey (Cmd) variant to the useAppShortcuts test so both ctrlOrCmd branches are exercised, and document that the settings layout's outer catch intentionally drops userSettings overrides in the already-degraded error path.
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { useAppStoreApi } from "@/components/state-provider";
|
|
import { matchesShortcut } from "@/lib/keyboard/utils";
|
|
import { getShortcut } from "@/lib/keyboard/shortcut-overrides";
|
|
|
|
/** Returns true if the active element is a text input or contenteditable. */
|
|
function isEditableTarget(e: KeyboardEvent): boolean {
|
|
const tag = (e.target as HTMLElement)?.tagName;
|
|
return (
|
|
tag === "INPUT" || tag === "TEXTAREA" || (e.target as HTMLElement)?.isContentEditable === true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* App-root keyboard shortcuts that must fire on every route — not just inside
|
|
* the dockview session editor.
|
|
*
|
|
* Currently handles `TOGGLE_SIDEBAR` (collapse/expand the global AppSidebar).
|
|
* Previously this lived in {@link useEditorKeybinds}, which is mounted only by
|
|
* the dockview desktop layout, so the shortcut was dead on the Kanban board,
|
|
* task list, Office, Settings, etc. Mount this once near the app root (it has
|
|
* no dockview dependency) so the binding works everywhere.
|
|
*
|
|
* The AppSidebar is desktop-only (`hidden md:flex`); on mobile the toggle still
|
|
* flips store state but has no visible effect, which is fine.
|
|
*/
|
|
export function useAppShortcuts() {
|
|
const appStore = useAppStoreApi();
|
|
|
|
useEffect(() => {
|
|
const handler = (e: KeyboardEvent) => {
|
|
if (isEditableTarget(e)) return;
|
|
|
|
const overrides = appStore.getState().userSettings.keyboardShortcuts;
|
|
if (matchesShortcut(e, getShortcut("TOGGLE_SIDEBAR", overrides))) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
appStore.getState().toggleAppSidebar();
|
|
}
|
|
};
|
|
|
|
// Capture phase so we win the event before focus-trapped surfaces (e.g.
|
|
// xterm.js) can swallow it — mirrors useEditorKeybinds.
|
|
window.addEventListener("keydown", handler, true);
|
|
return () => window.removeEventListener("keydown", handler, true);
|
|
}, [appStore]);
|
|
}
|