Skip to content

PROGS: the bare-deck program launcher

UDS last-used table are in; the INFO detail peek (§8) is not. Demo: demos/progs-launcher.gif. Decision record: ADR-0059 Companions: bare-deck-terminal.md (the tab contract), ADR-0042 (the program roster), ascii-design-system.md (the components), screen-design-rules.md (the row contract)


The seventh tab of the Bare Deck Terminal. It lists every first-party program the router can launch and launches the selected one. It is shell chrome: it holds a cursor, a sort mode and a view mode, and nothing else. The ADR-0042 roster is unchanged by its existence.

Exactly the entries of k_programs[] in runtime/src/screen_router.c (fourteen today). Flat, with no grouping or hierarchy.

programpurposestate hint source
MISSION BOARDcontract boardopen contract count
DECK HUBloadout + statenone
REPLlisp promptnone
nEMACSstructural editornone
CONDUITnetwork clientnone
KOMMANDERfile navigatornone
AMBERCALCspreadsheetnone
RIPSAWtree / LDAP walkernone
KEYRINGkey operationskey count (keystore in DeckState)
kn9operator commsunread count
DOSSIERtarget intelfiled record count
bzbxnet diagnosticsnone
knSALKdeck defenseunimplemented: renders dimmed
WAVAMPSTERstation playerlive station (station.c)

The purpose strings come from ADR-0042 verbatim. A program with no state hint leaves the column blank, which reads as idle.

Lab and demo screens are not listed. k_program_libs[] carries ten screens (fireplace, animlab, linelab, whirl, wirelab, trace, echo, bonsai, bootseq, calc) that have a lazy context but no launch-app id. Adding one to the launcher means adding it to k_programs[], which is an ADR-0042 decision.

Unimplemented entries stay visible. A chartered program with no registered screen renders dimmed and does not launch, mirroring the STATUS tab’s convention, where capabilities the deck does not have yet stay on screen.

Row 0 (status bar) and Row 74 (action bar) are firmware-owned as always. Inside the tab:

RowContent
1Tab strip (shell)
2Rule (shell)
3Control row: sort and view modes
4Rule
5–73List rows or the icon tile grid

Cycled by EQ. Two modes, ascending only.

  • A-Z: case-insensitive ascending by program name.
  • LAST USED: most recently launched first. Never-run programs sort last. In this mode the right-hand column shows elapsed time instead of the state hint.

LAST USED is backed by a fourteen-entry table keyed by roster index, persisted in Universal Deck State so ordering survives a power cycle. Sizing note: this is roughly 56 bytes, and the UDS region is enlarged to fit it if needed. The region size is a project constant that this project sets.

Cycled by QUOTE.

One row per program: a leading column (selection and attention), the name, the one-line purpose, and a right-aligned state column. Selection inverts the whole row. Composes ui/panel, ui/list and ui/list-row.

A tile grid. Each tile is a 7x3 cell mark (56x24 px) with the program name beneath it. At 1x the grid is nine tiles across on a 14-cell pitch, so all fourteen programs fit in two rows with room over, and every roster name fits the pitch (MISSION BOARD is the longest at 13 characters).

Tiles are composed only from glyphs already present in runtime/src/font.c: box drawing 0x800x90, the vertical ramp 0x960x9C, half and full blocks 0xA00xA4, shades 0xA50xA7, symbols 0x040x0F, and ASCII. Diagonals (U+2571/2572) are not in the font; use ASCII / and \.

Tile construction rules:

  1. 7 columns by 3 rows of cells. Fixed for every tile.
  2. Existing glyphs only. No new font bytes; the reserved single-cell icon slots 0x140x1F stay unspent, because a multi-cell tile sidesteps that budget entirely.
  3. Depict the program’s artifact, the thing it operates on: a grid, a key, a tree, an envelope, a file card, a waveform. One consistent basis for all fourteen marks is what makes the set cohere; without it they read as fourteen unrelated doodles.
  4. Keep lit-cell weight even across the set. A tile that fills every cell reads louder than its neighbours and breaks the grid.
  5. Spaces are transparent by convention.

