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