Repository navigation
os/puppy: full-screen console TUI (ratatui) replaces the dialog menu - #921
Conversation
os/v3/puppy/console-tui: one musl-static Rust binary (ratatui + crossterm)
that /etc/inittab's tty1 gets instead of the dialog menu. The owner asked for
it twice: 'can we make that a tui instead of this style' (cycle 18 item 7),
then, on the c18 board boot, 'when you first boot the headless there is no
data to display on how to connect, just the menu' (cycle 18 item 4).
The first block is CONNECT and it is composed from live facts on every draw:
IP(s) + hostname, the web UI, the OpenAI-compatible base, 'API key: press 1',
ssh root@<ip>, Lemonade Mobile; while booting it says 'waiting for network' /
'Lemonade starting' with a spinner and refreshes every 2 s until both answer,
and with no address it says so and points at key 3.
Panels: Lemonade, GPU, NPU, Models, Fans/temps, Data + the last request's
tok/s. Log pane tails Lemonade's log and dmesg's amdgpu/amdxdna lines, wrapped
so nothing is cut ('firmware prese', c16) and scrollable; key 8 is full screen.
Two layouts off one set of facts: 240x67 (tty1 on a monitor) and 80x25, where
the nine action labels are unchanged on purpose - vm-check's U2 menu row looks
for 'Show the API key' and 'Reboot / power off' on tty1.
Keys 2-7 hand the terminal to '1bit-console-menu --action N' (new, one item
interactively), i.e. the same code U2 menu actions/--selftest already exercise,
so the TUI cannot drift from the menu and the gates keep covering both. 1, 8,
9 and the log keys are native; q never exits to a bare prompt.
The GPU question is asked of the runtime (llama-server --list-devices) with
/etc/1bit/libhsa.env sourced, because an inittab respawn inherits neither
LD_LIBRARY_PATH nor the no-SVM trio - the cycle-13 board boot said 'no HRX
device' while chat-hrx served 303 tok/s. The env is obtained by sourcing that
file rather than re-implementing its expansion.
container-build installs it from CONSOLE_TUI_BIN (a Rust toolchain has no
business in the build container) and REFUSES a binary that does not run here
('--version'), a dynamic one, or a packed copy that is not executable; its
absence is only a note and tty1 keeps the dialog menu. 1bit-console execs the
menu on any exit, so there is no path from either screen to a bare prompt.
Tests: 25, including the parsers against recorded samples (the board's own
HRX0 line, the (none) of the libhsa measurement, the efivar bytes 06 00 00 00
01, the documented /v1/models|health|stats bodies, chunked HTTP, df, the c16
status lines) and render snapshots at 240x67 and 80x25 compared byte for byte
with tests/snapshots/, so a layout change cannot happen by accident.
tools/copyright.py: the two snapshot frames are EXEMPT (a notice line would
change the frame they exist to freeze). --check rc 0.
PR Reviewer Guide 🔍(Review updated until commit 1a5dc31)Here are some key observations to aid the review process:
|
…wording pi-os3's cycle-18 branch (os/v3-c18, 3a42884) adds the CONNECT block to the dialog menu AND a vm-check row that greps it off tty1's VGA buffer: U2 menu connect ... for w in CONNECT 'web UI http://' 'api http://' 'ssh root@' 'Lemonade Mobile' Those greps are on the SCREEN, so the moment this TUI is what tty1 gets they would go red on a block that says the same thing in different words. The TUI therefore renders the menu's block verbatim - same four lines, same two spaces in 'api http://' and 'ssh root@' - and a new test renders both console sizes and asserts every one of those strings (plus the address regex) is on screen. A row that fits is emitted verbatim for exactly that reason: wrap_words normalises runs of spaces by design. That also gives the owner one CONNECT block on both surfaces, which is what cycle-18 item 4 asked for. 26 tests.
|
Persistent review updated to latest commit 1c6ac26 |
… in code 1. gpu_mem hardcoded card0..card2. The cards are read from /sys/class/drm now: the node number depends on probe order, so a machine whose card0 reports no VRAM (this appliance's layout: the display controller is card0, the APU is card1) would have shown nothing. Tested against a fake DRM tree with an empty card0 and a populated card1, plus a directory with no card at all. 2. log_lines built the wrapped form of every buffered record each frame. It now walks BACKWARDS and stops when the pane is full, so drawing the log costs what is on screen instead of 400 records' wrapped length - a record with no spaces costs its own bytes and nothing more. 3. 1bit-console reported the fallback with no reason. It now says the status on tty1 AND appends it to /data/logs/console-tui.log, so a fallback can be named afterwards; the TUI's own stderr already carries the reason (a terminal it cannot drive, raw mode refused). All three cases re-measured: TUI exits 0, exits 7, absent - the menu always follows and the log line matches. 27 tests, clippy 0 warnings, copyright --check rc 0.
|
Persistent review updated to latest commit 34e994e |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 34e994e8c — the four things you asked me to check, checked by running the binary rather than reading it. Two nits, neither a gate.
Built and tested here (cargo 1.98.0, musl target present): cargo test → 27/27 passed, including the parsers against the board's own HRX0: AMD Radeon 8060S Graphics (Node 1) (gfx1151) (122880 MiB, 74158 MiB free) sample, env0_from_the_shipped_file, gpu_mem_reads_whichever_card_reports_vram and both render tests. build-static.sh → build-static: ok 7e30ec558a3d908c 1488472 bytes; the artifact is 0 INTERP entries, ldd says statically linked, ELF 64-bit LSB pie. (1,488,472 B vs the body's 1,465,880 — a different rustc, not a discrepancy in kind.)
1. The two renders. Rendered through the CLI, not just the test: --snapshot 80x25 --fixture and --snapshot 240x67 --fixture are byte-identical to tests/snapshots/80x25.txt and 240x67.txt (cmp clean, 25 and 67 lines).
2. The CONNECT block. On screen at both sizes, in the dialog menu's numbering:
CONNECT -----------------------------------------------------------------------
web UI http://192.168.50.110:13305
api http://192.168.50.110:13305/api/v1 (API key: press 1)
ssh root@192.168.50.110 (your key in /data/ssh/authorized_keys)
app Lemonade Mobile: server http://192.168.50.110:13305 + that key
That is inside the first 6 lines at both sizes, which matters because cycle 18's U2 menu connect row (os/v3-c18, vm-check.sh:459-464) reads 1bit-tty1 1 | head -10 and greps exactly CONNECT, web UI http://, api http://, ssh root@, Lemonade Mobile plus web UI http://<v4>:13305 — all five present here, with the two spaces api /ssh the row's grep -F needs.
3. The dialog-menu fallback. Measured on a copy of 1bit-console with its absolute paths repointed (fake pfix=ram,nox cmdline, stub TUI, stub menu), all three cases:
TUI exits 0 -> STUB MENU ran tty1: "1bit-console-tui exited (0) - starting the dialog menu" log: exited (0) at 2026-11-03T14:22:04-04:00
TUI exits 7 -> STUB MENU ran tty1: "…exited (7)…" log: exited (7) at …
TUI absent -> STUB MENU ran (no new line, as it should: nothing to fall back from)
So exec on every path, and the reason is named afterwards in both places. rc=$? is taken before the printf, so the status is the TUI's, not the printf's.
4. The probes use Lemonade's environment. facts::lemonade_env() shells . /etc/1bit/libhsa.env and exports IREE_HAL_AMDGPU_LIBHSA_PATH from /etc/1bit/libhsa, then env -0 — the shipped file is the source of truth, not a re-implementation. The GPU probe then runs llama-server --list-devices with env_clear() plus that map (src/facts.rs:461-472), with a PATH fallback only if the env had none. Lemonade up-ness is TcpStream::connect_timeout to 127.0.0.1:13305 (:325-338); the netstat listing is stored separately and reachable() does not use it, which is the cycle-13 "said not listening while curl answered 200" case. Ran --status here on a box with no /etc/1bit, no llama-server: it degrades honestly (no llama-server at /opt/1bit-llama/llama-server, API key: none in /etc/1bit/lemond.conf, DATA not mounted), rc 0, and found the real lemond pid (4606, /usr/bin/lemond) by exact comm match.
Also checked: 1bit-console-menu's diff is only the --action N arm, which calls the same run_item with NONINT left 0 so its dialogs work — no second copy of an interactive action; container-build.sh has all four CONSOLE_TUI_BIN branches including the post-pack -rwxr-xr-x assertion (:477-482); tools/copyright.py --check rc 0, with the snapshot fixtures exempted and the reason written where the exemption is.
Nits, not gates:
- "In the dialog menu's own wording" is true of the four greppable lines and not of the rest: cycle 18's block puts the hostname on the
CONNECTline (CONNECT <host> web UI …) and says(key: menu item 1)where the TUI says(API key: press 1). The row passes on either surface; one clause in the body would keep a future reader from expecting byte-equality. - The
U2 menu connectrow is only exercised on a boot — the Rust test asserts the strings are in the frame, which is the right proxy, but thehead -10and the VGA-buffer read are the parts a c18 boot will actually decide.
CI at this head: pr_agent, Analyze×4, CodeQL green, hw_gate SKIPPED, build still in flight (this PR does not touch it, but I am not labelling past a pending check unless you say so).
|
Persistent review updated to latest commit b7d04ec |
|
Three things a reviewer may want to check first:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at b7d04ecbd — head moved by one commit and the commit is what it says: src/main.rs loses a single line.
let mut app = App::new(Facts::collect());
- let mut _msg_clock = Instant::now();
let mut last_spin = Instant::now();_msg_clock was never read (that is why it is underscore-prefixed and why cargo did not warn), so this cannot change
behaviour. Re-ran at this head rather than assuming: 27/27 cargo test, --snapshot 80x25 --fixture and 240x67 --fixture
both byte-identical to the committed fixtures, tools/copyright.py --check rc 0. The other 13 files of the PR are
blob-identical to 34e994e8c, the head I verdicted in full (CONNECT block on screen at both sizes, fallback measured for
TUI exits 0 / 7 / absent, GPU probe through lemonade_env() + llama-server --list-devices, Lemonade up-ness by TCP
connect). CI build=SUCCESS.
Label stays tprs-ready; nothing of mine holds it.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 1a5dc31bf — Merge branch 'main' into os/v3-console-tui, and all 14 files are blob-identical to b7d04ecbd, the head I re-ran in full (27/27 cargo test, both render snapshots byte-identical to the committed fixtures, build-static.sh musl-static with 0 INTERP, CONNECT block on screen at 80×25 and 240×67, fallback measured for exits 0 / 7 / absent, probes through lemonade_env()). Nothing to re-derive; label stays tprs-ready.
One cross-PR note while it is fresh: the CONNECT block here renders api http:// and ssh root@ with two spaces, on purpose, because cycle 18's row was written against the dialog menu's spacing. #925 changes that row's word list to single-spaced and has a CHANGES from me on it — once that row is fixed (two-space words, and a missing- line actually failing the row), whichever surface tty1 gets is checked.
|
Persistent review updated to latest commit 1a5dc31 |
…nderers draw that screen now #921 merged the ratatui console TUI, and /etc/inittab's tty1 is the TUI when the image carries the binary and the dialog menu when it does not (the documented fallback). They draw the same facts and differ in one way that matters to a grep: dialog collapses runs of spaces in a --menu prompt, ratatui does not, so the TUI's screen says 'api http://…' and 'ssh root@…' with two spaces while the dialog menu's says one. U3's sibling row matches those strings from tty1's VGA buffer, so it would have printed two misleading missing-* words on a TUI image (and passed only because its marker keys on the single-spaced 'web UI http://' line). Measured on both renders of the same fixtures: 80x25 TUI snapshot and the dialog menu at 80x25.
…busy), and no request URL carries a user value (#926) * os/v3/puppy: the chat page retries the NPU's own refusal (503 device_busy), and no request URL carries a user value Two follow-ups to #925, both in the page the appliance serves. 1. engine#924 bounds the wait for the one device: a request that cannot have it within --queue-ms is REFUSED with a status instead of parked silently, and Lemonade's proxy does not retry that on the request path - so the page is the only layer that can. It now retries after the advertised delay (Retry-After, clamped to 10 s, 1 s when the header is absent) up to RETRIES = 3, saying 'NPU busy, retrying (2/3) in 1 s' while it waits, and after the last attempt it stops with the engine's own words instead of hanging or reporting zero chunks. Both shapes #924 can produce are handled, because this page streams: a 503 + Retry-After + {'error':{'type': 'device_busy'}} (status and headers reach the page through 1bit-webapp-proxy, which already forwards them), and - what the page actually sees for a streaming request - ONE 'data: {"error":{...}}' frame before 'data: [DONE]', which carries no type at all, so an error frame is retried when it says busy or when the stream had not produced a single chunk yet. A stream that fails after it produced content is reported, not silently requested again. 2. CodeQL, on the #925 diff: '?api=' made a fetch URL depend on a query parameter. It was a cycle-15 debug affordance nothing in the image uses (the launcher opens http://localhost:8080/ with no query string) and a way to make the appliance's own browser POST at another host, so it is gone: the paths are literal same-origin '/api/v1/...' and no tainted value reaches a request. ?model= / ?think= / the one-shot ?key= stay (none becomes a URL), and the key itself has not travelled in a URL since cycle 17. Proven in a real browser against the two documented refusal shapes (~/wt/os3i/c18-page-probe.py, screenshots in /tmp/busy-*): two streaming refusals then the answer; one 503 then the answer (status line reads 'NPU busy, retrying (1/3) in 1 s' in both); nine refusals -> four attempts then 'NPU busy: still refused after 4 attempts (the NPU is busy with another request) - press Send again in a moment'; and no refusals -> the answer unchanged. The probe also caught the bug that would have shipped: the predicate was named busy and var busy=false overwrote it, so the page answered 'TypeError: busy is not a function'. U3 webapp page now requires device_busy and 'NPU busy, retrying' in the page the RUNNING proxy serves, printing busy=N retry-text=N beside the other four counts. * os/v3/puppy: the CONNECT row reads tty1 through tr -s, because two renderers draw that screen now #921 merged the ratatui console TUI, and /etc/inittab's tty1 is the TUI when the image carries the binary and the dialog menu when it does not (the documented fallback). They draw the same facts and differ in one way that matters to a grep: dialog collapses runs of spaces in a --menu prompt, ratatui does not, so the TUI's screen says 'api http://…' and 'ssh root@…' with two spaces while the dialog menu's says one. U3's sibling row matches those strings from tty1's VGA buffer, so it would have printed two misleading missing-* words on a TUI image (and passed only because its marker keys on the single-spaced 'web UI http://' line). Measured on both renders of the same fixtures: 80x25 TUI snapshot and the dialog menu at 80x25. * os/v3/puppy: a refusal is retried only when the stream produced nothing, so a partial answer is never replaced Copilot's review of #926, and it is right: the retry condition was `isBusy(j) || /busy/i.test(why) || !chunks`, so (i) a busy-typed error after content was retried, and (ii) a *reasoning-only* stream (chunks = 0, rchunks > 0) also passed `!chunks` - and engine#924's own streaming refusal is exactly a reasoning-less-but-something shape. A retry clears the turn's two output elements, so either case silently replaced text the reader already had. Now a refusal is retried only when the stream produced NOTHING (no answer chunk AND no reasoning chunk): that keeps the c17 '0 chunks' recovery, which is what the retry exists for. A failure after any output is reported with the engine's own words and the chunk count, and the partial answer stays on screen. --------- Co-authored-by: pi-os3 <pi-os3@1bit.local>
…s TUI can actually reach an image (#927) * os/v3/puppy: build.sh hands the console TUI to the container, so #921's TUI can actually reach an image #921 merged the ratatui console TUI and taught container-build.sh to install it from CONSOLE_TUI_BIN, but nothing forwarded that variable: docker passes only what `-e` names and the binary - which lives outside the mounted tree, because build-static.sh builds it on the host - was never mounted. So the container saw the empty string and took its own documented fallback, in silence. Measured on cycle 19's first build: `CONSOLE_TUI_BIN=…/1bit-console-tui` on the host command line, and the build log says `note: CONSOLE_TUI_BIN is not set - tty1 keeps the dialog menu`. The image was the dialog-menu one, which is the safe failure but not what was asked for, and the same class as the cycle-14 PAYLOAD bug (a host variable that never crossed into the container). Now build.sh resolves the path (and refuses a missing file here, where the message can name it), mounts it read-only at /console-tui and passes CONSOLE_TUI_BIN=/console-tui, printing the binary's sha256 in its first lines so the log ties the image to a specific build. container-build.sh's own checks are unchanged and still do the work: it refuses a binary that does not run here, and refuses an image that was given one and has no /usr/local/bin/1bit-console-tui. * os/v3/puppy: a console TUI without a payload is refused, instead of being silently left out Copilot's review of #927, and it is right: container-build.sh installs the TUI and verifies it in the packed SFS inside its `PAYLOAD` block (the TUI belongs to the embedded appliance, not to a bare Puppy rootfs), so a build given CONSOLE_TUI_BIN and no PAYLOAD would pass the forwarding fixed in the previous commit and then skip the install with no note - the very failure that commit exists to end, one layer deeper. build.sh now refuses that combination before the image pull, naming the reason. --------- Co-authored-by: pi-os3 <pi-os3@1bit.local>
… action Owner, cycle 20: "can we place the menu above the scrolling logs and below the technical information". Before, the nine actions were packed two-to-five per row BELOW the log pane, so the pane pushed the menu off the bottom at 80x25 (4 packed rows, the last one a lone "9 Reboot / power off") and the owner had to read the log to find the menu. The 80x25 layout also spent nine rows on panels, which is what made the log pane two rows tall. Now, top to bottom: header, CONNECT, the status facts, MENU (a rule plus nine numbered rows, never abbreviated), LOGS (scrolling), one footer line. 80x25 keeps the same six facts on two compact rows (fields are packed whole, so "DATA" cannot end up alone at the end of a row) and leaves the log pane five rows (the fixture fills all 25 console rows; the earlier code counted the log title twice and left row 25 blank). Nothing about the API key changes here. Item 1 keeps ONE label in both key modes - "Show the API key" - because the appliance ships blank (cycle-20 item 7) and vm-check's `U2 menu` row greps that string off tty1: a mode-dependent label would turn that row, and pi-release's c15 menugate, red on every shipped image. The blank-key offer lives where the item-7 work puts it (the CONNECT block and item 1's own dialog, on os/v3-c20), so no gate edit is needed with this PR. A new test renders a KEYED and a BLANK box at 240x67 and 80x25 and requires both gate strings on both, which is the regression this PR first introduced and now holds down. Measured: - 80x25 (pty, musl binary): header 1, CONNECT 2-6, compact facts 7-8, MENU rule 9, the nine rows 10-18, LOGS title 19, five log lines 20-24, footer 25 - every row used, no blank row. - 240x67 (pty): MENU rule 17, the nine rows 18-26, log pane 40 rows (was: menu on rows 65-66 under it). - cargo test 31/31 (5 new: menu between the facts and the log, the nine rows on consecutive lines above the log, log pane >= 3 rows at 80x25 and reaching the footer, compact status keeps all six panels and never splits a field, item 1's gate string in both key modes), cargo clippy --all-targets clean, tools/copyright.py --check rc 0, both fixtures re-rendered (keyed board: unchanged bytes from #921).
… action (#935) Owner, cycle 20: "can we place the menu above the scrolling logs and below the technical information". Before, the nine actions were packed two-to-five per row BELOW the log pane, so the pane pushed the menu off the bottom at 80x25 (4 packed rows, the last one a lone "9 Reboot / power off") and the owner had to read the log to find the menu. The 80x25 layout also spent nine rows on panels, which is what made the log pane two rows tall. Now, top to bottom: header, CONNECT, the status facts, MENU (a rule plus nine numbered rows, never abbreviated), LOGS (scrolling), one footer line. 80x25 keeps the same six facts on two compact rows (fields are packed whole, so "DATA" cannot end up alone at the end of a row) and leaves the log pane five rows (the fixture fills all 25 console rows; the earlier code counted the log title twice and left row 25 blank). Nothing about the API key changes here. Item 1 keeps ONE label in both key modes - "Show the API key" - because the appliance ships blank (cycle-20 item 7) and vm-check's `U2 menu` row greps that string off tty1: a mode-dependent label would turn that row, and pi-release's c15 menugate, red on every shipped image. The blank-key offer lives where the item-7 work puts it (the CONNECT block and item 1's own dialog, on os/v3-c20), so no gate edit is needed with this PR. A new test renders a KEYED and a BLANK box at 240x67 and 80x25 and requires both gate strings on both, which is the regression this PR first introduced and now holds down. Measured: - 80x25 (pty, musl binary): header 1, CONNECT 2-6, compact facts 7-8, MENU rule 9, the nine rows 10-18, LOGS title 19, five log lines 20-24, footer 25 - every row used, no blank row. - 240x67 (pty): MENU rule 17, the nine rows 18-26, log pane 40 rows (was: menu on rows 65-66 under it). - cargo test 31/31 (5 new: menu between the facts and the log, the nine rows on consecutive lines above the log, log pane >= 3 rows at 80x25 and reaching the footer, compact status keeps all six panels and never splits a field, item 1's gate string in both key modes), cargo clippy --all-targets clean, tools/copyright.py --check rc 0, both fixtures re-rendered (keyed board: unchanged bytes from #921). Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Cycle 18 item 7 + item 4 (owner): the
dialogconsole menu on tty1 becomes a full-screen TUI, and the top ofthe screen is the CONNECT block the cycle-18 board boot asked for ("when you first boot the headless there is
no data to display on how to connect, just the menu").
Brief:
~/briefs/TCONSOLE-TUI.md. Built and measured on strixhalo; no image was built by this lane (pi-os3cycle 18 owns the build, and the recipe takes the binary as
CONSOLE_TUI_BIN).Coordination with cycle 18 (
os/v3-c18,3a428849)That branch adds the same CONNECT block to the dialog menu and the
U2 menu connectrow. This branch wastest-merged against it: no conflict (
1bit-console-menu: they changescreen_text()/main_menu, this adds--actionin the argument dispatch; git merges both cleanly - measured, then aborted). After they land, the rowreads green on either surface.
The
--actionflag is the only edit to1bit-console-menuhere;--status,--run,--selftestand the statusblock are untouched, and
U2 menu/U2 menu status/U2 menu actions/U2 menu envkeep passing.What it is
os/v3/puppy/console-tui/— one Rust binary (ratatui+crossterm),x86_64-unknown-linux-musl, 1,465,880bytes,
readelfPT_INTERP absent,lddsays "statically linked". No runtime dependency: the appliance has noRust toolchain and must never fail to start a console over a loader.
build-static.shrunscargo test, builds the musl target and refuses a dynamic result.container-build.shinstalls it when
CONSOLE_TUI_BINis set:CONSOLE_TUI_BIN--versionfails)-rwxr-xr-xor the build is REFUSEDAll four branches measured here by running the recipe's own block.
1bit-consoletries the TUI first andexecsthe dialog menu on any exit, zero or not — measured for all three cases (TUI exits 0 / exits 7 / is absent: the
menu always follows, so no path leads to a bare prompt).
The screen
Two layouts off ONE set of facts (
src/ui.rs), so the two cannot disagree:actions on two rows.
pane, and the same action labels —
Show the API keyandReboot / power offare never abbreviated,because
vm-check.sh'sU2 menurow looks for exactly those two strings on tty1.Both frames are committed as regression fixtures (
tests/snapshots/*.txt, compared byte for byte) and are below.CONNECT block (cycle 18 item 4)
Composed on every draw from live facts, and in the dialog menu's own wording (cycle 18's
screen_text()onos/v3-c18), because that branch's newU2 menu connectrow grepsCONNECT,web UI http://,api http://,ssh root@andLemonade Mobileoff tty1's VGA buffer — the row has to pass whichever of the two surfaces tty1gets, so the TUI renders the same four lines with the same two spaces, and a test renders 240x67 and 80x25 and
asserts all five strings plus the address regex are on screen. While the box is still coming up the title carries the state and a
spinner (
CONNECT - waiting for network |, thenCONNECT - Lemonade starting /), the facts refresh every 2 sinstead of 3 s until both answer, and with no address the block says
no network yet - press 3 to renew DHCP, set a static address or the hostname.Panels
Lemonade (up/pid/URL/key hint), GPU (the runtime's device + free MiB, VRAM/GTT used, temp), NPU (
amdxdnaloaded, firmware present,
accel0busy/idle), Models (downloaded, loaded), Fans/temps, Data (total/free) and thelast request's
tok/s+ time to first token fromGET /api/v1/stats. Log pane: tail of Lemonade's log plusdmesgfiltered to amdgpu/amdxdna/hrx/accel, wrapped (nothing is cut — the c16 board'sfirmware preseis atest that walks widths 8..60) and scrollable; 8 is full screen.
Keys
1 API key (confirmed first — it is a bearer token and this screen can be on a KVM recording) · 2 chat test ·
3 network · 4 models · 5 install · 6 desktop · 7 shell · 8 logs · 9 reboot/power off (confirmed). Arrows/PgUp/PgDn
scroll,
ffollows,rre-reads now,qstays on this screen.Keys 2-7 hand the terminal to
1bit-console-menu --action N(new argument: one item, interactively). That isdeliberately the same code path
U2 menu actionsand--selftestalready exercise, so the TUI cannot drift fromthe menu and an action the gate proves is an action the screen has. Nothing that needs a typed answer is
re-implemented.
The GPU probe, and why it is not the menu's bug again
facts::lemonade_env()sources/etc/1bit/libhsa.env(rather than re-implementing its${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}) and hands that environment tollama-server --list-devices, because an/etc/inittabrespawn inherits neither the loader path nor the no-SVM trio — the cycle-13 board boot said "GPU noHRX device" while chat-hrx served 303 tok/s. A connect to 127.0.0.1:13305 (not a
netstatlisting) decideswhether Lemonade is up: measured on this box,
netstatabsent, so a listing-based answer said "not listening"while
curlanswered 200.Tests — 25,
cargo testParsers against recorded samples, not invented ones: the board's own
HRX0: AMD Radeon 8060S Graphics (Node 1) (gfx1151) (122880 MiB, 74158 MiB free), the(none)of runs A and D ofthe libhsa measurement, the efivar bytes
06 00 00 00 01, the documented/v1/models//v1/health//v1/statsbodies, chunked and plain HTTP,df, the c16 status lines,wrap_wordsat every width. Plus the tworender snapshots below and a no-panic pass at 40x12 and 240x8.
--statusprints the same facts as plain text (agate row can read it without a terminal),
--snapshot WxH [--fixture]renders one frame.tools/copyright.py --checkrc 0 (the two snapshot frames are EXEMPT: a notice line would change the frame theyexist to freeze).
Not done here, on purpose
vm-check.shrow. Cycle 18's PR edits that file; a second edit from this branch would conflict. Suggestedrow for whoever lands second:
1bit-tty1 1 | grep -q 'CONNECT'+1bit-console-tui --statuscontainingLemonade GPU NPU Fans Data Models. The existingU2 menu/U2 menu status/U2 menu actionsrows keeppassing unchanged (the labels are the menu's, and the menu's
--status/--selftestcode is untouched).--actionaddition to1bit-console-menuis 6 lines and is the only change to that file.The two renders
240x67
80x25