Skip to content

The help overlay: binding discovery on every surface

Perkins inspiration entry, which carries the ? overlay as its surviving borrowable idea, and ranked second in the cross-cutting priorities of features-matrix.md with Zork’s HELP as the second precedent. Related: ADR-0053 (the overlay mechanism this reuses), input-dispatch.md (hold detection), ui-design-language.md (chrome bands).

The deck ships no way to discover what the keys do. A search of the runtime and the docs tree finds no help screen, no binding list, and no key legend on any surface.

Meanwhile the surface count has grown past what an operator can hold in memory. The tree carries 19 programs under runtime/programs/ plus 7 system libraries with their own on-key handler. Each defines private bindings, and the same physical key means different things across them: INFO toggles inspect in DOSSIER, marks a file in Kommander, marks a message read in kn9, and opens the sonar scope in Depth Charge.

The Row-74 action bar is the closest existing affordance, and it covers a fraction of the surface. Kommander binds 14 keys and advertises 4. The shell’s own DECK-ACTIONS table lists 3 of the bindings its on-key accepts. The action bar holds one row of scale-2 text, so the ceiling is around five hints; it works as a reminder for the operator who already knows the surface and leaves the rest undiscovered.

ui/action-bar enforces that ceiling as of the Row-74 width guard: it measures the composed line against the row budget derived from render/width, keeps the leading bindings whole, and closes with ►N for the N it dropped. Nine programs were over the budget and drew the overrun off the right edge. The bars are now cut to their primary verbs, and the verbs that came off them are the population this overlay carries: kn9 loses mark, flag, next-unread, relay, and the OPEN> handoff; Kommander loses mark, copy, view, and its terminal seam; Keyring loses key generation, revoke, and OPEN>; RIPSAW loses expand, collapse, inspect, promote, and OPEN>. Until the overlay ships, ►N is the only signal that those bindings exist.

The animation-style pickers added 2026-07-27 are a worked example of the failure. animlab binds PAD-1 through PAD-7 to select among the seven named motion styles, and nothing on the deck announces this. The screen carries a hand-painted [1-5] / [6-7] hint because no system exists to carry it. Every program that grows a binding faces the same choice: paint a private hint, or ship an undiscoverable feature.

The design adds one screen and one registration convention. Everything else exists.

The overlay mechanism is built and generic. ADR-0053 landed screen_router_overlay_toggle(rt, screen) for the REPL drop-down, and the signature takes a screen name. It snapshots the base display, restores it byte-exact on close, pauses the underlying tick while the overlay is down, and drives the overlay screen’s own slide animation. A help overlay is a second client of that mechanism.

The label vocabulary exists. ui/action-bar accepts a list of (key . label) conses, and every screen that paints an action bar already authors one:

(define DECK-ACTIONS
'(("CDR" . "next tab") ("1-9" . "select") ("EVAL" . "open")))

That shape is what a help table needs. What the tables lack is coverage.

The bindings are already data. Programs register through define-key into a keymap, and several carry a parallel (key . handler) alist for the non-mode path. What those structures lack is the human-readable label: the verb names sit in source comments (; MARK, ; COPY), where no renderer can reach them.

The 34-key layout has no spare key, so the overlay claims a hold rather than a press. Hold is an established idiom on the deck: SYS held for 2 s is the emergency exit, and LAMBDA held for 2 s starts macro record.

INFO held opens the overlay on any surface. The tap meaning stays with whatever screen owns it, so no existing binding moves. INFO reads as the help key across the deck’s own fiction, and the tap-versus-hold split matches how SYS already carries two meanings.

The overlay closes on a second INFO, on BACK, or on any key it does not consume. Holding SYS still aborts, because the emergency exit is never overridable.

Content: the action bar is the first line of the help table

Section titled “Content: the action bar is the first line of the help table”

A screen declares one table. The action bar renders its head; the overlay renders all of it.

(define KOMMANDER-HELP
'(("CAR" . "cursor up")
("CDR" . "cursor down")
("ATOM" . "switch panel")
("APPLY" . "enter dir / AVFS")
("BACK" . "parent / close peek")
("INFO" . "mark")
("CONS" . "copy to passive")
("SYS" . "move to passive")
("NIL" . "delete")
("LAMBDA" . "mkdir")
("EQ" . "clone pane")
("QUOTE" . "view (peek)")
("LINK" . "open with")
("EVAL" . "commit exfil")))

One source, two renderings. (ui/action-bar (take KOMMANDER-HELP 3)) keeps the Row-74 strip as a preview of the full table, which removes the drift risk of a screen whose hints and bindings disagree.

Deriving the table from the keymap instead was considered and rejected for this draft: the keymap holds handlers, the labels live in comments, and harvesting them would mean either parsing source or extending define-key upstream in the kec-lisp editor layer. A declared table is additive and local. Extending define-key to carry a label remains the better long-term shape and is recorded under open questions.

Composition: the overlay shows the whole live stack

Section titled “Composition: the overlay shows the whole live stack”

An operator inside a program is holding three sets of bindings at once: the program’s, the shell’s that stay live underneath, and the runtime’s that are never overridable. The overlay renders them as three sections, most-local first.

SectionSourceExample
This surfacethe active screen’s help tableINFO mark, CONS copy to passive
Deckthe shell’s table, filtered to bindings the program does not shadowTERM REPL
Alwaysa runtime constantSYS (hold) abort

A screen with no declared table renders the last two sections and a line naming the omission, so the overlay degrades to partial coverage instead of an empty panel.

The overlay composes from the ASCII design system rather than hand-drawn furniture: a framed panel over a scrim, a two-column key/label list per section, section dividers between. The key column is fixed-width so the labels align down the panel.

Sizing differs from the REPL drop-down. The REPL claims a fixed 40% band because its content scrolls; the help panel sizes to its content up to a cap, and a table longer than the cap scrolls with CAR / CDR. Kommander’s 14 bindings plus two sections fit inside 24 rows at scale 2.

The panel is chrome, so it draws over Rows 1 through 73 and leaves the status and action bars visible: the operator keeps the deck’s frame of reference while reading.

The whole contract is one table and one line at registration.

(define WIRELAB-HELP '(("PAD-1" . "barber-pole") ("PAD-2" . "water")))
(deck/register-screen 'wirelab wirelab/render wirelab/on-key)
(deck/register-help 'wirelab WIRELAB-HELP)

A program that already paints an action bar has the table; registration is the added line.

In scope for a first cut:

  • deck/register-help and the overlay screen.
  • Hold-INFO invocation through the existing overlay router.
  • The three-section composition and the scroll path.
  • Help tables for the system surfaces (bare deck, board, REPL, PROGS) and the programs whose bindings exceed the action bar: Kommander, DOSSIER, kn9, Keyring, CONDUIT, animlab.

Out of scope:

  • Per-binding long-form prose. The overlay lists what the keys do; a program needing a tutorial writes a screen.
  • Searching or filtering the table.
  • Cartridge help. Carts run in a constrained context with their own registration path, and the system surfaces are the population that needs this first.
  1. Hold duration. SYS and LAMBDA both use 2 s. Help is a lighter action than an emergency abort, and 2 s may read as sluggish for something an operator hits often. A shorter threshold for INFO needs a decision, and a per-key threshold table needs one either way.
  2. Labels in define-key. Extending the upstream signature to (define-key km 'INFO "mark" handler) would collapse the two structures into one and remove the chance of a table drifting from its keymap. The cost is a change to the kec-lisp editor layer, which is external. This draft avoids it; the decision belongs in the ADR.
  3. Whether this warrants an ADR. The overlay adds a runtime affordance and a registration primitive that every screen consumes, which fits the ADR bar. The counter-argument: it introduces no new mechanism, since the router, the overlay, and the label format all exist.
  4. CIPHER-LINE. The OLED could carry a one-line hint for the current surface’s primary binding, which would put discovery in the operator’s peripheral vision without claiming the main grid. That is a separate feature with its own cost, noted here so the option stays visible.