Skip to content

Live session awareness and multiple session overview - #136

Open
HermannBjorgvin wants to merge 10 commits into
mainfrom
feature/live-sessions
Open

Live session awareness and multiple session overview#136
HermannBjorgvin wants to merge 10 commits into
mainfrom
feature/live-sessions

Conversation

@HermannBjorgvin

@HermannBjorgvin HermannBjorgvin commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Implements #135.

The device now shows what Claude Code is doing right now: which chats are open,
how full each context window is, todo and subagent counts — and, above all,
whether any chat is stuck waiting on you. Nothing is operated by hand: the
display picks its own layout and ordering from Claude's state, and a chat that
needs you is always in the top slot with a pulsing indicator.

The feature is opt-in twice over: gated per board by a capability flag
(enabled on the 480×480 AMOLED-2.16 to start), and off by default on the
host until the hook integration is configured. Without either, the device
behaves exactly as it does on main.

On-device views (firmware)

No new screen. The usage screen's resolver (update_view_state() in
firmware/src/ui.cpp) selects among five sub-views: pairing hint, no-data,
quota panels (RESTING), ONE CHAT, and SEVERAL CHATS, per the §2.1 flowchart in
the issue. A waiting chat pins the view; after the last chat closes the view
lingers 10 minutes (CHAT_LINGER_MS) before returning to RESTING.

  • Chat cards (firmware/src/ui.cpp): name (ellipsized firmware-side
    against a worst-case sonnet-5.2 model pill), context bar, and a state line
    indicator plus state text on the left, a right-aligned cluster of
    todo badge (terra-cotta), subagent badge (purple #9e87be, hue-matched to
    the palette), and time-in-state.
  • ONE CHAT: the 5h quota panel as its own card, then the chat card with
    95% of context used, the absolute token count (190K / 1.2M), and a
    model pill. The long-form wording deliberately teaches what the terse
    multi-chat bars mean.
  • SEVERAL CHATS: a one-line 5h/7d quota strip, then up to 3½ cards at
    88px pitch — the clipped fourth card is the "more below" affordance. The
    host pre-sorts (waiting → working → idle); the firmware renders received
    order and never re-sorts.
  • Motion budget: waiting cards pulse indicator + state text on one shared
    700ms phase; rank changes slide cards 260ms ease-out with stable
    sid-keyed identity; content refreshes move nothing. Idle cards dim to 60%
    opacity via plain opa (not opa_layered, which would allocate composite
    buffers).
  • Wake on activity: every session payload resets the idle-fade timer
    (idle_note_activity() in firmware/src/main.cpp), so the panel is awake
    while Claude works and dark when nobody is.
  • Board gating: BOARD_HAS_SESSION_VIEWS compile-time flag + runtime
    board_caps().has_session_views (documented in
    docs/porting/capability-flags.md). Flag-off boards compile the card pool
    out entirely — verified: the C6 build contains zero session symbols.
    Flag-on envs run LV_MEM_SIZE=128K (the ~90-widget card pool exhausts
    LVGL's 64K default pool).
  • Icons: Lucide list-todo / users-round at 24/16px, RGB565A8 with a
    real alpha plane, recolored at render time so the 60% dim rides the normal
    draw path (firmware/src/icons.h, sources in assets/).

BLE wire

Session rows arrive on a new SS characteristic
4c41555a-4465-7669-6365-000000000005 (firmware/src/ble.cpp), behind the
same single-owner write guard as the existing RX characteristic, with
setMTU(517) and a 1KB receive buffer. Payload:

{"ss":[
  // [sid, label, state, ctx%, elapsed_s, model, tool, ntools, nagents, tdone, ttotal, tok]
  ["a3","billing-svc-3f",6,95,62,1,0,0,0,3,7,190]
]}

State codes 0–10 are append-only wire constants (firmware/src/data.h).
ctx −1 hides the bar rather than drawing it empty; tok is context tokens
in 1K units (−1 or absent → the card falls back to showing the percentage).
Malformed payloads are dropped and the last good list stays on screen
(firmware/src/main.cpp).

Host: session sidecar (daemon)

daemon/clawdmeter_sessions.py — a stdlib-only Python hook listener, usable
as a library or standalone:

  • Claude Code hooks POST to 127.0.0.1:<hook_port>; the listener is a
    read-only observer (answers 204, rejects non-loopback peers, can never
    block or approve a tool call).
  • State machine over the issue's §4.1 event table, plus PostToolUseFailure
    so a failed tool can't leave the concurrent-tool count stuck. Tool state
    clears on Stop/UserPromptSubmit, not PostToolUse, so the state line
    doesn't flicker between tools.
  • Liveness from the session roster (<config-dir>/sessions/<pid>.json,
    checked against pid + process start time so pid reuse can't fake a live
    session), with a 30s grace and a 6h staleness sweep — never from activity
    timeouts, because a chat blocked on a permission prompt is silent.
  • Context from the transcript's newest non-sidechain assistant record;
    window heuristic: 200k default, [1m] → 1M, snap-up on overflow,
    context_window_k config pins it. tok comes from the same read as the
    percentage so the two can never disagree.
  • Sort (bucket, -last_event_at); MTU fitting middle-elides labels to an
    8-char floor (trailing discriminators survive) before dropping rows from
    the tail, so a waiting chat is never the one dropped.
  • Writes the fitted payload atomically to ~/.clawdmeter/sessions.json; the
    bash daemon (daemon/claude-usage-daemon.sh) ships it to the SS
    characteristic on its existing 5s tick with a byte-exact UTF-8 GATT write.
    Fully inert when the sidecar isn't running or the firmware lacks the
    characteristic.
  • daemon/clawdmeter-sessions.service (systemd user unit),
    hook_port / context_window_k / sessions_budget_bytes in
    daemon/config.example, an install.sh prompt that merges the hook block
    into ~/.claude/settings.json via --install-hooks, and setup docs +
    wire-format reference in daemon/SESSIONS.md.

Scope

  • No scrolling. §2.5 marks it optional and it hard-depends on the touch
    rotation fix (Fix touch not tracking the auto-rotating display #120); ordering already keeps anything urgent above the fold.
  • Hooks-only. No transcript-fallback mode (§11.2); without hook_port
    the device is exactly today's device.
  • Boards: enabled on waveshare_amoled_216. Other boards follow the §7
    checklist (flag flip + hardware look).

Verification

  • All views screenshot-verified against the firmware running in a desktop
    SDL2 build (attached), including pixel-level alignment checks and
    pulse-phase captures. Not yet verified on 2.16 hardware — needs the §7
    pass (three chats, permission block, reorder smoothness) on a real panel
    before merge.
  • Daemon: 50 unit tests (pytest daemon/tests/test_sessions.py) covering the
    state machine, sort, eliding, MTU ladder, and window heuristic, plus a
    loopback smoke test driving the listener with curl'd hook sequences.

Screenshots

File Shows
image RESTING — today's quota view, unchanged
image ONE CHAT — 5h panel + chat card, badges
image Waiting: accent + pulse, status line hidden
image Sorted list, waiting chat pinned to top, clipped 4th card
image Working chats + 60%-dimmed idle card
image The 260ms reorder slide mid-animation
image Name ellipsis against the model pill
image Million-scale token formatting

🤖 Generated with Claude Code

HermannBjorgvin and others added 4 commits August 1, 2026 01:54
clawdmeter_sessions.py sidecar (stdlib-only) observes Claude Code hook
events on loopback, tracks per-session state/context/todos/subagents,
sorts attention-first, and writes an MTU-fitted wire payload to
~/.clawdmeter/sessions.json. The bash daemon ships it to the SS GATT
characteristic on its 5s tick (byte-exact UTF-8 write), inert when the
sidecar is off. Includes systemd unit, config keys, install.sh hook
prompt, SESSIONS.md, and 46 unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New BOARD_HAS_SESSION_VIEWS capability (on: amoled_216, sim) grows the
usage-screen resolver to five sub-views: ONE-CHAT and SEVERAL-CHATS
cards with sid-keyed identity, 260ms reorder slide, shared-phase
attention pulse, and idle dimming. Session rows arrive on a new SS
characteristic (…0005) parsed in main.cpp, waking the panel on
activity. Lucide list-todo/users-round icons (RGB565A8), sim scenario
playback for ss payloads, LV_MEM_SIZE 128K on flag-on envs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rows gain an appended tok field (index 11): context tokens in 1K
units, -1 when unknown, read in the same transcript pass as ctx% so
the pair can never disagree. Wire-format table added to SESSIONS.md.
Tests grow to 50.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
)

Cards show absolute token counts (190K / 1.2M) with the bar carrying
the percentage; todo badge in terra-cotta, subagent badge in palette-
matched purple (THEME_PURPLE), both recolored at render time. State
line: dot flush with the bar edge, text vertically centered, badges +
timer right-aligned; waiting text pulses with the dot. ONE-CHAT is a
5h quota panel plus a chat card ("95% of context used", token twin,
pill-styled model tag), names ellipsized firmware-side against a
worst-case model pill. Sim scenario uses realistic session names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Focus-card names measure the model pill (and list-card names their
token label) on every content update instead of reserving a static
worst-case width, so ellipsized names run to within 12px of the pill
and a hidden neighbor yields the full row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds opt-in “live session awareness” end-to-end: the host can observe Claude Code sessions via hooks and stream a compact session roster over BLE, and supported firmware builds render those sessions as auto-selected sub-views inside the existing usage screen.

Changes:

  • Firmware: adds session-row data model + BLE “SS” characteristic and UI sub-views (one-chat focus + multi-chat cards) gated by BOARD_HAS_SESSION_VIEWS and board_caps().has_session_views.
  • Host (Linux): adds a stdlib-only Python sidecar to ingest Claude Code hook events, maintain/sort session state, fit to an MTU budget, and hand off the payload to the existing bash daemon for BLE writes.
  • Docs/config: adds capability-flag documentation, host setup docs, config knobs, and a systemd user unit for the sidecar.

Reviewed changes

Copilot reviewed 32 out of 38 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
install.sh Installer now wires up optional session awareness (config prompt, hook installation, sidecar unit install/enable).
firmware/src/ui.h Exposes ui_update_sessions() API for session-view updates.
firmware/src/ui.cpp Implements session card views, resolver integration, animations, and quota mini-widgets (gated by capability).
firmware/src/theme.h Adds THEME_PURPLE for the subagent badge color.
firmware/src/main.cpp Adds session payload parsing and feeds session updates into UI when session data arrives.
firmware/src/icons.h Adds new RGB565A8 badge icons for session cards (todo/subagents).
firmware/src/hal/board_caps.h Adds has_session_views runtime capability bit.
firmware/src/data.h Adds session wire constants, enums, and SessionRow/SessionList structs; adds compile-time gate default.
firmware/src/boards/waveshare_lcd_154/caps.cpp Plumbs has_session_views into caps for this board.
firmware/src/boards/waveshare_lcd_154/board.h Sets BOARD_HAS_SESSION_VIEWS to 0 for the 240×240 LCD board.
firmware/src/boards/waveshare_amoled_216/caps.cpp Enables runtime capability mapping for session views on 2.16 S3.
firmware/src/boards/waveshare_amoled_216/board.h Sets BOARD_HAS_SESSION_VIEWS to 1 for the 480×480 AMOLED-2.16 S3.
firmware/src/boards/waveshare_amoled_216_c6/caps.cpp Plumbs has_session_views into caps for this board.
firmware/src/boards/waveshare_amoled_216_c6/board.h Keeps session views off for C6 variant pending hardware smoothness checks.
firmware/src/boards/waveshare_amoled_206/caps.cpp Plumbs has_session_views into caps for this board.
firmware/src/boards/waveshare_amoled_206/board.h Keeps session views off pending portrait-layout work.
firmware/src/boards/waveshare_amoled_18/caps.cpp Plumbs has_session_views into caps for this board.
firmware/src/boards/waveshare_amoled_18/board.h Keeps session views off pending portrait-layout work.
firmware/src/boards/waveshare_amoled_18_c6/caps.cpp Plumbs has_session_views into caps for this board.
firmware/src/boards/waveshare_amoled_18_c6/board.h Keeps session views off (no PSRAM + portrait layout).
firmware/src/boards/template/caps.cpp Updates template caps to include has_session_views.
firmware/src/boards/template/board.h Adds template flag for session views (default off).
firmware/src/ble.h Adds session characteristic accessors (ble_has_session_data, ble_get_session_data).
firmware/src/ble.cpp Adds SS characteristic, shared write-guard, larger session buffer, and MTU request.
firmware/platformio.ini Enables session views and increases LVGL heap for the target env.
docs/porting/capability-flags.md Documents BOARD_HAS_SESSION_VIEWS and its special build-flag requirement.
daemon/tests/test_sessions.py Adds unit tests covering sidecar state machine, sorting, fitting, and heuristics.
daemon/SESSIONS.md Adds host setup guide, wire format reference, and configuration docs for session awareness.
daemon/config.example Adds config keys and commentary for session awareness.
daemon/clawdmeter-sessions.service Adds systemd user unit to run the session-awareness sidecar.
daemon/clawdmeter_sessions.py Implements the hook listener sidecar (HTTP server, state machine, fitting, atomic handoff).
daemon/claude-usage-daemon.sh Adds SS characteristic writes, UTF-8-safe byte writing, and change-detection for sessions payload.
assets/icon_users-round.svg Adds Lucide source icon for subagent badge.
assets/icon_list-todo.svg Adds Lucide source icon for todo badge.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread firmware/src/ui.cpp Outdated
Comment on lines +637 to +640
strcat(buf, "...");
lv_text_get_size(&sz, buf, font, 0, 0, LV_COORD_MAX, LV_TEXT_FLAG_NONE);
if (sz.x <= max_w) break;
len--;
Comment on lines +741 to +757
def _read_body(self):
try:
length = int(self.headers.get("Content-Length") or 0)
except ValueError:
length = 0
if length <= 0:
return b""
body = b""
remaining = length
while remaining > 0:
chunk = self.rfile.read(min(remaining, 65536))
if not chunk:
break
remaining -= len(chunk)
if len(body) < MAX_BODY_BYTES:
body += chunk # oversize tails are drained but not kept
return body
The 5h/7d strip uses #129's measured layout — styrene_24 percentages
in fixed right-aligned columns, styrene_20 dim tags, a 30px vertically
centered band — and its vertical rhythm: list top at 148, 10px card
gaps (90px pitch, keeping the mid-card clip affordance). Card
internals unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@HermannBjorgvin

Copy link
Copy Markdown
Owner Author

Don't have a board on me so this is untested on real hardware. Will test it on actual hardware on Tuesday when I'm back at work.

The SEVERAL-CHATS list runs to the physical bottom edge (y=480) with a
60px transparent-to-black style gradient band over it, so the clipped
card tapers out instead of hitting a hard cut above a dead margin.
Buffer-free (bg_main_opa/bg_grad_opa), input-transparent so the splash
tap passes through, and created after the card container so reorder
slides can never draw over it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@HermannBjorgvin

Copy link
Copy Markdown
Owner Author

Still haven't looked at how the daemon and hooks work on this PR. I think it's likely I'll have some opinions on those but going to look at it again a bit later

@HermannBjorgvin

Copy link
Copy Markdown
Owner Author

Readability is a concern here, the UI might need to be reworked a bit so the user doesn't need a microscope to monitor the sessions.

HermannBjorgvin and others added 3 commits August 4, 2026 11:57
List cards and the ONE-CHAT boxes span the panel width with a 20px
inner text inset, so text keeps the old glass-edge clearance while
bars and name budgets gain the freed 32px. The 5h/7d strip keeps its
margins, which now align with the card text line. RESTING and the
classic views are untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The device's Styrene fonts cover ASCII 32..126 only, so the U+2026
ellipsis rendered as a tofu box in elided card names. "..." costs the
same three UTF-8 bytes, so the payload byte-budget math is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
List cards: name 20 -> 28, state line 16 -> 24, card 80 -> 94 -> 108 px,
bigger dot, wider state ellipsis budget. Quota strip: 5h/7d tags 20 -> 24,
percentages 24 -> 28, wider text columns — same band height, so the card
list doesn't move. Focus card: name 28 -> 48, secondary text (state,
model pill, context/token row) 20 -> 24, card 150 -> 176 px.

label_set_ellipsized() now middle-elides, keeping the trailing four
characters — the host's session discriminator ("-35" vs "-2c") survives
firmware width elision instead of being truncated away.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants