Add global keyboard EventRouter and unify publish (M6, §01D)

One module owns the document keydown listener and dispatches the shortcut map
(Cmd/Ctrl+Shift+N / +K / +S / +, / Escape), platform-aware, via the single-source
focus-utils interactive-context gate. Escape and Cmd/Ctrl+S run before the gate so
save works while editing; the rest are suppressed mid-typing. Publish + its toast
move into services/snippet-actions so the button and Cmd/Ctrl+S behave identically.
Removes the ad-hoc keydown handler from App and Monaco's Cmd+S command.
This commit is contained in:
2026-06-07 17:18:55 +03:00
parent f365258fcc
commit 807d3c8e3c
12 changed files with 401 additions and 48 deletions
+1 -20
View File
@@ -8,8 +8,7 @@ import { SnippetLibrary } from './components/SnippetLibrary';
import { SpecEditor } from './components/SpecEditor';
import { ThemeToggle } from './components/ThemeToggle';
import { Toaster } from './components/Toaster';
import { openModal, setConfirm, toggleDatasets } from './modals/ModalCoordinator';
import { openSettingsPopover } from './stores/SettingsPopoverStore';
import { openModal, setConfirm } from './modals/ModalCoordinator';
import { confirm } from './stores/ConfirmStore';
import { usePanesStore } from './stores/PanesStore';
import { exportWorkspace, importWorkspace } from './services/transfer';
@@ -47,24 +46,6 @@ export function App() {
setConfirm((message) => confirm({ title: 'Discard changes?', message, danger: true }));
}, []);
// Cmd/Ctrl+K toggles the Datasets manager (spec §05); Cmd/Ctrl+, opens the
// editor settings popover — the primary preference cluster, now that settings
// are distributed per pane (spec §07). Full key router lands in M6 (arch 04).
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (!(e.metaKey || e.ctrlKey)) return;
if (e.key === 'k' || e.key === 'K') {
e.preventDefault();
toggleDatasets();
} else if (e.key === ',') {
e.preventDefault();
openSettingsPopover('editor-settings');
}
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, []);
const handleImportFile = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
// Reset the input so picking the same file again still fires onChange.