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.kbKeyspushLayer, 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

Each module registers onEscClear with its own local reset (typically its search-box clear + close). When the operator presses Escape while focus is nowhere (no modal, no active edit, focus on document.body), the active module's onEscClear runs — Sales' clears the search, Customers' clears the filter, Inventory's clears the SKU query. Same key, contextually right.

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