TimePort is the seam every pacing mechanism in the mobile app is built on: one monotonic clock, one wall clock, and a per-run scheduler for timers and frames.
Quick Start
1
Pick the real adapter at boot
createWebTime() builds a TimePort over the browser’s clock and frame loop. Pass it as time: to createApp.2
Read a monotonic instant
Call
time.nowMs() for delay budgets and elapsed measurements. Call time.epochMs() only when a record needs a real timestamp.3
Poll or paint through a scheduler
Create one scheduler per run, then use
every, setTimer + clearTimer, and requestFrame. Dispose it at teardown.How It Works
The port carries two clocks and a scheduler factory, and each member has one job.Why Two Clocks
One clock measures durations, the other stamps records, and collapsing them reintroduces the bug the port was carved to prevent. When the phone’s clock is corrected mid-turn — a common event on a wake from sleep — a wall-clock elapsed measurement can jump backwards. The publish gate’sMAX_HELD_CHARS and the coalescer’s maxDelayMs are both measured against nowMs, so pacing stays sane through a correction. Records that need to be sortable across devices use epochMs.
Executable Specification
The port’s first conformance suite runs the same cases against the fake clock and the web adapter, so the two cannot drift.The fake and the real adapter run the same cases.
every’s period was found discarded in the fake in two separate rounds; a test that only ever drives the fake cannot see it a third time.Each of these cases is also driven by the contract fixture —
time_every_fires_once and time_clear_does_nothing — so a case that lost its assertion is caught by the assertion no longer failing when the adapter is deliberately broken. See Adapter Conformance.Common Patterns
The coalescer’s flush tick is a repeatingevery, stopped at dispose.
Best Practices
Read the monotonic clock for durations, the wall clock for records
Read the monotonic clock for durations, the wall clock for records
Collapsing them makes elapsed measurements jump when the OS corrects the clock.
One scheduler per run, disposed with the run
One scheduler per run, disposed with the run
A scheduler that outlives its run carries closed gates into the next answer.
every() returns an unsubscribe, and you must call it
every() returns an unsubscribe, and you must call it
A leaked interval keeps the event loop alive for the app’s lifetime.
Pair every setTimer with an eventual clearTimer on cancel
Pair every setTimer with an eventual clearTimer on cancel
A gate that stays armed after a cancel forces a paint that no longer belongs.
Test pacing against a fake clock that actually advances
Test pacing against a fake clock that actually advances
A
TimePort.every fake that never fires the tick makes every pacing test pass for the wrong reason.Related
Mobile Architecture
The coalescer and publish gate the port paces.
Shell & Adapters
The sibling port every conformance suite pins.

