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)
1. What PROGS is
Section titled “1. What PROGS is”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.
2. What it lists
Section titled “2. What it lists”Exactly the entries of k_programs[] in runtime/src/screen_router.c (fourteen today). Flat, with
no grouping or hierarchy.
| program | purpose | state hint source |
|---|---|---|
| MISSION BOARD | contract board | open contract count |
| DECK HUB | loadout + state | none |
| REPL | lisp prompt | none |
| nEMACS | structural editor | none |
| CONDUIT | network client | none |
| KOMMANDER | file navigator | none |
| AMBERCALC | spreadsheet | none |
| RIPSAW | tree / LDAP walker | none |
| KEYRING | key operations | key count (keystore in DeckState) |
| kn9 | operator comms | unread count |
| DOSSIER | target intel | filed record count |
| bzbx | net diagnostics | none |
| knSALK | deck defense | unimplemented: renders dimmed |
| WAVAMPSTER | station player | live 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.
3. Row and column contract
Section titled “3. Row and column contract”Row 0 (status bar) and Row 74 (action bar) are firmware-owned as always. Inside the tab:
| Row | Content |
|---|---|
| 1 | Tab strip (shell) |
| 2 | Rule (shell) |
| 3 | Control row: sort and view modes |
| 4 | Rule |
| 5–73 | List rows or the icon tile grid |
4. Sorts
Section titled “4. Sorts”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.
5. Views
Section titled “5. Views”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
0x80–0x90, the vertical ramp 0x96–0x9C, half and full blocks 0xA0–0xA4, shades
0xA5–0xA7, symbols 0x04–0x0F, and ASCII. Diagonals (U+2571/2572) are not in the font;
use ASCII / and \.
Tile construction rules:
- 7 columns by 3 rows of cells. Fixed for every tile.
- Existing glyphs only. No new font bytes; the reserved single-cell icon slots
0x14–0x1Fstay unspent, because a multi-cell tile sidesteps that budget entirely. - 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.
- Keep lit-cell weight even across the set. A tile that fills every cell reads louder than its neighbours and breaks the grid.
- 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.
6. Chrome
Section titled “6. Chrome”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 ICONThis costs one content row and buys discoverability: the operator sees that the other sort and the other view exist without pressing anything.
7. Keys
Section titled “7. Keys”| Key | At tab level | Inside the list (after CAR) |
|---|---|---|
7 | Jump to PROGS | Quick-run the 7th entry |
1–9 | Tab jump | Quick-run the Nth entry |
CAR | Enter the list | n/a |
BACK | n/a | Leave the list |
CDR | Next tab | Next tab (the shell owns CDR on every tab) |
CONS | Move selection | Move selection |
EVAL | Run the selected program | Run the selected program |
APPLY | Run the selected program | Run the selected program |
EQ | Cycle sort | Cycle sort |
QUOTE | Cycle view | Cycle 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.
8. Detail peek
Section titled “8. Detail peek”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.
9. Return path
Section titled “9. Return path”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.
10. Deploying this to a device
Section titled “10. Deploying this to a device”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 resetFiles 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.
11. Open
Section titled “11. Open”- 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 (
INFO→ui/peek) is specified in §8 and not yet built. - A
ui/palettetype-to-filter accelerator over PROGS is deferred (ADR-0059 Option C). - Each state-hint source needs a read-only seam.