0eb9845cd9d1d4e4e45d22d545a41cb997ae09f7
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8ac0be0928 |
The battery gauge is a block, filled in eighths
One braille cell was four coarse levels lit in one colour. It is now a filled block whose two channels answer two different questions. Colour answers "roughly how much" from across the room: a lit foreground over its own darker ground of the same hue — light green on green over half, yellow on brown over a quarter, light red on red below. The braille fill inside the block answers "exactly how much" on a second look: each dot row lights left before right, so one cell carries eight rungs, one per 12.5% of charge. The band holds still while the dots climb through it, which is what keeps the two readable as two. The charging sweep keeps its five whole-row frames and does not colour itself — a frame of an animation is not a reading, so the band goes on answering for the real charge underneath it, exactly as the number beside it does. That leaves the alarm nothing to say in hue, so it says it in ground: under 6.25% — half the last dot's worth of charge — the cell alternates black dots on red with the light red on the bare page. Where every other cell is filled, a beat with no fill at all is the loudest thing the palette has. It is drawn a frame at a time because a terminal's own blink only ever toggles the foreground, and the PicoCalc's framebuffer console ignores SGR 5 outright — device-checked, along with its want of bright backgrounds, which is why the grounds are all dim-bank slots. Neither animation sits behind Platform.effects any more. The header is rebuilt on the idle tick regardless, so both cost a colour swap in a frame already being painted, and the one platform the alarm is *for* was the one platform never drawing it. effects keeps the braille spinner, which is what it was really gating. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c66e2c86b6 |
Frames: the border is one colour again, so drop the corner light
Every framed box was post-processed on the way to the screen: the top-left corner blended toward white and the highlight decayed along the top and left edges, so a box read as lit from that corner. It reads as a gradient laid over the chrome rather than as the box being a box. The borders draw flat now. The pass goes with it — ui/tui/glow.py, and the three composers that ran it (base, startup, dialog), each of which also carried the platform gate that kept it off the 16-slot console where it could only ever hand back the same lines. Nothing else called it, and its memo went with the module. Text colour is untouched: the pass only ever recoloured box-drawing glyphs, and titles and subtitles were already skipped. The four glow tests become one that pins the outcome instead of the mechanism — every box glyph in a composed frame, nested panels and dialogs included, carries the border's own colour and no other. Verified against the old pass: with it back, that set holds sixty-five. Platform.effects keeps its other two claims (the header blink, the braille spinner); it just no longer mentions a pass that no longer exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3376a18271 |
PicoCalc: a command row's icon lane is the first thing to go
Every icon on the console is a one-glyph stand-in for an emoji, and the menu is where that pinches hardest: the main menu drew @ for both Map and Mesh walk, … for both Live feed and Time machine, and ?? for Trace path, in a lane costing three of fifty-three columns on every row (JP, on-device). So drop the lane there, mark and emoji alike. A command row's icon is decoration — it names the action's family while the label names the action — so where it can only be approximated it is worth less than the cells it takes, and the label and its description get them back. This is a platform choice (Platform.menu_icons), not a per-emoji one: a glyph that carries *data* is not an icon in this sense and never goes, which is why the Channels page keeps its #/🌐/🔒 openness mark, the feed keeps the class icon it shows in place of the class word at 53 columns, and node types, status marks and the mute mark in the channel lane all stay put. Three funnels do the work, so a screen never asks the platform itself: command_label for a label carrying its own icon, command_icon for a row that builds its own lane, and marked_label where the icon carried a tint — a delete row's red moves onto its words rather than leaving with the 🗑. Section headings go through the same cut (a stand-in beside the ── rule is pure noise), which also retires the ?? that No revisits was drawing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ce86e86693 |
Packet card: two columns back from the gutter, and rssi holds its line
On the PicoCalc the viewer's reception row folded the bare word "rssi" onto a line of its own. Two things were spending the cells it needed. The dialog gutter: every floating box left six columns to the backdrop it sits over. Three a side reads as floating at 72; at 53 the same gutter is a twelfth of the whole display, and it costs content. How wide the gutter is worth being is now the platform's call (Platform.dialog_margin), and the console's is four — still floating, two columns back. The separator: the row chained its two atoms on the roomy " · " no matter the lane. That is the header's problem too, and the header already solved it — roomy by default, compact when the cells run out — so the row now takes the same trade, sizing itself at render time like the via path beside it. Two cells alone would have left a two-digit SNR beside a three-digit RSSI still folding; together they fit with a cell to spare. The roomy/compact pair moves to ui/menus.py, one definition for both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
935b4fb3b6 |
PicoCalc: nodes colour by key, like everywhere else
A platform's ceiling may change a rule's resolution, never the rule. P3 had picocalc colour node names by *type* instead of by key, so a name's colour there meant something different than it does on the desktop — the node's kind, not its identity. Both platforms now run one rule: the key's first byte picks the hue. The console just spends fewer of them. node_style gains the binding the heat scale already had. Regular keeps the full 256-hue wheel; picocalc snaps the same hue to its sixth of it — the six chromatic bright slots, ~43 first bytes each. It returns the slot's own hex rather than a color(N), so every downsample on the way out (Rich's, and the fold's quantizer) lands on that exact slot instead of guessing a neighbour, and the callers that read an RGB back out of a style string keep working. The type marks needed the opposite treatment: their hues are fixed, not derived, and a naive nearest-RGB match drained the repeater's violet to grey — a repeater read as an unknown node. They move to the theme's type.* entries so each platform picks its slot deliberately, and a braille raster resolves the same entry through theme.mark_rgb, so the canvas marker and its Rich-drawn twin can't drift. With names off the type registry, nothing reads it: Platform.name_colour and core/nodetypes.py go, along with the three reception-layer feeders. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2f8f77be49 |
Review round: the standard palette, by JP's call
The console keeps its stock kernel palette - black background, classic PC hues - instead of the custom setvtrgb remap. MESH_THEME_16 is re-tuned against the stock slots, which hands back the map's own language for free: clients wear bright pink (13), repeaters purple (5), rooms cyan (6), sensors brown (3). The cost is one dark grey - muted, faint and track all collapse onto slot 8. Heat quantizes on plain human units (15 min / 1 h / 1 day). The custom design is archived, not deleted: theme._VT_SLOTS_CUSTOM + MESHTERM_CUSTOM_PALETTE=1 on the font script reinstalls it, with the parity test keeping the two in sync. Undoing the remap on the device exposed a trap: setvtrgb overwrites the kernel's DEFAULT colormap, so the OSC palette-reset "resets" straight back to the custom values - the script's default mode now programs the stock palette explicitly (verified live; specimen screenshot in .claude/plans/p3-specimen-stock.png). The header also follows JP's spec: the handheld brands the app - "MeshTerm vX" - instead of naming a soldered port that never changes, and the activity pulse takes whatever room remains. Also root-causes the intermittent suite flake: Rich memoizes a parsed Style's ANSI codes on first render (Style.parse is globally cached), so a raw hex style first rendered by a truecolor console replays 38;2 verbatim on a standard console later in the process. The specimen's nodes row now wears the type.* theme styles - which is also just correct: the marks now match the names row. Six consecutive clean full-suite runs. Deferred at JP's request: the glyph substitution map (he will redo it), the F-key lane design (awaits his explicit spec), the CLI fold gap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
84caae53f8 |
P3 rework: the PicoCalc visual language, done properly
The first P3 (a84abf3) ran on the wrong model and its palette never reached the TUI: MESH_THEME_16 was written in 256-colour cube indices instead of the 16 VT slots, and the platform theme pick was wired into the scripted-CLI console while the real rasterizer stayed hardcoded to truecolor. Replace it end to end: - theme._VT_SLOTS: the canonical slot->RGB design (conventional hue families kept; slots 5/6 become the track/faint slates), MESH_THEME_16 rewritten against color(0..15) with the VT's bold-as-brightness and dim-bank-background rules pinned by tests. /etc/vtrgb + a boot oneshot install it (console-font script), OSC fallback when kbd is absent. - platforms.on_platform: set_platform now re-runs registered bindings, so theme, rasterizer and widgets swap implementations once per switch and hot paths read module globals - no per-call get_platform(). - The render-boundary fold (theme.fold_text): one str.translate pass (NFKD accent folds - the Terminus base has no accented Latin - plus symbol stand-ins and the width-padded emoji map), an embedded-SGR quantizer for canvas truecolor, and a ?-at-own-width catch-all that makes the no-wide-glyph short-circuit true by construction. Applied at the only two places every visible byte passes: render_to_ansi and MapCanvas.to_ansi_lines. - ui/fontset.py freezes the installed font's inventory (dumped live); the gallery now asserts rendered picocalc ANSI carries no truecolor/ 256-colour SGR and no character outside it - the parity gate the first attempt faked by asserting a dataclass field. - Font build: nine new 6x12 marks (ellipsis, warn, backspace, shift, gear, refresh, clock, crosshair, padlock) drawn into donor slots, with donor misses now fatal and a keeper canary (donating pi once erased Cyrillic pe - shared slot). Table re-dumped on-device: identical to fontset.py, 519 = 519. - Type-based name colouring: registry moved to core/nodetypes (services no longer import ui), fed from monitor observations, heard_nodes and merge_contacts. Heat gradient quantizes to heat.* steps on picocalc. - ui/marks.py: the P2-deferred constant relocation - widgets no longer drags map_render/mapcanvas/pathgraph for tuples and aliases. - meshterm specimen: the visual language on one card through the real funnels - the on-device acceptance screen (JP's request). - tools/chat: delivered/no-ack state uses the standard check/query marks, not the banned emoji pair. Verified on the Lyra: font+palette installed, specimen framebuffer captures in .claude/plans/. 1259 tests green, 13 xfailed (P6 list). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
63cd4e5f96 |
Performance: SQLite WAL, lighter startup, per-platform tick rates
PicoCalc plan phase P2, driven by measurements taken on the Lyra as a paired A/B (git archive HEAD vs the working tree, run side by side against the same venv). The headline is the database. Every packet the monitor hears is one durable commit, and on the SD card each was costing 33.78 ms of fsync. db._tune() opens in WAL with temp_store=MEMORY: 0.59 ms per commit, a 54x cut. synchronous=NORMAL is requested only once WAL is confirmed engaged - the pragma returns the mode it settled on rather than raising when a filesystem refuses it - because under WAL it risks the last transactions to a power cut, and under the rollback journal it risks the file itself. This device runs on a battery pack. Startup: bleak/dbus-fast were already lazy; the real finds were two cheap *values* dragging heavy subtrees. core/connection.py imports one constant from core/channels.py, which imported PyCryptodome at module level (224 ms of CPU-feature probing per run) - now deferred into the two functions that decrypt. And tools/map.py's register_cli, which runs for every tool at boot, reached into ui/map_screen.py for a --fraction default, dragging the basemap's urllib.request -> http.client -> ssl behind it; DEFAULT_VIEW_FRACTION moves to core/geo.py (pure arithmetic, already on the boot path) and is re-exported so existing importers are untouched. 26 modules and 316 ms of import self-time gone. Per-frame: the corner glow only ever recolours truecolor foregrounds, so on a 16-slot console it scanned every line of every frame to hand back the identical list - skipped outright where effects are off, along with the battery blink and the braille spinner cycle. _has_wide_glyph answers from the platform when no emoji can be drawn instead of scanning the composed frame (6.5-9.1 ms per repaint on its own). The header built its segments twice to measure the compact separator, on exactly the narrow terminals that take that branch every frame; it now builds once and measures arithmetically. Together: 101.2 -> 73.2 ms per frame at 53x26, 151.8 -> 110.4 at 53x40, and 72x24 unregressed at 91.7. That finally gave numbers to set the clocks against. refresh_interval becomes per-platform tick_s (2.0 s on picocalc: a 1 Hz idle tick alone was 7-11% of a core doing nothing, and only the header's per-minute pulse drifts, since screens push their own repaints via invalidate). The 0.12 s spinner was untenable - 84% of a core at 53x26, and at 53x40 it did not fit in its own interval at all. Five duplicate _SPINNER_INTERVAL = 0.12 constants across the UI collapse into one platform-aware spinner_interval() beside the Spinner it paces. The header refactor was verified equivalent over 540 renders, plain text and style spans both. The span check earned its keep: the first cut passed style= to the Text constructor, which makes it the object's base style and blankets everything appended after, so the version string would have rendered brand-under-muted. test_header.py only asserted on .plain and never saw it; it now pins the styling too. tests/test_performance.py pins each contract as behaviour rather than timing, including a subprocess guard that fails if Crypto, the network stack, or the map screen ever creep back onto the startup import graph. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8f1fba135e |
Platform seam: meshterm/platforms.py, --platform flag, dual-platform gallery
One frozen Platform spec (REGULAR/PICOCALC), resolved once at boot (--platform flag > MESHTERM_PLATFORM env > /proc/device-tree/model auto-detect > REGULAR) and read via get_platform()/set_platform() - never a stale `from platforms import PLATFORM`, which the module docstring calls out. `meshterm platform` reports the resolution and everything it looked at. Wires the two correctness bindings that matter before any flavour work lands: width_reclaim off on PicoCalc's exact-width console (session._reclaim_last_column, env override still wins), and the emoji-width calibration probe skipped outright when a platform never draws emoji. tests/test_gallery.py renders all 16 screens the interactive menu opens against REGULAR (72x24) and PICOCALC (53x26 live floor, 53x40 lux case), asserting no line overflows its platform - 35 passing cases, 13 xfailed as a real P6 worklist (contacts, map, walk, livefeed, remote_cli, path_composer). Also caught and left alone: the map screen's footer hint already overflowed the *existing* 72-col standard before this seam existed, unrelated to picocalc - xfailed rather than fixed as a drive-by. tests/test_platforms.py unit-tests the module directly; conftest.py gained an autouse fixture resetting the platform between tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |