Skip to main content

Core keyboard layer

Before v0.7.282 keyboard behaviour was per-module: Sales' cart handled Escape one way, the tender modal handled it another, the Customers detail popup didn't handle it at all. The result was that an operator who hit Escape in a nested modal-inside-a-modal never knew which layer was going to close. core/keys.ts puts one LIFO stack in the Kernel and every layer pushes onto it.

Shippedv0.7.282
Substratesrc/core/keys.ts
APIwindow.kbKeys — pushLayer, register, onEscClear

The LIFO Escape stack​

Every modal / popup / active edit calls kbKeys.pushLayer({ onEsc }) when it opens and receives back a disposer to call on close. Pressing Escape dismisses the topmost layer's onEsc — pop, run, done. The next Escape hits the next layer down.

The layer order that emerges naturally:

  1. Nested modal — an "Are you sure?" confirm over an Adjust-tax modal
  2. Modal — the Adjust-tax modal itself
  3. Popup — a hover-card that got clicked into
  4. Active edit — a contenteditable (rare — mostly retired per no-inline-editing doctrine)
  5. Till reset — Escape on the idle Till discards via #sc-cancel

An operator hitting Escape three times steps back through three layers; a focus-in-nothing Escape resets the Till. No guessing.

Per-tab reset via onEscClear + .till-clear-dot​

When the LIFO stack is idle (no dialog to close, no contenteditable swallowing the key) idle Esc looks at the active tab in two steps:

  1. If the tab registered an explicit onEscClear override, it fires.
  2. Otherwise Esc clicks the active module-pane's visible + enabled .till-clear-dot — the same Till-header reset the operator would click by hand (v0.7.383, BIKE.L1-0020). On Sales that Holds the current sale; on the other tabs it clears the Till while preserving the underlying data. Scoped to the active module-pane so a dot inside an open modal is ignored.

The .till-clear-dot label reads "Esc" on every tab as of v0.7.383 — the physical key and the on-screen glyph name the same action. The former Sales-specific #sc-cancel onEscClear default was retired at the same time; it pointed at Cancel/discard (not the Till's reset) and never fired in practice. The onEscClear API stays for explicit per-tab overrides.

Shortcut registry + "?" cheat-sheet​

kbKeys.register({ combo, description, run }) adds a shortcut and feeds the description into the Kernel's "?" cheat-sheet — an operator can press ? (or Shift-/) anywhere in the app and see the full list of active shortcuts, grouped by module. New shortcuts are one register call; the cheat-sheet updates on next open with no explicit doc work.

Composes with​

  • Focus stewardship — the Escape stack respects focus-steward rules; a refocus doesn't interrupt an open modal's Escape handler
  • Receipt engine — the post-sale prompt's Enter = Print / Escape = skip binding rides kbKeys
  • No-inline-editing doctrine — layered Escape lets a "click-into-modal, edit, Escape, Escape" sequence undo cleanly without accidentally clearing the till

What NOT to do​

  • Don't attach a global keydown listener from a module. Layer-based dispatch is why nested Escape works; a rogue listener steals from the layer that legitimately owns the key at that moment.
  • Don't register a shortcut without a description. The "?" cheat-sheet is the discovery surface; a nameless shortcut is a bug the operator can't ask about.
  • Don't re-bind system combos (Ctrl+C, Ctrl+V, Cmd+R). The register call rejects them.

See also​