Files
kandev/apps/web/hooks/use-app-shortcuts.ts
T
José Almeida e626d33a7c fix(web): make toggle-sidebar shortcut work on every route (#1283)
* 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.
2026-06-06 23:09:12 +01:00

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]);
}