ui-new: Status › System, Enter shows it full screen. L2: Settings › About.
One precomputed 25 x 25 matrix (ui-core/SiteQr.h) for both, so no QR
library; the URL in upper case keeps it at version 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Nearby › Map (ui-new): the positioned nodes and you on one view, scaled
to fit, with north, a scale bar and the selected node's name and
distance; Left / Right picks a node, Up / Down zooms on it, Enter opens it.
- Clock tools › Calendar (ui-new): the month with today and the alarm's
days; a tight grid on small screens, Left / Right pages the months.
- L2 Settings: a Message banner switch (on / off) in place of ui-new's
three-way alert setting.
- Simulator: sim_nodes() puts eight positioned contacts around Kraków.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui-new's messagesInView() and ui-lvgl's threadInView() hold the
lit-and-unlocked test the viewing checks repeated; the keyboard's three
copies of the backspace call deleteBeforeCursor(). A stale comment on the
cursor-mode hint goes, onDisplayOn() sits after the view helpers it calls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cursor marks the character it stands before instead of pushing the
text apart: underlined while typing, an inverted block while moving it.
The grid stays on screen with the special row turned into the key hints;
Up/Down move a word, Hold-Enter deletes before the cursor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A conversation left open under the lock screen or a dark display no longer
counts as read: the counters and the lock screen's tally go up, and on
waking (L2: on unlocking) the conversation reopens on its first unread
under the "New" line, a typed draft kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hashtag channel names already start with '#'; the tiles added another
("##test"). chanctl::bareName() is now the one place that strips it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contacts and paths are saved a while after the change and never while the
user is active, so a send no longer stutters on the 53 KB contact rewrite
(a path that came back unchanged isn't written at all). The clock is saved
every 30 minutes and restored no earlier than the firmware's build day.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both UIs: a channel, DM or room with unread messages opens on the oldest of
them (MessageHistory::firstUnreadChannel / firstUnreadDm, own messages not
counted), a "New" line above it, so it reads down to the newest. Nothing
unread: the newest, as before.
L1: the first unread is selected and scrolled up top with the line in view;
the channel's unread count now counts down as you read towards the newest.
L2: the page holding it, scrolled to the line; an incoming message scrolls
down only when the list is already at its end. Sim hooks: sim_unread (L1),
sim_open("unread") (L2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Display > Message alert: Normal keeps the sliding banner; Compact shows an
envelope with the unread count beside the battery (in the header line where
there's no status row), blinking for 2 s; Off shows nothing. The blink only
ever brings the next frame earlier, so it no longer holds back the UI's
animations while it runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The limit is what the send leaves of MAX_TEXT_LEN: a channel post carries
"<node name>: ", a direct message resent more than three times keeps two
bytes spare, and placeholders count as they expand (msgtext::limit/sentLen).
Bytes, not characters. The path and packet header sit outside the payload;
static_asserts keep a full message within the radio frame on the longest path.
From 40 bytes left the count shows: on the L2 as a tag on the field's top
edge, on OLED/e-ink at the right of the preview's last line with the text
wrapped short of it. Input stops at the limit; a send still over (a
placeholder grew) is held. Sim hooks to open a compose with text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ui-lvgl: navTextEntry()/TextEntry replace eight copies of popup + field +
keyboard wiring; one byte-capped insert callback instead of two.
- ui-new: drawHeadedBubble()/BubbleMark replace the duplicated DM and channel
bubble drawing in MessagesScreen.
- GpsSky::halt(): stopping the receiver ends an unfinished search but keeps
the first-fix time; keyboard cursor blink restarts after a key.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VeBrxKyHHxWsmttVkUdBrj
- GPS: stop() resets the sky state so the 'Searching' timer does not run
while the receiver is off; the status page shows it only when GPS is on.
- L2 keyboard: put the FOCUSED state back after typing a key or picking a
variant, so the cursor is drawn again after characters like a-ogonek.
- Quick panel: drop the full-screen dim (a blend per frame), so the screen
below stays interactive; tiles are one sideways-scrolling, snapping row;
the brightness slider no longer animates and only updates the backlight
when the value changes.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VeBrxKyHHxWsmttVkUdBrj
/prefs holds a header (magic, format version, size, CRC-32), the node's
position and NodePrefs as it is in memory, in place of /new_prefs' two
hand-kept field lists and 0xC0DE00xx schema bumps. A new field goes at the
end of the struct; moving one bumps PREFS_VERSION. sanitize() keeps every
value in range.
The first boot after an update from before 2.0 imports only the first
block of the old file, laid out the same in upstream MeshCore and Solo
(name, position, radio, BLE PIN, scope), then deletes it; everything else
starts from the defaults. Contacts, channels, scopes, waypoints and trails
are untouched.
Gone: the page-order and GPS-interval migrations, the unused
trail_interval_idx / trail_units_idx / repeat_extra_scopes; fav_sort_off
and msg_wake_screen_off become fav_sort and msg_wake (on by default).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The OLED gets the arrows towards nodes in Nodes and the Path row with its
hop rings in a stored node's detail (its position on one line to make the
room); no compass rose. Trail gets a height-profile view on both. The
Status history, trail profile and calendar come from ui-core. A sim hook
turns on the advert's position for tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landscape build's 8x13 digits stand 9 px, so its status, page and
check glyphs and the row-heading map markers come in a 7 px set instead
of 5. The battery outline, in the header and on the Status tile, leaves
its corner pixels out.
The page icons go back to a row of their own under the header: beside
them on the header line only three status icons fitted, and the rest
went missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Message ages, the GPS search time and the sky plot changed every second,
and each change is a panel refresh. On e-ink an age under a minute reads
"<1m", the search time counts minutes, and the sky redraws every 15 s
(at once after a key).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tall screens stack the Sky view and the map over their rows, show more
recent chats, give favourite cards the heard age and distance, and put
the trail map under the trail summary. Wide screens put the page icons
on the header line.
E-ink only (Features::IS_EINK):
- a month calendar under the clock and on the portrait lock screen, with
the alarm's days marked; the lock screen also lists unread messages,
the alarm and the clock fields
- 8-hour charts in Status: battery, noise and SNR, mesh traffic
- an elevation profile under the trail map, with the climb and the top
- a compass rose and the path's hops on a contact's detail, arrows by
the distance in Nodes rows and on favourite cards
- an ordered-dither fade under chart lines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A screen with room for nine lines or more shows each message bubble
fully wrapped instead of one line. The bubble's height is measured at
the width it is drawn at.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wrapLines carried the overflowing space's word to the next line too
early, so bubbles wrapped short of their width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The L1 e-ink solo build splits in two, one per screen shape, so each
screen can be laid out for a single shape. Settings > Rotation now only
turns the screen upside down, and the joystick turns with it.
Landscape writes in misc-fixed 8x13 at the panel's own resolution
(EINK_LARGE_FONT); portrait keeps the 6x9. All drawing moves into
EinkGfxDisplay, which the panel driver and the simulator share; the
FreeSans fonts and the CRC32 library go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ui-core: Diagnostics::systemLines, NearbyModel::typeShort,
rptctl::currentPresetName, RoomSessions::pendingKey, settings::isSwitch,
UiCore::reconcileDMUnread.
- ui-new: the retired Recent page, fmtAlt, the battMvToPercent wrapper,
setFavouriteChannelSlot, AccordionList onHeader/visibleCount, drawBoxedIcon
and seven unused icons; info::row folded into info::valueRow.
- ui-lvgl: onOpenNav, TileDownloader::withTrails; the trail header size is
one constant (TRAIL_HDR) for live and saved trails.
- Home quick panels save prefs only when a value changed.
- Comments brought in line with the code (status bar reserve, settings rows,
page order, SH1106 frame skip, UiCore header).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The redraw, alert, splash and sensor timers compare millis() wrap-safe
(refreshDue(), alertShowing(); 0 = now / none), so the screen keeps
refreshing past the 49.7-day rollover.
- A failed send leaves the keyboard with its text ("Not sent" in the field
label, as alerts don't show over it) or the quick-message list with
"Send failed", instead of dropping to Home. No "Sent" echo.
- The dashboard's Sats field shows "--" with the GPS off, not a stale count.
- Brightness and volume in Settings stop at both ends instead of wrapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Home clock page draws HH:MM in the boot splash's slanted digits, with a
seconds bar, a centred date and the clock fields as columns. The lock screen
keeps the status bar, puts the big clock under it and shows unread messages
in a pill; the unlock hint appears for a few seconds after a key press on the
lit screen (not the waking one). Settings > Display > Lock clock adds a
compact look: a small clock with the date beside it and the clock fields as
rows (NodePrefs lock_compact, schema 0xC0DE0033). 12-hour time sets AM / PM
beside the digits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Battery curve: NodePrefs batt_curve_mv[11] (voltage at 0..100 %, all
zeros = built-in LiPo curve, sentinel 0xC0DE0032). ui-core/Battery.h
uses it for every percentage. L1: Settings > System > Batt curve list
(Left/Right 10 mV, Reset); L2: Settings > Power > Battery curve chart
with draggable points and a line at the cell's voltage.
- L2: the Nodes map is the full navigation map plus the nodes, Back
returns to Nodes; the favourite button is lit while it is one; pinning
to the Home dial uses a thumbtack (fonts regenerated); minimap dots
follow the accent; arranging tiles shows where they land while dragging;
emoji row in Diagnostics > Font.
- Sim hooks: sim_open nodesmap / accent@N / homeedit / battcurve.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Icon pages become quick panels: two or three rows (Settings, Messages,
Bluetooth, Advert, Tools, Power) inset from the edges, the last one
opening the full screen. Brightness and auto-advert are edited in place
(Enter, Left/Right, Enter/Back saves once).
- Tools panel shows the two tools used last with their live state.
- Map page: preview on the left, GPS / trail / live / target on the right.
- Favourites as soft cards; Status tile with a battery spark line.
- Status bar without boxes or blinking; BT only when connected, GPS
search glyph without a fix.
- Redrawn mini icons (advert waves, cog, wrench, messages); big hovering
icons removed. Tools list is text only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
drawScreenHeader replaces drawCenteredHeader on every ui-new screen: the
title in normal case, page dots instead of "TRAIL 1/3", the menu hint, no
icon (the screen was just picked by one). Accordions in Settings and Tools
show a disclosure triangle instead of +/-, Tools sections drop their icons.
Form "[Save]" rows and "+ Send" share drawButton; form fields use valueRow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InfoKit gains valueRow (value at the right edge, cut and scrolling when
selected), switchRow and listRow (exact ON/OFF becomes a switch). Settings,
Live Share, Locator, Repeater, Bot, Auto-Advert, alarm, GPIO and popup value
rows use them; units after a space, "Off" in title case, Trail summary as
blocks with a big distance and a points meter, "+ Send" as a soft pill.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home's Radio, GPS and Sensors pages become one Status page: four tiles split
by dotted rules (radio, GPS, battery, nodes), each a headline over a detail.
Enter opens the Status screen, tabs Radio / GPS / Sky / Power / Mesh /
System, which also takes over Tools > Diagnostics and Tools > Satellites
(the sky plot now lives in SkyView.h). GPS on/off moves to Enter on its tab.
New InfoKit.h holds the shared pieces, sized from the font: label/value row
(a long value takes its own line), centred section title, meter, chip, big
number with unit, switch, history line (one sample a minute) and a block
flow that scrolls a tab by whole blocks.
The Status page keeps the old Radio bit and shows when any of the three old
pages did; the save format is unchanged. The L1 degree sign was a cp437
byte the UTF-8 renderer drew as a box; now U+00B0. The perf walk covers
every Home page and Status tab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DisplayDriver gains slideBegin()/slideCompose(): the driver keeps the frame
on screen and, in the next one, moves it out while the new rows follow.
SH1106 does it on its 1 KB buffer (+1 KB RAM for the copy), the sim on its
canvas; drivers that can't read their frame back keep cutting over (e-ink).
Home slides the content below the page-icon row in 220 ms with an ease-out
(the whole screen to and from the header-less clock), and the current-page
pill glides between icons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Splash: the L2 wordmark and lettering, shared through ui-core/Lettering.h,
rise in with loading dots under the version line (static frame on e-ink).
- Home: the header shows the time once the clock is set; icon pages carry
only their title. One 32 px icon family (3 px strokes) bobs 2 px over a
dithered blob shadow that shrinks as it rises; the unread badge rides on
the Messages bubble; Bluetooth off is the icon at half density.
- Soft corners on every selection, the page pill, tabs and popups;
dotted scrollbar track with a rounded thumb; the tab bar lays out whole
tabs and shows edge arrows only in free space.
- DM bubbles drop the sender row and wrap to a second line, with the
delivery mark and age closing it.
- Keyboards name what is typed (channel, contact, Password, Scope...)
and show characters left near the limit; PIN entry has its own preview.
- Loading dots for waits (login, scan, GPS) and signal bars in place of
raw RSSI in Nearby.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DisplayDriver gets drawSoftRect / fillSoftRect / drawPanel (the unread badge's
corner knockout; a panel also clears a 1 px moat so lines below stop short of
its border). Used by alerts, popups, the accent picker, the lock hint, chat
bubbles and the send button.
Fixes: no "Msg:" toast for a message landing in the open thread; Nodes scan
text fits the screen; the PIN pad skips the blanks beside 0; Auto-Advert is a
plain value row; the Admin picker says what it needs when empty; the GPS page
shows "-" instead of 999 before a first fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opens each screen directly, types keys, and prints per step over USB: frames
drawn/sent, render and flush time (CPU cycle counter), heap used/free/largest
block, every task's stack headroom, and a screen dump. Measurement only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Trail on the L2: 32768 points every 5 m, no simplification, no point
spacing setting; copied to the card every minute and restored at power-on.
The line keeps up with the position dot (incremental sync, pending point).
Map: a tap's finger jitter no longer turns follow off; the GPS button
shows when the map follows. The trail is drawn as one polyline with
breaks and off-view chunks skipped (an lv_line queued a draw task per
point).
Fewer rebuilds: conversation bubbles patched (new messages, delivery
marks), home mini-map dots reused, Nearby rows rewritten in place,
history read in runs and each file opened once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since bf1e6fca kbApplyCapsUtf8 wanted 4 bytes free before each codepoint;
the ABC grid passes a 3-byte cell buffer, so every cell came out empty (T9
and CardKB Compact draw no such cells, so it went unnoticed). A codepoint
is now copied when it fits with the NUL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The contributor's feature -- a PIN asked before the screen unlocks, on
boot, and when the magnet cover opens, stored as a salted SHA-256 -- on
top of this branch, with:
- ui-core/ScreenLock.h: set / check / isSet and the tries limit, shared by
L1 and L2. isSet looks at every byte (the PR tested only the first, so one
PIN in 256 silently disabled the lock). 5 misses pause entry for 30 s.
- NodePrefs: the fields at the tail after quiet hours, sentinel 0xC0DE0031,
sizeof 2880; an older file's bytes there are cleared. A sentinel bump no
longer resets display_brightness_pct from a file that already has it.
- DataStore: no AGPL header (the project is MIT), no whitespace churn.
- Keyboard: the number pad is a widget flag (pin_mode) with a prompt in the
preview ("New PIN", "Again", "PIN"), not keyboard_type = 2 written into the
prefs and restored after.
- L1 Settings: "Lock PIN" right after the schema's "Lock screen" row, typed
twice, at least 4 characters, saved at once (a power-off right after
setting it kept no PIN). Home is told about a boot-time lock once it
exists (also fixes booting with the cover closed).
- L2: the screen PIN moves from plain text in NVS to the same hash in
NodePrefs; the old one is migrated on first boot and removed. The pad
unlocks as soon as the hash matches from 4 digits on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- L1's MessagesScreen sends through UiCore::sendDirectText / sendChannelText
(its own copy of send + file + ACK tracking gone).
- ui-core/Telemetry.h: the dashboard fields (numbers, names, formats) and
sensor readings as text, each frontend passing its Style; L2 Home, L1's
clock page, lock screen, sensors page and GPS altitude use it.
- L2: the side button held in the first 8 s is the CLI rescue, as on L1.
- L1's repeater radio goes through rptctl.
- L1 Settings are built from SettingsSchema: short labels for small
screens, SCHEMA_* placeholders expand to the schema's sections, one
generic row (switch ON/OFF, bars for brightness / volume). 20 hand-written
rows and their tables gone; L1 implements applyDisplayPrefs /
applySoundPrefs. settings::text / step / indexOf shared with L2.
- Schema: auto-off gains 5 s; the advert sound scope reads "Direct".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dead code gone: anim::fadeHide, radioctl::presetCount, the L2 board's
setSdPower / setGrovePower / expanderOK (awRead, gnssReset now private).
- buttonBar() / barButton() replace 7 copies of the popup button row.
- confirmBody(): one red-button confirm for reboot / power off, reset
counters, admin commands, preset and scope delete.
- tapConfirmed(): the 11 "tap again" buttons share one mechanism, 3 s
everywhere, the label restored when left alone.
- noteLabel() for ~30 wrapped notes.
- localTm() in NodePrefs.h and one MONTHS table: lvgl, MsgExpand, the L1
clock and lock screen.
- The sim keeps UI settings in an in-memory nvs::, so one set of load /
save helpers serves both.
- Asleep, the loop wakes every 50 ms (the button / touch poll), not 20.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>