Technical docs

How the app is built, from the pure timer logic to the device it runs on.

Hardware & controls

The Car Thing is an 800×480 landscape touchscreen with a rotary encoder (the wheel), a press-in knob, four buttons along the top, a mic button and a back button. Chromium on the device delivers the encoder as horizontal wheel events; one detent is a delta of 120.

InputTimerPicker / History
Turn wheelIdle: set the duration. Running: add / remove whole minutesMove through designs / scroll days
Press knobStart, pause or resumeApply design / back to timer
Hold knob (700 ms)Stop the active phase, or skip a waiting break—
Buttons 1 / 2 / 3Presets 15 / 25 / 45 min—
Button 4Toggle historyToggle history
Mic buttonOpen the design pickerClose
Back button—Close

The Pomodoro cycle

A set is four focus sessions. After each focus session comes a short break; after the fourth, a long break, then the set starts over.

Timer state machine

All timer logic is a pure module (timer.ts) — no React, no device calls. State has a mode (idle / running / paused) and a phase (focus / short / long). Times are stored as epoch timestamps, so a throttled or backgrounded page catches up correctly instead of drifting.

// one detent of the wheel; press / hold handlers
export function press(s, now) {
  switch (s.mode) {
    case 'idle':    return { ...s, mode: 'running', endsAt: now + phaseMs(s), startedAt: now };
    case 'running': return { ...s, mode: 'paused', remaining: s.endsAt - now, pausedAt: now };
    case 'paused':  return { ...s, mode: 'running', endsAt: now + s.remaining,
                             pausedTotal: s.pausedTotal + (now - s.pausedAt) };
  }
}

When a running phase reaches its end, complete() chains focus into its break from the exact end instant and logs the finished session; a finished break returns to idle.

Clock trust

The Car Thing has no battery-backed real-time clock. A systemd guard saves the time every few minutes and restores it at boot, so after a power-off the clock resumes behind by however long the device was off — and nothing on the device can tell how long that was. The app judges whether the clock is believable, strongest evidence first:

  1. the paired phone's wall clock, when the gateway supplies one (authoritative);
  2. a logged session that ends in the “future” — the clock went backwards;
  3. the boot id — a clock confirmed on a boot stays trusted until the next boot.

Differences under two minutes are treated as ordinary drift. The status resolves to ok, unverified (rebooted since last confirmed), or behind by a known amount, which designs surface as an unobtrusive warning rather than silently logging against a wrong time.

Architecture

Timer logic and presentation are kept apart, so a design never touches the clock.

Storage & persistence

State is persisted through the bridgething daemon's per-app store, with localStorage as a fallback for the moment before the socket settles and for desktop development where there is no daemon. Timer writes are debounced (~400 ms); history is rewritten only when it changes.

KeyHolds
pomodoro.v1timer state
pomodoro.history.v1session history, compact array form
pomodoro.theme.v1selected design
pomodoro.clock-trust.v1boot id the clock was last confirmed on

History is stored in a tight wire form — [startSec, endSec, focusedSec, done] per session — and capped at 3000 entries (roughly a year at eight sessions a day).

bridgething SDK

The app talks to the on-device daemon through @bridgething/client over a local WebSocket (ws://<host>/). Surfaces it uses:

SurfaceUsed for
storeper-app key/value persistence (the four keys above)
timethe phone's wall clock, to check the device clock
systemboot diagnostics (the boot id) for clock trust
capabilitiesdiscovering the device's notification earcons
audioplaying an earcon when a session completes

A public/manifest.json declares the app id, name, icon and permissions.

Build & deploy

npm install
npm run dev            # vite, size the window to 800x480
npm run build          # tsc --noEmit, then vite build -> dist/
DEVICE=<adb-serial> npm run deploy

Deploy pushes the built dist/ into /var/bridgething/webapps/<app-id>/ over adb and restarts the bridgething service. On desktop, keyboard stands in for the hardware: Enter/Space is the knob, 1–4 the top buttons, M the design picker, Escape back, and a trackpad's horizontal scroll is the wheel.

← Back to overview