The tile set is locked (2026-07-27) and codified as the icon/tile slot in ascii-design-system.md §4b, which carries the fourteen marks and the construction rules. Labels are centred on the tile’s axis; a name longer than the grid pitch wraps to a second centred row.

The two modes render as an explicit control row above the content rule, showing both options of each with the active one marked:

SORT ►A-Z LAST USED VIEW ►LIST ICON

This costs one content row and buys discoverability: the operator sees that the other sort and the other view exist without pressing anything.

KeyAt tab levelInside the list (after CAR)
7Jump to PROGSQuick-run the 7th entry
19Tab jumpQuick-run the Nth entry
CAREnter the listn/a
BACKn/aLeave the list
CDRNext tabNext tab (the shell owns CDR on every tab)
CONSMove selectionMove selection
EVALRun the selected programRun the selected program
APPLYRun the selected programRun the selected program
EQCycle sortCycle sort
QUOTECycle viewCycle view

How the digit split is enforced. The bare-deck shell consumes every digit as a tab jump before the active tab sees it, so “digits are data inside a mode” needs the shell’s cooperation. deck.lsp carries one flag, *deck-digit-claim*, which a tab raises while it is inside an interactive mode; the shell routes a digit to the tab only while that flag is up, and clears it on every tab change so a claim can never leak. PROGS raises it on CAR and drops it on BACK.

That is also why CAR reaches the tab at all: a tab’s on-key now returns non-nil to mean “I consumed this key”, and the shell only falls back to its own handling when the tab declines. Every tab written before this convention returns nil, so their behaviour is unchanged. CDR is not offered to tabs, so it always cycles tabs and never moves the cursor.

This keeps the input-dispatch invariant intact (see input-dispatch.md §3D): digits stay STATUS-anchored tab jumps until the operator explicitly enters a mode.

APPLY launches here. bare-deck-terminal.md records APPLY as the one key with no bare-deck function, reasoning that APPLY deploys tools and a bare deck has none. PROGS is the deck’s tools, so APPLY is live on this tab and inert on the other six.

INFO opens ui/peek (right-docked, 52 cells, over Rows 1–72). The peek carries the program’s full purpose text, when it was last run, and what it reads and feeds. Keeping the prose there lets the list row stay terse.

BACK out of a program returns to PROGS with that program selected, so run, leave, run-next is one loop, with no walk back through the tab strip.

Two things a deploy has to account for.

Your deck survives the version bump. The last-used table moves DECK_STRUCT_VERSION 5 -> 6, which used to mean the loader rejected the file and reinitialized, discarding handle, credits, reputation, cartridge history, phase chain, Lambda slots, REPL history and the Keyring keystore. It no longer does. DeckState grows by appending, so an older payload is a byte-prefix of the newer struct and the upgrade is a prefix copy with the new tail zeroed. Boot prints:

[DECK] migrated deck.state v5 -> v6 (4060 bytes carried, 64 zero-filled); the deck was NOT reset

Files written from here on also carry their payload size in the header, so future versions migrate in both directions without needing a table entry. The only case that still resets is a layout the build cannot interpret at all. A change that is not append-only must bump DECK_FORMAT_VERSION (rejected outright) rather than the struct version, because a prefix copy would silently misread it.

The dispatch set must be bound at boot. The launcher reads the roster through program-roster, which the router binds into the System context as it builds it — so a host that installs its program-bind hook after screen_router_init gets a System context without it, and PROGS lists nothing with no error anywhere. Both nosh_host.c and the recorder shipped that ordering before ADR-0059. The boot line now says which way it went:

[SCREEN] router ready — … (17 system screens registered, dispatch set bound)

dispatch set MISSING on that line (with a warning above it) means the launcher will be empty. test_screen_router pins the ordering so a third host cannot reintroduce it.

  • Three tiles (DECK HUB, kn9, REPL) share an identical outer frame and lit weight spans 5 to 21 cells, so the tile grid should draw a uniform well behind every tile (design-system §4b rule 4).
  • The detail peek (INFOui/peek) is specified in §8 and not yet built.
  • A ui/palette type-to-filter accelerator over PROGS is deferred (ADR-0059 Option C).
  • Each state-hint source needs a read-only seam.