Chords were string literals at each `keys.bind` call site, so the only thing that knew a command existed was the line that bound it. Twenty-seven of the thirty-three shortcuts appeared nowhere in the product or the docs, five menu rows showed Mac glyphs to Windows users, and one advertised `⌘Z` for a feature `⌘Z` has never run. The only reference was six hand-written rows in `README.md`, and its copy under `apps/cli/` had already drifted. `keys/catalog.ts` is the declaration: chord, title, sentence, mode, surface, and where a scoped command applies. A binding supplies only `run`, `when` and `within`. `CommandId` is a union, so a mistyped id is a compile error rather than a shortcut that quietly never fires. Three surfaces read it. `keys/shortcuts-panel.ts` is the `?` sheet, grouped and marking what is live right now; `keys/palette.ts` is `⌘K`; and `MenuItem.command` renders a menu row's chord so nothing spells one by hand — `catalog.test.ts` fails any `hint:` literal that looks like a keystroke. `scripts/gen-controls.mjs` is the fourth reader. It imports the `.ts` catalog directly under `--experimental-strip-types`, which is why that module may contain no value imports, and writes `CONTROLS.md` plus the short table in `README.md`. `keys/controls-doc.test.ts` byte-compares the committed files against the same renderers, so drift fails the suite even on a Node that cannot strip types. The tooltip chip moves with it. It used to be found by matching the tip's own *text* against a binding's label — elegant until someone reworded a tip, at which point the chip vanished with nothing failing. A control now names its command with `data-key`.
56 lines
1.9 KiB
TypeScript
56 lines
1.9 KiB
TypeScript
/**
|
|
* Types for the two renderers `gen-controls.mjs` exports.
|
|
*
|
|
* Hand-written and deliberately structural. The script is plain JavaScript
|
|
* because every generator in this repo is — it runs from a Makefile target and
|
|
* a CI step, neither of which builds anything first — but
|
|
* `packages/overlay/src/keys/controls-doc.test.ts` imports the same two
|
|
* functions so that the drift gate and the writer cannot diverge, and that test
|
|
* is TypeScript.
|
|
*
|
|
* The parameter shapes are the fields the renderers actually read, not the full
|
|
* `CommandSpec` and `GestureSpec` — importing those from `packages/overlay`
|
|
* would point a repo-root script at a workspace package's internals for no gain,
|
|
* and the real definitions are checked where they live.
|
|
*/
|
|
|
|
interface RenderedCommand {
|
|
readonly display?: string;
|
|
readonly doc: string;
|
|
readonly essential?: boolean;
|
|
readonly group: string;
|
|
readonly keys: readonly string[];
|
|
readonly mode: string;
|
|
readonly primary?: readonly string[];
|
|
readonly surface: string;
|
|
readonly title: string;
|
|
readonly where?: string;
|
|
}
|
|
|
|
interface RenderedGesture {
|
|
readonly doc: string;
|
|
readonly essential?: boolean;
|
|
readonly input: string;
|
|
/** The Windows/Linux spelling, when it differs from `input`. */
|
|
readonly inputPc?: string;
|
|
readonly mode: string;
|
|
readonly surface: string;
|
|
readonly title: string;
|
|
}
|
|
|
|
interface RenderInput {
|
|
readonly commands: readonly RenderedCommand[];
|
|
readonly displayChord: (chord: string, platform: "mac" | "pc") => string;
|
|
readonly gestures: readonly RenderedGesture[];
|
|
readonly groups: readonly string[];
|
|
readonly notes: readonly string[];
|
|
}
|
|
|
|
/** The whole of `CONTROLS.md`. */
|
|
export function renderControls(input: RenderInput): string;
|
|
|
|
/** The short table between the markers in `README.md`. */
|
|
export function renderEssentials(
|
|
input: Omit<RenderInput, "groups" | "notes">
|
|
): string;
|