Files
roro9stack/docs/milestones/U1.md
T
twislaandClaude Opus 5.5 0c52613b72
CI / build (pull_request) Successful in 2m22s
Site / build (pull_request) Successful in 10s
Settings: its rows in five groups
Settings had grown to 21 rows in one list. It opens on five groups (This
device, Display, GNSS and radio, Network, System); Enter opens one, Back
returns to the groups, and Back from the groups leaves Settings.

SettingsMenu holds the groups and which one is open (host-tested: every
setting is in exactly one group). The guide, the how-tos, the README,
the scripts' messages and the firmware's own name the new paths, such as
"Settings > System > Firmware".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
2026-10-10 00:11:52 +02:00

14 KiB
Raw Blame History

U1 — Look and feel

Status: in progress. Shipped: the help key (issue #69) and the key tables the website shares with it (#72), both in v0.13.0; the screenshot key (#83, v0.17.0). The Launcher as a grid of icons (#9) and themes (#10) are done, not released yet. Not started: screen recording (#17).

Goal: the interface is consistent and uncrowded: the same thing is done the same way on every screen, and the 135 pixels of height go to content.

The help key (issue #69)

Every screen used to say something about its keys, differently: a footer of abbreviations in one place (c x v:paste r:name d:del n:new i:info s:sort), a line under a text field in another (Enter: save \: cancel), Tab: sky` in a corner, and nothing at all in several. About 30 such strings, each costing a line of a small screen, and none of them complete.

Decisions (design round 2026-10-07)

# Decision
Q196 Fn+h, on every screen, text fields included (Fn is held, so nothing is typed). ? too, outside Text Entry.
Q197 It opens a panel over the content area, titled with where you are: the screen's own keys, then an "Everywhere" group (Back, Home, the arrows, the help key). The arrows scroll it; any other key closes it and is not passed on.
Q198 Each App answers "what are your keys right now?" for the state it is in; pages, viewers, dialogs and text fields answer for themselves, with shared lists for dialogs, lists and text entry. The lists are constants; the panel's rows exist only while it is open.
Q199 Every hint that names a key goes, text fields included. What stays is state: REC 12 points, LOG 42, sort:signal, typing/saved, what is waiting to be pasted, the Sweep's floor. Messages were reworded where they named a key ("v pastes a copy of…" is "Copied …: paste it where you like").
Q200 The first-start Setup keeps its hints, and is the one place that does: someone in their first minute doesn't know the help key yet. It tells them about it on its first and last screens.
Q201 Loud everywhere else: the user guide opens with it, the FAQ has it first, and a device set up before this firmware gets one Toast, once: "Fn+h: the keys of any screen".
Q202 key help over the consoles. Generating the website's key tables from the same lists is a follow-up, not this issue.
Q203 The key and the panel first, host-tested; then one App at a time, declaring its keys and losing its hints in the same step; then every screen looked at on the device.

As built

  • Key::Help from the key mapper: Fn+h in both modes, ? only outside Text Entry (lib/input, 3 tests).
  • App::help() and App::helpTitle() (lib/core/src/app.h), KeyHelp rows and HelpModel (key_help.h). The App manager opens the panel, appends the "Everywhere" group, and while it is open takes every key: nothing reaches the App, Home included. It closes when the App changes (5 tests).
  • Every App declares its keys by state: the Launcher, IRC (chat, settings, a field), Wi-Fi Tools (4 views), GNSS, Gemini (page, saved page, address, answer, dialogs), the LoRa Scanner (4 views), Storage (browse, details, a name, the viewer's 6 modes, the editor, Maintenance, busy), Notes (list, editor, a file name), System (5 views), Settings (menu, text, choice, and the Wi-Fi, Firmware and Debug Console pages with their own states), Setup and the widget demo.
  • The hints are gone from all of them. The footers that remain say state only.
  • It costs 8.5 KB of flash and 40 bytes of static RAM.

Checks

Check Result
Host tests 476 pass (468 before)
On the device, key help and a screenshot The Launcher and all nine Apps, and these states: Storage scrolled, System's tasks, a Settings text field, Wi-Fi Tools' networks. The panel is titled with the scope, lists the right keys, scrolls, and closes on Tab
The one-time Toast notification: Fn+h: the keys of any screen on the first start after the update

One source for the device and the website (issue #72)

The lists first lived in each App's help(), as code. They are now data, in one file: lib/core/src/app_keys.h, 52 constant tables, each under a comment // id: Title. An App's help() picks the table of the state it is in. site/tools/gen_dev_docs.py reads the same file and writes site/data/keys.toml; the keys shortcode shows a screen's tables on its guide page, and /guide/keys/ shows all of them. The Site job fails when the data file is out of date, or when a page asks for a table that doesn't exist, and it now runs when app_keys.h changes. A key added to an App shows up on the website without anyone editing a page.

Three rows lost their second wording on the way, since a table is constant: GNSS's Tab and r, and the Scanner's c, now say both things they do ("record a Track, or stop it") instead of the one that applies. The guide pages keep their written tables too, where they say more than a key list can; those can still drift, and the generated ones under them are the reference.

Not checked: the real Fn+h and ? on the keyboard (the mapper is host-tested; the device was driven with key help); the Setup screens, which only a device that was never set up shows, so their new text has not been seen on a screen; and the states that need something to happen first (a dialog, a copy in progress, a Gemini prompt, a packet's details): their lists were read against the key handling, not looked at.

The screenshot key (issue #83)

A screenshot could only be taken by typing screenshot in the Shell, where "now" is a picture of the Shell.

  • Fn+p, on every screen, text fields included, saves the screen as it is (a dialog, the help panel or a Toast if one is showing) as a PNG in /screenshots, the way the Shell's command does. A Toast says so once the file is written, so it is never in the picture.
  • It never reaches an App. Key::Screenshot comes out of the key mapper and is handled before the App manager, so the help panel stays open and a dialog keeps its selection.
  • Not on Settings > Debug Console: that page shows the token, and a picture of it is a copy of the token in a file. App::showsSecret() says so, and the key answers with a Toast instead.
  • No card: a Toast says so.
  • No setting to switch it off: Fn with a letter isn't pressed by accident.
  • It is in the "Everywhere" group of the help panel, and so in the website's key tables. key shot presses it over the consoles.

Checked on the device (2026-10-07, with key shot): in the Launcher, a 33,383-byte PNG appears in /screenshots and is the Launcher; with the help panel open, the picture is of the panel (which now lists Fn p) and the panel stays open; on Settings > Debug Console, no file is written; back on the Settings list, one is. The test pictures were removed.

Not checked: the real Fn+p on the keyboard (the mapper is host-tested); the two Toasts that refuse, which weren't looked at (one of them is on the page that mustn't be photographed); a device with no card.

The Launcher as a grid (issue #9)

The Launcher was a list of names. It is now a grid of icons.

Question in the issue Decided
Layout 4 × 3 tiles of 60 × 36, so the 11 Apps are on one page. Only the selected App's name is written, on a line under the grid: a name under every tile doesn't fit 60 pixels ("LoRa Scanner"). More than 12 Apps: the rows scroll
Icons The website's own: its nine 16 × 16 pictures (site/static/img/icons-dark.svg), doubled to 32 × 32, and two drawn the same way for SSH and the Shell. 1 bit a pixel (128 bytes each, in flash). The pictures are PNGs in assets/icons/, named after the App's id; scripts/make_icons.py writes src/ui/icons.cpp from them and CI checks the two match
Colours The website's: cyan (#00dbff) on black, and the selected tile slate (#494955, the Status Bar's) on a cyan fill with notched corners. Both are RGB332 colours. The name under the grid is white
Live state A dot on the tile, from App::badge(): IRC (unread messages), GNSS (a Track recording), the LoRa Scanner (a Capture running), SSH (a session open). The Launcher asks every App on each pass and redraws when an answer changes
Order As registered, as before
Shortcuts None for now
The list Kept: Settings > Launcher, Grid or List. Both keep the same selection
  • GridModel (lib/ui) holds the selection and the scrolling, host-tested: left and right go through every item and wrap; up and down stay in the column, and land on the last item where the last row is short.
  • An App registered without an icon shows its first letter in a frame.
  • No animation: not asked for, and not measured.

The website's colours, on every screen

Every screen takes its colours from src/ui/theme.h, so the palette changed there, to the website's (site/static/css/site.css, dark side). Each is a colour the RGB332 frame buffer holds exactly.

Was Is
Accent teal-blue cyan #00dbff
A selected row, a dialog's chosen button darker teal, white text cyan, dark blue text (#000055)
Status Bar #202020, which the frame buffer showed as a dull olive slate #494955; what is idle on it in light grey #b6b6aa
Muted text grey, as the frame buffer rounded it the same grey, exact: #9292aa
Warning orange the website's orange #ff9200
Someone wrote (unread count, a mention, a message Toast) green pink #ff92ff
Good (a Fix, the quietest channel, a live task) the same green green #49db55, the one colour that isn't the website's: it has no green
Text on a Toast black dark blue

Looked at on the device, by screenshot: the Launcher, IRC, Wi-Fi Tools (menu, channel occupancy), GNSS (position, sky), Gemini, the LoRa Scanner (Sniffer, Sweep, presets), Storage, Notes, SSH, the Shell, System, Settings and the help panel. Not looked at: dialogs, a warning or a message Toast, the SSH terminal (which keeps its own 16 ANSI colours), the Setup screens.

Themes (issue #10)

Seven themes, each with a dark and a light side, chosen in Settings > Theme and Settings > Light or dark: roro9stack (the website's, the default), Catppuccin (Mocha and Latte), Dracula (and Alucard), Nord, ANSI terminal, Gruvbox, Solarized.

Question in the issue Decided
Which themes The seven above. Not the high-contrast and phosphor ones the issue suggested: ANSI terminal dark (grey and green on black) and light (black on white) come close
Fonts and spacing too No: colours only
Themes from the SD card No: built in
A preview while picking The Setting applies at once, so the Settings screen is the preview
Constellation colours From the theme (its accent, its "good", and three roles named red, yellow and violet), so they read on every background
Light by day, dark by night No
  • A palette is 14 roles (lib/ui/src/palette.h): background, text, muted, faint, the bar and its idle marks, accent, what is drawn on an accent, warning, message, good, and red, yellow and violet.
  • theme::kText and the others are now references into the palette in use (src/ui/theme.h), so no App changed: they still read as constants. The main loop applies the palette when either Setting changes, and draws everything again.
  • RGB332. Every colour in the table is one the frame buffer holds exactly. The themes' own colours were rounded to those; where two roles fell together or lost contrast, one was picked by hand (Nord's accent and muted text, Gruvbox's greys, Solarized's surfaces). test_palette checks that every colour is exact, and 18 contrast ratios for each of the 14 palettes (text on background at 4.5 or more, the rest at 2 to 3).
  • What it costs in fidelity: blue has four levels, so Dracula and Nord share a dark background (#242455), which was Catppuccin's too: Catppuccin dark is on black instead (its "crust"), with that blue for its bar and its brighter accents (peach, pink, green, sky), to tell it from Dracula; Solarized light's cream becomes white, and Gruvbox's dark brown becomes a dark olive (#242400).
  • theme [0-6] [light|dark] on the consoles lists them or picks one.

Looked at on the device, by screenshot: the Launcher in all 14; and in Gruvbox light, Settings, GNSS (position and sky), the Sweep, Storage, the help panel, Gemini and Wi-Fi Tools' channel occupancy. Nothing was drawn for black only.

Kept as they are: the SSH terminal's 16 ANSI colours and its black background, the Sweep's waterfall scale, pictures, and the screen shown while the device powers off.

Not looked at: the other 12 palettes beyond the Launcher, dialogs and Toasts in any theme but the default, the Setup screens. In Gruvbox light, the Sky view's GPS, GLONASS and BeiDou colours are three close shades of brown and red.

Settings in groups

Settings had grown to 21 rows in one list. It now opens on five groups, each a short list of its own:

Group Rows
This device Long name, Short name, Region, Timezone, Sound & LED
Display Brightness, Dim after, Screen off after, Theme, Light or dark, Launcher
GNSS and radio GNSS, Pause GNSS for LoRa, Coordinates, Probe MACs
Network Wi-Fi, VPN
System Check for updates, Firmware, Debug Console, About
  • Enter opens a group, Back returns to the groups with the selection on the one just left, and Back from the groups leaves Settings. An open group has its name over its rows.
  • SettingsMenu (lib/apps_model) holds the groups and which one is open; every index is into what is listed. Host-tested: every setting is in exactly one group, and no group has more rows than fit under its name.
  • The guide, the how-tos, the README, the scripts' messages and the firmware's own (the Toast for a new release, the GNSS App's hint) name the new paths: "Settings > System > Firmware". The milestone notes and the devlog keep the paths of their day.