Files
platform/src/apps/officer-web/state/shortcuts.ts
T
pastilhas 2e846933e9 shortcut registry keyed (96 -> 73)
shortcuts.ts is data with no component around it, so it carries catalog keys and
ShortcutsModal resolves them -- same shape as the settings sections.

gen-platform-skill.ts also reads this registry to generate the agent-facing SKILL.md,
which docs:check validates. That doc stays in one language, so the generator resolves
against en-GB directly and throws if a key is missing rather than emitting a dot-path
into a file an agent will quote at somebody. The regenerated skill is byte-identical,
which is the check that keying the registry changed nothing an agent reads.
2026-08-23 05:26:43 +00:00

135 lines
4.9 KiB
TypeScript

import type { AppTranslationKey } from '@/lib/i18n';
/*
Every keyboard shortcut the shell owns, as data.
── Why a registry ──
Ported in shape from the owner's `vimazing` (`vim-maze/src/gameInfo.ts`), where the controls are declared
once and the help screen renders them. The alternative is what this replaced: chords spelled out inside
three separate key handlers, and a help text nobody updates because it lives somewhere else.
Two things follow from having it. `Ctrl+?` can render an accurate list without anybody maintaining prose.
And when shortcuts become configurable — the next big piece — this is the table a binding UI edits, rather
than a grep across handlers.
── What it is NOT, yet ──
Declarative but not authoritative: the handlers still contain their own conditions, so a chord changed here
does not move. Making this the single source is part of the configurable-shortcuts work, and doing it now
would mean a rewrite of three handlers for no visible gain. Where a chord appears in both places the file
that owns it is named in `implementedIn`, so the pair can be found in one search.
*/
export type Shortcut = {
/** As a person reads it — `Ctrl+Shift+Space`, not a KeyboardEvent spelling. */
keys: string;
/** A catalog key. This is data with no component around it, so ShortcutsModal resolves it. */
descriptionKey: AppTranslationKey;
/** The file whose handler actually implements it, until this registry is authoritative. */
implementedIn: string;
};
export type ShortcutSection = {
titleKey: AppTranslationKey;
/** Shown under the title when the section needs a caveat that a per-row description cannot carry. */
noteKey?: AppTranslationKey;
shortcuts: Shortcut[];
};
export const SHORTCUT_SECTIONS: ShortcutSection[] = [
{
titleKey: 'shortcuts.navigation',
shortcuts: [
{
keys: 'Shift+Space',
descriptionKey: 'shortcuts.spotlight',
implementedIn: 'Layout/Spotlight/Spotlight.tsx',
},
{ keys: 'Ctrl+?', descriptionKey: 'shortcuts.thisList', implementedIn: 'Layout/ShortcutsModal.tsx' },
],
},
{
titleKey: 'shortcuts.themes',
noteKey: 'shortcuts.themesNote',
shortcuts: [
{
keys: 'Ctrl+Shift+Space',
descriptionKey: 'shortcuts.openBrowser',
implementedIn: 'theme/useThemeKeys.ts',
},
{
keys: 'Ctrl+Shift+← / →',
descriptionKey: 'shortcuts.prevNextTheme',
implementedIn: 'theme/useThemeKeys.ts',
},
{
keys: 'Ctrl+Shift+F',
descriptionKey: 'shortcuts.starCurrent',
implementedIn: 'theme/useThemeKeys.ts',
},
],
},
{
titleKey: 'shortcuts.inBrowser',
shortcuts: [
{ keys: 'j / k ↑ / ↓', descriptionKey: 'shortcuts.moveList', implementedIn: 'theme/useThemeKeys.ts' },
{
keys: 'l →',
descriptionKey: 'shortcuts.applyKeepBrowsing',
implementedIn: 'theme/useThemeKeys.ts',
},
{ keys: 'h ←', descriptionKey: 'shortcuts.restoreStart', implementedIn: 'theme/useThemeKeys.ts' },
{ keys: 'f', descriptionKey: 'shortcuts.starFocused', implementedIn: 'theme/useThemeKeys.ts' },
{ keys: 'Enter', descriptionKey: 'shortcuts.keepLooking', implementedIn: 'theme/useThemeKeys.ts' },
{ keys: 'Esc', descriptionKey: 'shortcuts.cancelRestore', implementedIn: 'theme/useThemeKeys.ts' },
],
},
{
titleKey: 'shortcuts.inSpotlight',
shortcuts: [
{ keys: '↑ / ↓', descriptionKey: 'shortcuts.moveResults', implementedIn: 'Layout/Spotlight/Spotlight.tsx' },
{ keys: 'Enter', descriptionKey: 'shortcuts.goThere', implementedIn: 'Layout/Spotlight/Spotlight.tsx' },
{ keys: 'Esc', descriptionKey: 'shortcuts.close', implementedIn: 'Layout/Spotlight/Spotlight.tsx' },
],
},
{
titleKey: 'shortcuts.chat',
noteKey: 'shortcuts.chatNote',
shortcuts: [
{
keys: '↑ / ↓',
descriptionKey: 'shortcuts.prevNextPrompt',
implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts',
},
{
keys: 'Enter',
descriptionKey: 'shortcuts.send',
implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts',
},
{
keys: 'Shift+Enter',
descriptionKey: 'shortcuts.newline',
implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts',
},
{
keys: 'Esc',
descriptionKey: 'shortcuts.stopTurn',
implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts',
},
],
},
];
/**
* Is this the `Ctrl+?` chord?
*
* `?` is `Shift+/` on most layouts and its own key on some, so both spellings are accepted rather than
* guessing which keyboard is attached. `code === 'Slash'` is the physical key, which is stable where `key`
* is not.
*/
export function isShortcutsChord(ev: KeyboardEvent): boolean {
if (!ev.ctrlKey) return false;
return ev.key === '?' || (ev.shiftKey && ev.code === 'Slash');
}