Skip to content

Spike: e-ink (black & white) mode for the client #147

Description

@jonocodes

Summary

Explore a trimmed-down e-ink mode for the client so deckd can run as a long-lived control surface on an e-ink Android device (Boox / Bigme class), reviving the original "dedicated low-power display" idea while keeping the web-app renderer.

Assume black-and-white (2-colour) e-ink for this work. Colour e-ink (Kaleido 3) is muted and lower-res on the colour layer; designing mono-first is simpler and degrades gracefully to colour panels. Parked for now — filing to dive in later.

Why deckd is a good fit

The core deck is a static button grid that only repaints on discrete events (focus switch, button press) — exactly what e-ink holds well at ~zero power. The work is not "make the app work on e-ink"; it's isolating the few live/animated elements that break e-ink and giving them an e-ink behaviour. Most of that is an extension of the existing prefers-reduced-motion / .a11y-high-contrast paths.

Known live/animated offenders (from a code pass)

  • Meters/stats — 1 Hz updates, each firing a 250 ms width+colour transition + HSL gradient recompute (client/src/MeterCell.tsx:122, client/src/meter-store.ts). Worst offender: constant partial refresh → ghosting. Mono panel also has no equivalent for the HSL fill gradient (needs fill-% / hatching / number).
  • Pulsing indicators — connection dot (1.1 s) and media playback dot (1.6 s) animate forever with no semantic need.
  • Press feedback — 80 ms scale-down + brightness filter.
  • Jogstrip momentum scroll — continuous motion; replace with page-step on e-ink.

Grid layout itself (client/src/ButtonGrid.tsx, CSS Grid, fixed-pixel cells, no transforms) is already e-ink-friendly.

The one hard constraint

A browser tab cannot control the e-ink refresh mode. Refresh-mode control (full vs A2/fast vs Super-Refresh) is native-only via the Onyx SDK, applied to Android Views incl. WebView (OnyxAndroidDemo). This forks the effort:

  • Path A — browser + "e-ink mode" (cheap, do first): a client mode/toggle that kills animation, freezes/strips meter transitions, renders mono-first, disables momentum. Works on any e-ink Android browser, zero native code, reuses PWA + ?demo= / gallery infra. Also useful as a generic "static/low-power" theme on normal phones. At the mercy of the device's global refresh setting.
  • Path B — thin native WebView wrapper (unlocks the appliance vision): small Kotlin shell loading the existing client, using the Onyx SDK for per-view refresh mode, kiosk/fullscreen, wake/doze management, and a full refresh on layout switch to clear ghosting. Keeps 100% of the React renderer. Only path that gives true always-on.

Power / connection reality

An e-ink Android tablet running Chrome + persistent WebSocket + Wi-Fi is not a low-power appliance the way dedicated e-ink firmware is (panel is ~0W at rest; SoC/radio/wake-lock aren't). Doze/Wi-Fi sleep will repeatedly drop the socket. Decide between:

  • Presence model — static image at rest, reconnect on tap (battery-friendly), vs
  • Always-on wake lock — usable instantly but burns battery (conflicts with the low-power goal; Path B gets native control here).

Open questions to resolve when we pick this up

  • Do the live widgets (meters, now-playing, album art) even belong in e-ink mode? Candidate framing: e-ink profile = the deck, minus the live dashboard widgets.
  • Path A only, or commit to Path B for a real appliance?
  • Mono representation for meters (fill-%, hatching, numeric).
  • Presence model vs always-on wake lock.
  • Touch-feedback story: the action fires on the desktop instantly, but the on-screen confirmation lags on e-ink — rely on the desktop as feedback? haptics?
  • Test device: a Kaleido 3 / Android 14 device with a real Chromium browser (Bigme B6, Bigme inkNoteX Color, Boox Note Air 4C). Confirm it holds a background WebSocket + runs the PWA before committing.

Scope for a first cut (Path A)

An e-ink mode toggle (settings + ?eink=1 dev override, matching the existing query-param pattern) that: disables all animations/pulses, freezes meter transitions (or hides live widgets), forces a high-contrast mono palette (borders/icons/labels carry identity, not fills), and disables jogstrip momentum. Eyeball-able in the existing gallery/?demo= infra with no hardware.


Captured from a research discussion; not yet started.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestlow-priorityAcknowledged but parked; not in active queuespikeDesign-doc spike work (input injection, focus watcher, etc.)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions