Skip to content

ADR-0059: PROGS, the bare-deck program launcher

roster, unchanged by this ADR), ADR-0005 (launch-app, the primitive this surface calls), ADR-0016 (numpad input model; the digits-are-data invariant), ADR-0036 (integer-scale glyph composition, which the icon tiles use), bare-deck-terminal.md (the tab contract this adds to), progs-launcher.md (the per-view spec), ascii-design-system.md (the components PROGS composes from)


Nothing on the deck could launch a program

Section titled “Nothing on the deck could launch a program”

The Bare Deck Terminal registers six tabs (runtime/system-image/lib/deck/deck.lsp): STATUS, CIPHER, LAMBDA, LINK, SYS, MISSIONS. None of them calls launch-app. Verified by grep: there are zero launch-app call sites anywhere under runtime/system-image/lib/deck/.

So on a shipping deck the only routes to a first-party program are the REPL (TERM, then (launch-app :knamp)) and the dev recorder (kn86rec --screen <name>). Nine built programs (CONDUIT, Kommander, AmberCalc, RIPSAW, Keyring, kn9, DOSSIER, bzbx, WavAmpster) are unreachable by an operator who has not been told to type a Lisp form. ADR-0042 chartered a toolkit and gave it no door.

runtime/src/screen_router.c carries two tables that are easy to confuse:

tableentrieswhat it is
k_programs[]14The ADR-0042 roster plus WavAmpster. launch-app resolves against this; an off-roster id raises :unknown-app, and a chartered-but-unimplemented one returns false.
k_program_libs[]19Which registered screens need their own lazy Fe context. Includes ten lab and demo screens (fireplace, animlab, linelab, whirl, wirelab, trace, echo, bonsai, bootseq, calc) that are not launchable.

k_programs[] is already closed by ADR, already authoritative, and already excludes the labs. A launcher needs no second list.

PROGS is a shell surface, so the roster does not change

Section titled “PROGS is a shell surface, so the roster does not change”

A launcher could be read as a fifteenth first-party program, which would demand an ADR-0042 amendment. It is not one. PROGS is a tab of the bare-deck shell, in the same category as STATUS and MISSIONS: it renders inside deck.lsp, holds no state of its own beyond a cursor and two view modes, and routes. ADR-0042’s roster stays at fourteen.


The bare deck gains a seventh tab, PROGS, which lists every entry in k_programs[] and launches the selected one.

  1. Placement. One deck/register-tab call, appended after MISSIONS. No other tab’s code is touched. It lands at tab index 7 and inherits CDR cycling and digit-jump from the shell.

  2. Contents. All fourteen k_programs[] entries, flat. No groups, no folders, no hierarchy. Lab and demo screens stay off the list; making one launchable is an k_programs[] change, which is an ADR-0042 decision, so it happens there. A chartered-but-unimplemented program (today, knSALK) renders dimmed and does not launch.

  3. Two sorts, cycled by EQ: A-Z (case-insensitive ascending) and LAST USED (most recent first, never-run entries last). Ascending only; there is no descending mode.

  4. Two views, cycled by QUOTE: LIST (one row per program: name, one-line purpose, and a right-hand state column) and ICON (a tile grid, nine across at 1x, each tile a 7x3 cell mark with the program name beneath).

  5. Icon tiles are drawn from existing glyphs. A tile is 7x3 cells (56x24 px) composed only from codes already in runtime/src/font.c. This is deliberate: a multi-cell tile needs no new font bytes, so the twelve reserved single-cell icon slots (0x14-0x1F) stay unspent. The tile set lands as an icon/tile slot in the ASCII design system.

  6. Chrome is an explicit control row: one row above the content rule showing both options of each mode with the active one marked. The operator can see that the other sort and the other view exist without pressing anything.

  7. Key map. CONS/CDR move, EVAL runs, INFO opens the right-docked detail peek, EQ cycles sort, QUOTE cycles view, BACK leaves, digits 1-9 quick-run the Nth entry. APPLY also launches. bare-deck-terminal.md records APPLY as the one key with no bare-deck function, on the reasoning that APPLY deploys tools and a bare deck has no tools. PROGS is the deck’s tools, so the gap closes here and nowhere else; APPLY remains inert on the other six tabs.

  8. Return path. BACK out of a program returns to PROGS with that program selected.

    Inside the tab, BACK peels one layer at a time: it closes the detail peek if open, otherwise it leaves the list.

  9. LAST USED persists in Universal Deck State. A fourteen-entry last-used table keyed by roster index is roughly 56 bytes. If the UDS region needs to grow to hold it, grow it. The 4 KB figure is a number this project chose; enlarging it is a DECK_STRUCT_VERSION bump and a loader path, the same cost as any other schema change. Donor-field scavenging is one option among several and is not required.

Implementation note (2026-07-27, same-PR): the shell needed a mode gate

Section titled “Implementation note (2026-07-27, same-PR): the shell needed a mode gate”

Decision 7 above assumed a tab could see a digit. It could not. The bare-deck shell consumed every digit as a tab jump before the active tab’s handler ran, and CAR likewise never reached a tab, so “digits are data inside a mode” had nowhere to live. Two small, backward-compatible additions to deck.lsp close that:

  • *deck-digit-claim*: one shell flag a tab raises while it is inside an interactive mode. The shell routes a digit to the tab only while the flag is up, and clears it on every tab change so a claim cannot leak. PROGS raises it on CAR and drops it on BACK. Digits stay STATUS-anchored tab jumps otherwise, so the input-dispatch.md §3D invariant holds exactly as written.
  • “non-nil means consumed”: a tab’s on-key may now return non-nil to tell the shell not to also act on that key. CAR is offered to the active tab first and falls through to the shell’s previous-tab step when the tab declines. Every tab written before this convention returns nil, so none of them change behaviour.

CDR is deliberately not offered to tabs: it always cycles tabs. The key table in progs-launcher.md §7 reflects that.


Option A: A seventh bare-deck tab. (ACCEPTED)

Section titled “Option A: A seventh bare-deck tab. (ACCEPTED)”

Chosen because it matches the existing deck/register-tab registry exactly, needs no edits to any other tab, inherits the shell’s navigation grammar for free, and puts the launcher at the same level as the other things an operator reaches between missions.

Rejected because SYS is settings and diagnostics. A launcher is neither, and burying a primary surface one level inside SYS costs two extra keypresses on every launch.

ui/palette already exists in ui/ui/compositing.lsp, so a type-to-filter launcher is nearly free.

Rejected as the sole answer because a palette shows nothing until the operator types, and an operator who does not know CONDUIT exists will never type “con”. Discovery has to come first. The palette remains attractive as a later accelerator layered over PROGS, and is tracked as a follow-on.

Option D: A fifteenth first-party program.

Section titled “Option D: A fifteenth first-party program.”

Rejected because the launcher has no state, no save data, and no domain. It is shell chrome, and promoting it would force an ADR-0042 amendment for no gain.


DimensionA: seventh tab (chosen)B: inside SYSC: palette onlyD: 15th program
Discoverable without prior knowledge◐ buried
Keypresses to launch from boot✓ 2✗ 4✓ 2
Edits to existing tab code✓ none✗ SYS rewrite✓ none✓ none
ADR-0042 roster untouched✗ amendment
Reuses an existing component✓ list/panelui/palette

  • Nine built programs become reachable without typing a Lisp form.
  • k_programs[] gains an operator-facing consumer, so the roster stops being an internal detail.
  • APPLY earns a bare-deck meaning on exactly the tab where it is fictionally correct.
  • The icon-tile construction rule (multi-cell marks from existing glyphs) is reusable by any future surface that wants pictorial identity without spending font slots.
  • The tab strip grows to seven, and the digit map in bare-deck-terminal.md needs a sweep. That doc already describes four tabs against the six that ship, so the drift predates this ADR; this change makes fixing it unavoidable.
  • The icon tile set is fourteen pieces of drawn art that need design attention and upkeep as the roster grows.
  • LAST USED adds a UDS field and therefore a struct-version bump.
  • The icon/tile slot and the chosen fourteen tiles land in ascii-design-system.md, with the implementation in ui/ui/.
  • ui/palette as a type-to-filter accelerator over PROGS (Option C, deferred).
  • The state-hint sources per program (WavAmpster’s live station, kn9 unread, DOSSIER record count, Keyring key count) are read-only reads that each need a seam.

Documentation Updates (REQUIRED: Spec Hygiene Rule 3)

Section titled “Documentation Updates (REQUIRED: Spec Hygiene Rule 3)”