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.

Is it worth assessing toolkits like these? https://bandarlabs.github.io/Cobalt/

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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