custom_msgs templates are up to 140 chars; the 80-byte msg[] buffer
silently truncated any message longer than 79 characters before sending.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tryBotReplyChannel checked bot_channel_enabled but not bot_enabled,
so disabling the bot via the main Enable toggle had no effect on
channel replies. Added bot_enabled to the guard condition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
auto_lock was defined in NodePrefs but missing from both loadPrefsInt
and savePrefs in DataStore.cpp. Added at the end of the serialization
chain (inside if(file.available())) for backwards compatibility with
existing saved files — old files default to auto_lock=0 (disabled).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Triggers on tags matching companion-experimental-* pushed from the
font-experiments branch. Appends -experimental to the version string
so all output filenames are clearly distinguishable from stable builds.
Creates a separate draft release labeled "(Experimental)".
Usage: git tag companion-experimental-v1.x.x && git push origin companion-experimental-v1.x.x
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- drawLemonChar: for codepoints outside the font range (> U+04FF) that
are printable (>= U+0020), render a 4×6 filled rectangle instead of
silently advancing the cursor with no visible output
- decodeUtf8: properly decode 4-byte UTF-8 sequences (U+10000+) to their
actual codepoints so they reach the block fallback; return U+FFFD for
genuinely invalid bytes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- All menu screens (Tools, Bot, Settings) now derive item height from
display.getLineHeight() instead of hardcoded constants, preventing
item overflow on 64px OLED displays
- ToolsScreen caps rendered items to screen height so Char Test is visible
- FullscreenMsgView word-wrap now uses display.getTextWidth() per candidate
line instead of fixed char count, correctly handles variable-width and
multi-byte UTF-8 glyphs; FS_CHARS_MAX increased 32→80
- CharTestScreen: fix v-arrow y-position; remove stale "→ blocks █" comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add CharTestScreen showing sample characters from 11 scripts
(PL, DE, CS, FR, NO, HU, HR, TR, LT, Cyrillic, Greek) as raw
UTF-8 to verify native font rendering without transliteration
- Add "Char Test" entry to ToolsScreen menu
- Regenerate LemonFont.h from lemon.bdf with full U+0020–U+04FF
range: 1074 BDF glyphs + 174 placeholders, 4111 bitmap bytes;
adds Greek (137 glyphs) and Cyrillic (256 glyphs) coverage;
also fixes space advance (DWIDTH=5, was BBX+1=1)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously each LPP sensor field called querySensors() separately.
Now a single querySensors() call fills a shared buffer used by both fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously every key press while locked reset _lock_wake_until to now+5s,
so multiple presses (or spurious button events) kept extending the window.
Now the 5s timer is set only when the display first wakes from dark.
The unlock sequence still extends the window on each Back+Enter step.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Turn display on at first Back+Enter press so hints are visible even when screen was dark
- Extend _lock_wake_until on every sequence step to prevent screen blanking mid-press
- Set _next_refresh=0 on every Enter press (not just on completion) for immediate hint redraw
- Add _lock_seq_used flag to suppress the Back CLICK that fires on release after unlock
- Block triple-Back (buzzer toggle) when screen is locked
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Long press detection on back_btn was unreliable — the release generated
a spurious CLICK that cancelled the sequence. Now the Enter click handler
checks back_btn.isPressed() directly: holding Back and clicking Enter 3×
within 3 seconds locks/unlocks the screen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reuses dashboard_fields[0] and [1] from NodePrefs — left-aligned and
right-aligned in one row under the clock, no labels to save space.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Long press Back starts lock sequence; 3 Enter presses within 3s toggle lock
- Locked screen wakes for 5 seconds on any key, showing clock and unlock hint
- Incoming messages do not wake the display while locked
- Auto Lock setting in Display section: locks automatically when screen turns off
- Lock state persisted in UITask; NodePrefs gains auto_lock flag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Static screens (SettingsScreen, BotScreen, FullscreenMsgView, message
list, contact picker) were re-rendering every 300ms with no visible
benefit. New messages still trigger immediate refresh via newMsg() →
_next_refresh=100, so responsiveness is unchanged. Reduces unnecessary
SPI transfers and CPU wakeups ~6x on idle static screens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Text lines fill the full display width, causing the last chars to bleed
into the scroll arrow area. Draw a DARK fillRect behind each arrow before
rendering it, so the arrow appears on a clean black background without
truncating the text width.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single shared _bot_last_reply_ms caused a DM reply to block the
channel bot for 10s and vice versa. Split into _bot_last_dm_reply_ms
and _bot_last_ch_reply_ms so each cooldown is independent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- with_sender[160] could hold only ~160 chars but node_name(32) +
expanded(200) needs up to 234; snprintf would silently truncate
the UI display entry. Increased to 240.
- FS_LINE_H was 9 but Adafruit 5x7 font line height is 8, wasting
1px per line and showing 5 lines instead of 6.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Channel messages have the format "sender_name: body". The trigger was
being searched across the whole string, so a nick containing the trigger
word would fire a reply even with an unrelated message. Now the match
skips past the ": " separator and only checks the message body.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Loading... screen was always shown at full brightness because prefs
are not yet loaded at that point. Apply setBrightness() in the first
common code path after the_mesh.begin() loads prefs, so at least the
tail of the loading screen and the entire splash screen respect the
saved brightness level.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Size 2 rendered text wider than display width. Switched to size 1
and adjusted y-positions to keep both lines visible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Raw UTF-8 message body was passed directly to display.print(), rendering
Polish and other accented chars as garbage on the Adafruit font.
Transliterating before wrapLines also fixes byte-count wrapping for
multi-byte UTF-8 characters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Names printed via drawTextEllipsized (contacts, senders) bypassed
translateUTF8ToBlocks, causing raw UTF-8 bytes to render as garbage
on the Adafruit font. Now transliteration happens inside the method.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the block-character fallback with proper transliteration for
Polish (ą→a, ć→c, ę→e, ł→l, ń→n, ó→o, ś→s, ź→z, ż→z), German (ä ö ü ß),
and common French/Spanish/Portuguese accents. Unknown codepoints fall back
to '?' instead of █.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously each LPP sensor field called querySensors() separately.
Now a single querySensors() call fills a shared buffer used by both fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously every key press while locked reset _lock_wake_until to now+5s,
so multiple presses (or spurious button events) kept extending the window.
Now the 5s timer is set only when the display first wakes from dark.
The unlock sequence still extends the window on each Back+Enter step.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds ð/Ð (eth), þ/Þ (thorn) for Icelandic, and ţ/Ţ (t with cedilla)
for legacy-encoded Romanian text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds háček and other Czech/Slovak characters (č š ž ř ě ů ď ť ň ľ ĺ ŕ)
to the UTF-8 transliteration table so they display as ASCII letters
instead of █ blocks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Turn display on at first Back+Enter press so hints are visible even when screen was dark
- Extend _lock_wake_until on every sequence step to prevent screen blanking mid-press
- Set _next_refresh=0 on every Enter press (not just on completion) for immediate hint redraw
- Add _lock_seq_used flag to suppress the Back CLICK that fires on release after unlock
- Block triple-Back (buzzer toggle) when screen is locked
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Long press detection on back_btn was unreliable — the release generated
a spurious CLICK that cancelled the sequence. Now the Enter click handler
checks back_btn.isPressed() directly: holding Back and clicking Enter 3×
within 3 seconds locks/unlocks the screen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reuses dashboard_fields[0] and [1] from NodePrefs — left-aligned and
right-aligned in one row under the clock, no labels to save space.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Long press Back starts lock sequence; 3 Enter presses within 3s toggle lock
- Locked screen wakes for 5 seconds on any key, showing clock and unlock hint
- Incoming messages do not wake the display while locked
- Auto Lock setting in Display section: locks automatically when screen turns off
- Lock state persisted in UITask; NodePrefs gains auto_lock flag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Static screens (SettingsScreen, BotScreen, FullscreenMsgView, message
list, contact picker) were re-rendering every 300ms with no visible
benefit. New messages still trigger immediate refresh via newMsg() →
_next_refresh=100, so responsiveness is unchanged. Reduces unnecessary
SPI transfers and CPU wakeups ~6x on idle static screens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Text lines fill the full display width, causing the last chars to bleed
into the scroll arrow area. Draw a DARK fillRect behind each arrow before
rendering it, so the arrow appears on a clean black background without
truncating the text width.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single shared _bot_last_reply_ms caused a DM reply to block the
channel bot for 10s and vice versa. Split into _bot_last_dm_reply_ms
and _bot_last_ch_reply_ms so each cooldown is independent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- with_sender[160] could hold only ~160 chars but node_name(32) +
expanded(200) needs up to 234; snprintf would silently truncate
the UI display entry. Increased to 240.
- FS_LINE_H was 9 but Adafruit 5x7 font line height is 8, wasting
1px per line and showing 5 lines instead of 6.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Channel messages have the format "sender_name: body". The trigger was
being searched across the whole string, so a nick containing the trigger
word would fire a reply even with an unrelated message. Now the match
skips past the ": " separator and only checks the message body.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>