render() ran after load()'s ordered failure path and after shownLoad
had moved on. A snapshot the board could not show (a session whose
detectedHarnesses is stored as an object) threw past the ordering, and
an older valid snapshot could no longer take the board. render() now
runs inside that path, and shownLoad moves only once it returns.
Run posted state.query, the query typed last. When an older load
succeeds after a newer one failed, the board shows the older query's
results and recall command while Run acted on the newer query. Each
load now keeps its query, render() records it for the board, and Run
posts that.
The test harness assigns new arrays instead of pushing onto
page.pending, and records requests the same way.
Two cases the finished-load rule got wrong. When the first snapshot was
still loading and a live refresh failed, the first snapshot arrived
late and was dropped, leaving the pane empty. And manual loads, the
Refresh button and a query, reported their failures outside the
ordering, so a late manual failure could cover newer data and an older
success could clear a newer manual failure.
The board now shows the newest data any load brought, and the error
box the outcome of the newest load that has finished. load() reports
its own fetch failures, as a live-refresh failure when the interval
started it and as a plain error otherwise, so every load is ordered the
same way. Data that arrives after a newer failure still shows, and the
failure stays, dated by that data.
Two held refreshes complete in reverse order with different data. The
older response arrives last and must not replace what the newer one
put on the board. It did before the previous commit.
A newer refresh that failed was shown, and then an older one that
succeeded cleared the message, so the board looked live although no
load that started after the failed one had succeeded.
The board now follows the newest load that has finished, with its data
or with its failure. A load that finishes after a newer one leaves the
board alone: its data is not rendered and its failure is not reported.
This also keeps a late response from replacing newer data.
With three refreshes in flight, the second succeeds and the first
fails while the third still runs. The board shows data from a load that
started after the failed one, so the failure stays off it. Comparing
against every load started so far would report it.
The refresh-failure test looked for the current Gregorian year in a time
the page formats with toLocaleString. A locale with another calendar
shows another year: under th_TH the page prints 2569 and the test
failed. The page now runs on a fixed instant, and the test compares
against toLocaleString of that instant.
A failed refresh was dropped whenever any load finished while it ran.
When two interval refreshes overlapped and the older one succeeded, the
newer one's failure was dropped too, so the board looked live although
its latest refresh had failed.
Loads are now numbered as they start. A failure is dropped only when
the board shows data from a load that started after it.
A live refresh that fails after a newer load has already replaced the
board no longer describes what is shown, so it is only reported while
the data it was meant to replace is still on screen. The time of that
data now includes its date, for a pane left open overnight, and it is
kept in its own binding instead of on the shared state object.
The 15-second refresh caught every error with `load().catch(() => {})`,
so a control pane that had lost its server kept showing the last
snapshot as if it were current. A failed refresh now shows the error box
with "Live refresh failed. The data below is from <time>." and the
error, and the next successful refresh clears it, as load() already
clears the box.
The new test runs the served page script in a vm with a stand-in for the
browser APIs it uses, fails a refresh, and checks both transitions.
Fixes#3176