Skip to content

RFC-003 — Lambert worker: message protocol, progress reporting, cancellation

Status · Decided TA anchor · §components/lambert-worker · §contracts/lambert-worker-message Closed by · ADR-022 (2026-04-28) Why this is an RFC · ADR-008 locks the Lambert solver in a Web Worker. The message protocol, progress reporting strategy, and cancellation mechanism have genuine alternatives with different complexity and UX implications.

The question

The Lambert solver computes 11,200 cells (~200ms fast, 1-3s slow hardware). Open questions: does the user see progress during computation? If the user changes the date range mid-computation, does the old computation get cancelled cleanly? What's the exact message shape?

Use cases

  • User opens plan screen: porkchop plot computes for default date range
  • User changes departure slider mid-computation: new computation should supersede old one
  • Slow device: user should see feedback, not a frozen UI

Goals

  • Main thread stays responsive (ADR-008 guarantees this)
  • Stale results never overwrite fresh ones
  • Progress feedback on slow devices
  • Protocol simple enough that the worker code is readable

Constraints

Web Workers communicate via postMessage. No SharedArrayBuffer required. Worker must be a separate file importable by Vite.

Proposed approach

Protocol:

Main → Worker:

json
{ "id": 3, "depRange": [depStart, depEnd], "arrRange": [arrStart, arrEnd], "steps": [112, 100] }

Worker → Main (progress, every 10 rows):

json
{ "id": 3, "progress": 0.45 }

Worker → Main (result):

json
{ "id": 3, "grid": [[dv, ...], ...], "depDays": [...], "arrDays": [...] }

Cancellation: monotonically increasing id. The worker checks current id before posting any message — if it has changed, discard. Simpler than terminate()/new Worker().

Progress: post every 10 rows. The plan screen shows a progress bar that fills over the computation time.

Alternatives considered

No cancellation — stale results overwrite fresh ones if the user changes inputs quickly. Real bug. Rejected.

terminate() + new Worker() — clean slate; ~10ms worker startup cost. More complex lifecycle. The id-based approach is equivalent and simpler.

Streaming (post each row as computed) — allows partial porkchop rendering; more complex render code; not needed at target computation time.

Trade-offs

The id field adds minor complexity to every message. The alternative (stale results) is a real UX bug.

Open questions

  1. Should steps be configurable from the UI, or fixed at [112, 100]?
  2. Progress interval: every 10 rows, every 1%, or time-based (every 100ms)?
  3. Should the worker be kept alive between plan screen visits, or terminated on navigation?

Closing evidence

Worker running in production with porkchop plot. Input changes cancel previous computation. Available at Slice 3 gate.

How this closes

One ADR: Lambert worker message protocol and cancellation strategy.

Orrery — architecture documentation · MIT · No tracking