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
+8 -16
View File
@@ -27,6 +27,7 @@ import { openModal } from '../modals/ModalCoordinator';
import { useAppStore } from '../stores/AppStore';
import { confirm } from '../stores/ConfirmStore';
import { hasInlineData } from '../stores/ExtractStore';
import { publishActiveSnippet } from '../services/snippet-actions';
import { notify } from '../stores/NotificationStore';
import { usePreviewStore } from '../stores/PreviewStore';
import { selectActiveSnippet, selectShownText, useSnippetStore } from '../stores/SnippetStore';
@@ -152,18 +153,9 @@ function EditorToolbar() {
(s) => s.activeSnippetId !== null && hasInlineData(s.draftText),
);
const handlePublish = () => {
if (!useSnippetStore.getState().activeSnippetId) return;
useSnippetStore.getState().publish();
// Success confirmation (spec §03D). Per the council's toast-copy rule
// (docs/architecture/10 → Toast copy), the title states the action and the
// message adds the consequence rather than paraphrasing it.
notify({
kind: 'success',
title: 'Snippet published',
message: 'Your draft is now the published version.',
});
};
// Publish + its success toast live in one place (services/snippet-actions) so
// the button and the Cmd/Ctrl+S shortcut (EventRouter) behave identically.
const handlePublish = publishActiveSnippet;
const handleRevert = async () => {
const ok = await confirm({
@@ -275,10 +267,10 @@ export function SpecEditor() {
}
});
// Cmd/Ctrl+S publishes the current draft (spec §03D → Publish).
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
if (useSnippetStore.getState().activeSnippetId) useSnippetStore.getState().publish();
});
// Cmd/Ctrl+S is owned globally by the EventRouter (docs/architecture/04 →
// "bind listeners in exactly one place"), which publishes before the
// interactive-context gate so it works while the editor has focus. Monaco
// binds no default for Cmd+S, so the keystroke bubbles to that one handler.
return () => {
sub.dispose();