mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
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:
@@ -0,0 +1,35 @@
|
||||
import { beforeEach, describe, expect, test } from 'vitest';
|
||||
import { createSnippet } from '@core/snippet';
|
||||
import { useNotificationStore } from '../stores/NotificationStore';
|
||||
import { useSnippetStore } from '../stores/SnippetStore';
|
||||
import { publishActiveSnippet } from './snippet-actions';
|
||||
|
||||
const T = new Date('2026-06-01T00:00:00Z');
|
||||
const snippets = () => useSnippetStore.getState();
|
||||
const toasts = () => useNotificationStore.getState().notifications;
|
||||
|
||||
beforeEach(() => {
|
||||
useSnippetStore.getState().reset();
|
||||
useNotificationStore.getState().clear();
|
||||
});
|
||||
|
||||
describe('publishActiveSnippet', () => {
|
||||
test('publishes the active draft and raises one success toast', () => {
|
||||
snippets().hydrate([createSnippet({ id: 'a', now: T })], 'a');
|
||||
snippets().updateDraft('{"published":1}');
|
||||
|
||||
publishActiveSnippet();
|
||||
|
||||
expect(snippets().snippets.find((s) => s.id === 'a')?.spec).toBe('{"published":1}');
|
||||
expect(toasts()).toHaveLength(1);
|
||||
expect(toasts()[0]).toMatchObject({ kind: 'success', title: 'Snippet published' });
|
||||
});
|
||||
|
||||
test('is a silent no-op when no snippet is active', () => {
|
||||
snippets().hydrate([], null);
|
||||
|
||||
publishActiveSnippet();
|
||||
|
||||
expect(toasts()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Snippet action helpers that pair a store mutation with its user-facing
|
||||
* confirmation, so every trigger of an action behaves identically.
|
||||
*
|
||||
* `publishActiveSnippet` is the single publish-with-confirmation path: the
|
||||
* Publish button (SpecEditor) and the Cmd/Ctrl+S shortcut (EventRouter) both go
|
||||
* through it, so the success toast (spec §03D) fires no matter how the user
|
||||
* published. Without this, Cmd+S published silently while the button toasted — a
|
||||
* trigger-dependent inconsistency.
|
||||
*/
|
||||
|
||||
import { notify } from '../stores/NotificationStore';
|
||||
import { useSnippetStore } from '../stores/SnippetStore';
|
||||
|
||||
/**
|
||||
* Publish the active snippet's draft and confirm it. No-op (no toast) when no
|
||||
* snippet is active. The toast copy follows the council's rule (architecture 10
|
||||
* → Toast copy): the title states the action, the message adds the consequence.
|
||||
*/
|
||||
export function publishActiveSnippet(): void {
|
||||
if (!useSnippetStore.getState().activeSnippetId) return;
|
||||
useSnippetStore.getState().publish();
|
||||
notify({
|
||||
kind: 'success',
|
||||
title: 'Snippet published',
|
||||
message: 'Your draft is now the published version.',
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user