M4 — the product terminal and the container delivery

Provenance: the frames below are the user-verification layer's own PNGs, and the raw output is the run's own artifact — nothing here is retyped. Source: mihail812-org/polymarket_wiki, milestones/M4-report.md and milestones/M4-user-verification/.

Milestone: M4 — the product UI and the container delivery (Kind: feature)
Change: m4-ui-and-container-delivery
Branch: feat/m4-ui-and-container-delivery
Layer: SBHOST — the real binary serving the Kotlin/JS + React terminal over a live ClickHouse store, driven by a real headless chromium
Verdict: the terminal is delivered and its user stories are captured and checked; the container image's build/run is not run on this host — the podman-kind runner carries no engine client (preinstall#114).

What was done, in short

The three defects the acceptance caught (and the offline tests did not)

The terminal's own jsNodeTest suite was green the whole time; it drives the components with a fake read API. Only driving the real bundle in a real browser against a real store exposed these:

  1. The read-API GET never reached the browser. Bridge.kt declared httpGet as @JsModule("./http.js") @JsNonModule; the Kotlin/JS UMD rule then binds the module namespace object, and the bundle called that object as a function — TypeError: t is not a function, and the terminal sat in store: unreachable while /api/health on the same origin answered {"reachable":true}. Fixed by inlining the same-origin GET and leaving no relative JS-module seam but the chart's.
  2. /api/event/<slug> answered nothing. The price series was read with a sentinel Long.MAX_VALUE/2 window; the upper bound lands in the year 292278994, ClickHouse rejects the DateTime64 (HTTP 400), and the throw escaped the handler as an empty reply. Fixed by reading the series over the store's real recorded span, and by picking a token the store actually recorded rows for.
  3. The chart never drew, then drew Invalid Date. renderChart was declared but called from nowhere, and lightweight-charts was absent from the bundle (an ES export behind the same UMD binding). Once wired, the series handed it a boxed Kotlin/JS Long and a locale of en-US@posix (Chromium on a LANG=C host), which threw inside the time axis. Fixed by a CommonJS chart bridge with a dynamic import() (the package is ESM-only), integer-seconds series points, and an explicit localization.locale.

User story 1 — "I open the terminal and see what has been collected"

An owner opens the terminal and wants to know, at a glance, which events the collector has recorded and how much of each: the events with their market and token counts, the number of order-book and trade rows, the recorded time span, and a filter that narrows the list to a slug.

the terminal's events panel — the collected events, their markets, tokens, both counts and the recorded span (header: store up, events 4/4)
the terminal's events panel — the collected events, their markets, tokens, both counts and the recorded span (header: store up, events 4/4)
the counts table — val-nrg-loud 10 order-book / 5 trade rows; the three others 2 / 1
the counts table — val-nrg-loud 10 order-book / 5 trade rows; the three others 2 / 1
the recorded span — the min and max recv_time across both tables
the recorded span — the min and max recv_time across both tables
the filter narrows the list — the chosen event's leading token keeps it and drops the rest (events 1/4)
the filter narrows the list — the chosen event's leading token keeps it and drops the rest (events 1/4)

Against the store: list.order_book rows == store and list.trade rows == store for all four events (2=2, 1=1, 10=10, 5=5); the filter narrows 4 → 1. match.

User story 2 — "I pick an event and see its recorded price series, the book and the trades at a moment"

The owner picks an event and a moment T and wants the recorded price series, the order book that stood then and the trades that executed around it. The view names the event, the moment and the window, so the moment is recountable by hand.

the event view at T — the chart of the recorded series, the event name, the moment T (2026-10-10T08:34:25.889Z) and the window (±10s), then the two tables
the event view at T — the chart of the recorded series, the event name, the moment T (2026-10-10T08:34:25.889Z) and the window (±10s), then the two tables
the price chart — the recorded series over the recorded span, drawn with lightweight-charts; the layer samples the canvas itself (1839 series-coloured pixels, 53 distinct colours)
the price chart — the recorded series over the recorded span, drawn with lightweight-charts; the layer samples the canvas itself (1839 series-coloured pixels, 53 distinct colours)
the order book at T — best_bid 0.57, best_ask 0.58
the order book at T — best_bid 0.57, best_ask 0.58
the trades around T — the ±10 s window, in receive-time order (3 rows)
the trades around T — the ±10 s window, in receive-time order (3 rows)

Against the store: orderbook.at_T best_bid == store → 0.57=0.57; best_ask → 0.58=0.58; trades.around_T count == store → 3=3; chart series points == store trades over the span → 5=5. match.

User story 3 — "I can trust what the terminal shows me"

An owner has to be able to tell "the store did not answer" from "the store is empty". The terminal renders each as its own state; the layer captures each and asserts the page says so.

the store unreachable — an explicit state, not an empty list: 'nothing is shown rather than an empty success'
the store unreachable — an explicit state, not an empty list: 'nothing is shown rather than an empty success'
a reachable but empty store — events 0/0, span: n/a — a different fact, rendered as such
a reachable but empty store — events 0/0, span: n/a — a different fact, rendered as such

Goal → case → what was observed → result

GoalThe caseWhat the run observedResult
The terminal serves the collected events — markets, tokens, order-book and trade counts and the recorded span — and narrows the list by a filterthe terminal's events panel beside a query of the store for the same events and countsframes 1–4; list.order_book rows == store and list.trade rows == store for all four events (2=2, 1=1, 10=10, 5=5); the filter narrows 4→1OK, captured and checked
For a chosen event the terminal charts the recorded price series and shows the order book at a moment T and the trades around T, read from the storethe terminal's event view at T beside the store's own rows at Tframes 5–8; the chart's canvas carries 1839 series pixels; best_bid 0.57=0.57, best_ask 0.58=0.58, trades around T 3=3, series 5=5OK, captured and checked
The terminal reports its store honestly — a dead store is not an empty successthe terminal against a dead store and an empty storeframes 9–10, each asserted for its own stateOK, captured and checked
The product ships as one container image carrying all of its dependencies, and is built and run as an image./run-m4.sh deploy on a clean environment (podman build → podman run)not run — the podman layer carries no engine client (have podman MISSING, preinstall#114); the Containerfile's static checks pass in ./gradlew testopen
The snap is retiredno snap/snapcraft.yaml, no snap scenario, no snap path in scripts or workflowsgrep for the snap paths is empty; the REMOVED delta sits in the changeOK

The reverse runs — a green result turned red

Each check must go red on its own broken subject. All were run from the same script as the green run, each pointing it at a deliberately wrong subject (so no subject is mutated in place).

Reverse runWhat was brokenWhat went red
reverse-athe layer pointed at the readable fallback page instead of the terminal32 mismatches, user-verify-m4: FAIL — the page is not the terminal (class="terminal" absent), and its events do not carry the terminal's rows or chart
reverse-bthe reference store is a different database than the terminal reads20 mismatches, user-verify-m4: FAIL — the terminal's rows, span and book do not match the reference the layer reads
reverse-cthe terminal is served by a product whose store does not answer12 mismatches, user-verify-m4: FAIL — the terminal lists 0 events where the store has rows for 4

Layer — the run's own record

Full m4-user-verify.txt (raw file):

== M4 user verification: the product terminal beside the collection store, field for field ==
terminal      : http://127.0.0.1:8899 (the Kotlin/JS + React bundle the product serves, driven in a real browser)
store         : http://127.0.0.1:8123 / liveui
listed events : 4 (the terminal) / 4 (the store has rows for)
chosen event  : val-nrg-loud-2026-10-10
chosen token  : 54766275672533937226534147411813036045778021882156237046712863902839579722640
moment T      : 2026-10-10T08:34:25.889Z
window        : ±10s
fonts         : /tmp/m4-fontconfig/fonts.conf
browser flags : --no-sandbox --disable-gpu --disable-dev-shm-usage

-- the store's own rows (read from ClickHouse, not from the DOM) --
  cs2-dep5-vita-2026-10-10                             order_book=2 trade=1
  val-nrg-loud-2026-10-10                              order_book=10 trade=5
  ja2-jub-con-2026-10-10-more-markets                  order_book=2 trade=1
  auc-sm-vic-2026-10-10-more-markets                   order_book=2 trade=1
  recorded span   : 2026-10-10T08:33:53.313Z .. 2026-10-10T08:34:25.889Z
  book at T       : best_bid=0.57 best_ask=0.58
  trades around T : 3
  series (span)   : 5

-- the frames (each asserted: DOM before the shot, non-blank after) --
  ok    us1-01-terminal-list.png              100%  full page
  ok    us1-02-list-counts.png                100%  #events
  ok    us1-03-recorded-span.png              100%  #span
  ok    us1-04-filter-narrows.png             100%  #events
  ok    us2-01-event-detail-T.png             100%  full page
  ok    us2-02-chart.png                      100%  #chart
  ok    us2-03-orderbook-at-T.png             100%  #orderbook
  ok    us2-04-trades-around-T.png            100%  #trades
  ok    us3-01-store-unreachable.png          100%  full page
  ok    us3-02-empty-store.png                100%  full page

-- field for field: the terminal against the store --
  match  [us1] the page is the product terminal, not the readable fallback class="terminal" present: true
  match  [us1] row is a store event [auc-sm-vic-2026-10-10-more]      store has rows: order_book=2 trade=1
  match  [us1] list.order_book rows == store [auc-sm-vic-2026-10-10-more] terminal=2 store=2
  match  [us1] list.trade rows == store [auc-sm-vic-2026-10-10-more]  terminal=1 store=1
  match  [us1] row is a store event [ja2-jub-con-2026-10-10-mor]      store has rows: order_book=2 trade=1
  match  [us1] list.order_book rows == store [ja2-jub-con-2026-10-10-mor] terminal=2 store=2
  match  [us1] list.trade rows == store [ja2-jub-con-2026-10-10-mor]  terminal=1 store=1
  match  [us1] row is a store event [val-nrg-loud-2026-10-10]         store has rows: order_book=10 trade=5
  match  [us1] list.order_book rows == store [val-nrg-loud-2026-10-10] terminal=10 store=10
  match  [us1] list.trade rows == store [val-nrg-loud-2026-10-10]     terminal=5 store=5
  match  [us1] row is a store event [cs2-dep5-vita-2026-10-10]        store has rows: order_book=2 trade=1
  match  [us1] list.order_book rows == store [cs2-dep5-vita-2026-10-10] terminal=2 store=2
  match  [us1] list.trade rows == store [cs2-dep5-vita-2026-10-10]    terminal=1 store=1
  match  [us1] us1-01-terminal-list.png painted                       non-white 100%
  match  [us1] us1-02-list-counts.png painted                         non-white 100%
  match  [us1] us1-03-recorded-span.png painted                       non-white 100%
  match  [us1] the terminal lists at least one event                  rows=4
  match  [us1] filter narrows to the named event                      terminal=["val-nrg-loud-2026-10-10"] want=[val-nrg-loud-2026-10-10]
  match  [us1] us1-04-filter-narrows.png painted                      non-white 100%
  match  [us2] the chosen event row is clickable                      slug=val-nrg-loud-2026-10-10
  match  [us2] the product's event answer names a token               token=54766275672533937226534147411813036045778021882156237046712863902839579722640
  match  [us2] detail names the chosen event                          terminal=val-nrg-loud-2026-10-10 store=val-nrg-loud-2026-10-10
  match  [us2] detail names the moment T it printed (the store's span end) terminal=moment T: 2026-10-10T08:34:25.889Z · window: ±10s store=2026-10-10T08:34:25.889Z
  match  [us2] detail names the window it printed                     terminal=moment T: 2026-10-10T08:34:25.889Z · window: ±10s
  match  [us2] orderbook.at_T best_bid == store                       terminal=0.57 store=0.57
  match  [us2] orderbook.at_T best_ask == store                       terminal=0.58 store=0.58
  match  [us2] trades.around_T count == store                         terminal=3 store=3
  match  [us2] the chart drew a canvas                                canvas=614x212
  match  [us2] the chart canvas painted a series                      series-coloured pixels=1839 of 614x212
  match  [us2] the chart canvas is not a flat fill                    distinct colours=53
  match  [us2] chart series points == store trades over the span      terminal=5 store=5
  match  [us2] us2-01-event-detail-T.png painted                      non-white 100%
  match  [us2] us2-02-chart.png painted                               non-white 100%
  match  [us2] us2-03-orderbook-at-T.png painted                      non-white 100%
  match  [us2] us2-04-trades-around-T.png painted                     non-white 100%
  match  [us3] us3-01-store-unreachable.png painted                   non-white 100%
  match  [us3] an empty store lists no event                          terminal rows=0
  match  [us3] us3-02-empty-store.png painted                         non-white 100%
user-verify-m4: OK

The store's own answers

raw file:

# store side — the store's own answers (ClickHouse, direct), read by the terminal's own reads #
select1                 : 1
order_book rows (total) : 16
trade rows (total)      : 8

-- per event (the store's own slug groups) --
  auc-sm-vic-2026-10-10-more-markets                 order_book=2
  cs2-dep5-vita-2026-10-10                           order_book=2
  ja2-jub-con-2026-10-10-more-markets                order_book=2
  val-nrg-loud-2026-10-10                            order_book=10
  auc-sm-vic-2026-10-10-more-markets                 trade=1
  cs2-dep5-vita-2026-10-10                           trade=1
  ja2-jub-con-2026-10-10-more-markets                trade=1
  val-nrg-loud-2026-10-10                            trade=5

-- the chosen event's book at T (the span end) --
  best_bid=0.57 best_ask=0.58 at=1791621264661
-- trades for that token (the chart series) --
5
-- recorded span --
  order_book: 1791621233313 .. 1791621265870
  trade:      1791621233337 .. 1791621265889

The raw console log

The layer's console output (raw file):

ok   us1-01-terminal-list.png              100%  full page
ok   us1-02-list-counts.png                100%  #events
ok   us1-03-recorded-span.png              100%  #span
ok   us1-04-filter-narrows.png             100%  #events
ok   us2-01-event-detail-T.png             100%  full page
ok   us2-02-chart.png                      100%  #chart
ok   us2-03-orderbook-at-T.png             100%  #orderbook
ok   us2-04-trades-around-T.png            100%  #trades
ok   us3-01-store-unreachable.png          100%  full page
ok   us3-02-empty-store.png                100%  full page

== M4 user verification: the product terminal beside the collection store, field for field ==
terminal      : http://127.0.0.1:8899 (the Kotlin/JS + React bundle the product serves, driven in a real browser)
store         : http://127.0.0.1:8123 / liveui
listed events : 4 (the terminal) / 4 (the store has rows for)
chosen event  : val-nrg-loud-2026-10-10
chosen token  : 54766275672533937226534147411813036045778021882156237046712863902839579722640
moment T      : 2026-10-10T08:34:25.889Z
window        : ±10s
fonts         : /tmp/m4-fontconfig/fonts.conf
browser flags : --no-sandbox --disable-gpu --disable-dev-shm-usage

-- the store's own rows (read from ClickHouse, not from the DOM) --
  cs2-dep5-vita-2026-10-10                             order_book=2 trade=1
  val-nrg-loud-2026-10-10                              order_book=10 trade=5
  ja2-jub-con-2026-10-10-more-markets                  order_book=2 trade=1
  auc-sm-vic-2026-10-10-more-markets                   order_book=2 trade=1
  recorded span   : 2026-10-10T08:33:53.313Z .. 2026-10-10T08:34:25.889Z
  book at T       : best_bid=0.57 best_ask=0.58
  trades around T : 3
  series (span)   : 5

-- the frames (each asserted: DOM before the shot, non-blank after) --
  ok    us1-01-terminal-list.png              100%  full page
  ok    us1-02-list-counts.png                100%  #events
  ok    us1-03-recorded-span.png              100%  #span
  ok    us1-04-filter-narrows.png             100%  #events
  ok    us2-01-event-detail-T.png             100%  full page
  ok    us2-02-chart.png                      100%  #chart
  ok    us2-03-orderbook-at-T.png             100%  #orderbook
  ok    us2-04-trades-around-T.png            100%  #trades
  ok    us3-01-store-unreachable.png          100%  full page
  ok    us3-02-empty-store.png                100%  full page

-- field for field: the terminal against the store --
  match  [us1] the page is the product terminal, not the readable fallback class="terminal" present: true
  match  [us1] row is a store event [auc-sm-vic-2026-10-10-more]      store has rows: order_book=2 trade=1
  match  [us1] list.order_book rows == store [auc-sm-vic-2026-10-10-more] terminal=2 store=2
  match  [us1] list.trade rows == store [auc-sm-vic-2026-10-10-more]  terminal=1 store=1
  match  [us1] row is a store event [ja2-jub-con-2026-10-10-mor]      store has rows: order_book=2 trade=1
  match  [us1] list.order_book rows == store [ja2-jub-con-2026-10-10-mor] terminal=2 store=2
  match  [us1] list.trade rows == store [ja2-jub-con-2026-10-10-mor]  terminal=1 store=1
  match  [us1] row is a store event [val-nrg-loud-2026-10-10]         store has rows: order_book=10 trade=5
  match  [us1] list.order_book rows == store [val-nrg-loud-2026-10-10] terminal=10 store=10
  match  [us1] list.trade rows == store [val-nrg-loud-2026-10-10]     terminal=5 store=5
  match  [us1] row is a store event [cs2-dep5-vita-2026-10-10]        store has rows: order_book=2 trade=1
  match  [us1] list.order_book rows == store [cs2-dep5-vita-2026-10-10] terminal=2 store=2
  match  [us1] list.trade rows == store [cs2-dep5-vita-2026-10-10]    terminal=1 store=1
  match  [us1] us1-01-terminal-list.png painted                       non-white 100%
  match  [us1] us1-02-list-counts.png painted                         non-white 100%
  match  [us1] us1-03-recorded-span.png painted                       non-white 100%
  match  [us1] the terminal lists at least one event                  rows=4
  match  [us1] filter narrows to the named event                      terminal=["val-nrg-loud-2026-10-10"] want=[val-nrg-loud-2026-10-10]
  match  [us1] us1-04-filter-narrows.png painted                      non-white 100%
  match  [us2] the chosen event row is clickable                      slug=val-nrg-loud-2026-10-10
  match  [us2] the product's event answer names a token               token=54766275672533937226534147411813036045778021882156237046712863902839579722640
  match  [us2] detail names the chosen event                          terminal=val-nrg-loud-2026-10-10 store=val-nrg-loud-2026-10-10
  match  [us2] detail names the moment T it printed (the store's span end) terminal=moment T: 2026-10-10T08:34:25.889Z · window: ±10s store=2026-10-10T08:34:25.889Z
  match  [us2] detail names the window it printed                     terminal=moment T: 2026-10-10T08:34:25.889Z · window: ±10s
  match  [us2] orderbook.at_T best_bid == store                       terminal=0.57 store=0.57
  match  [us2] orderbook.at_T best_ask == store                       terminal=0.58 store=0.58
  match  [us2] trades.around_T count == store                         terminal=3 store=3
  match  [us2] the chart drew a canvas                                canvas=614x212
  match  [us2] the chart canvas painted a series                      series-coloured pixels=1839 of 614x212
  match  [us2] the chart canvas is not a flat fill                    distinct colours=53
  match  [us2] chart series points == store trades over the span      terminal=5 store=5
  match  [us2] us2-01-event-detail-T.png painted                      non-white 100%
  match  [us2] us2-02-chart.png painted                               non-white 100%
  match  [us2] us2-03-orderbook-at-T.png painted                      non-white 100%
  match  [us2] us2-04-trades-around-T.png painted                     non-white 100%
  match  [us3] us3-01-store-unreachable.png painted                   non-white 100%
  match  [us3] an empty store lists no event                          terminal rows=0
  match  [us3] us3-02-empty-store.png painted                         non-white 100%
user-verify-m4: OK

Reverse runs — raw output

reverse-a.txt

FAIL us1-01-terminal-list.png              2.1%  full page  missing: class="terminal"; missing: trading terminal; missing: store: up
ok   us1-02-list-counts.png              19.72%  #events
ok   us1-03-recorded-span.png            10.01%  #span
FAIL us1-04-filter-narrows.png           19.72%  #events  missing: events: 1/4
FAIL us2-01-event-detail-T.png             2.1%  full page  missing: Order book at the moment; missing: Trades around the moment; missing: best_bid; missing: best_ask; missing: moment T: 2026-10-10T08:34:25.889Z
FAIL us2-02-chart.png                        0%  #chart  missing selector #chart; blank frame: 0.00% non-white
FAIL us2-03-orderbook-at-T.png               0%  #orderbook  missing: best_bid; missing: best_ask; missing selector #orderbook; blank frame: 0.00% non-white
FAIL us2-04-trades-around-T.png              0%  #trades  missing selector #trades; blank frame: 0.00% non-white

== M4 user verification: the product terminal beside the collection store, field for field ==
terminal      : http://127.0.0.1:8890 (the Kotlin/JS + React bundle the product serves, driven in a real browser)
store         : http://127.0.0.1:8123 / liveui
listed events : 4 (the terminal) / 4 (the store has rows for)
chosen event  : val-nrg-loud-2026-10-10
chosen token  : none
moment T      : 2026-10-10T08:34:25.889Z
window        : ±10s
fonts         : /tmp/m4-fontconfig/fonts.conf
browser flags : --no-sandbox --disable-gpu --disable-dev-shm-usage

-- the store's own rows (read from ClickHouse, not from the DOM) --
  cs2-dep5-vita-2026-10-10                             order_book=2 trade=1
  val-nrg-loud-2026-10-10                              order_book=10 trade=5
  ja2-jub-con-2026-10-10-more-markets                  order_book=2 trade=1
  auc-sm-vic-2026-10-10-more-markets                   order_book=2 trade=1
  recorded span   : 2026-10-10T08:33:53.313Z .. 2026-10-10T08:34:25.889Z
  book at T       : best_bid=n/a best_ask=n/a
  trades around T : 0
  series (span)   : 0

-- the frames (each asserted: DOM before the shot, non-blank after) --
  FAIL  us1-01-terminal-list.png              2.1%  full page
  ok    us1-02-list-counts.png              19.72%  #events
  ok    us1-03-recorded-span.png            10.01%  #span
  FAIL  us1-04-filter-narrows.png           19.72%  #events
  FAIL  us2-01-event-detail-T.png             2.1%  full page
  FAIL  us2-02-chart.png                        0%  #chart
  FAIL  us2-03-orderbook-at-T.png               0%  #orderbook
  FAIL  us2-04-trades-around-T.png              0%  #trades

-- field for field: the terminal against the store --
  MISMATCH  [us1] the page is the product terminal, not the readable fallback class="terminal" present: false
  match  [us1] row is a store event [auc-sm-vic-2026-10-10-more]      store has rows: order_book=2 trade=1
  match  [us1] list.order_book rows == store [auc-sm-vic-2026-10-10-more] terminal=2 store=2
  match  [us1] list.trade rows == store [auc-sm-vic-2026-10-10-more]  terminal=1 store=1
  match  [us1] row is a store event [ja2-jub-con-2026-10-10-mor]      store has rows: order_book=2 trade=1
  match  [us1] list.order_book rows == store [ja2-jub-con-2026-10-10-mor] terminal=2 store=2
  match  [us1] list.trade rows == store [ja2-jub-con-2026-10-10-mor]  terminal=1 store=1
  match  [us1] row is a store event [val-nrg-loud-2026-10-10]         store has rows: order_book=10 trade=5
  match  [us1] list.order_book rows == store [val-nrg-loud-2026-10-10] terminal=10 store=10
  match  [us1] list.trade rows == store [val-nrg-loud-2026-10-10]     terminal=5 store=5
  match  [us1] row is a store event [cs2-dep5-vita-2026-10-10]        store has rows: order_book=2 trade=1
  match  [us1] list.order_book rows == store [cs2-dep5-vita-2026-10-10] terminal=2 store=2
  match  [us1] list.trade rows == store [cs2-dep5-vita-2026-10-10]    terminal=1 store=1
  match  [us1] us1-01-terminal-list.png painted                       non-white 2.1%
  MISMATCH  [us1] us1-01-terminal-list.png holds                         missing: class="terminal"
  MISMATCH  [us1] us1-01-terminal-list.png holds                         missing: trading terminal
  MISMATCH  [us1] us1-01-terminal-list.png holds                         missing: store: up
  match  [us1] us1-02-list-counts.png painted                         non-white 19.72%
  match  [us1] us1-03-recorded-span.png painted                       non-white 10.01%
  match  [us1] the terminal lists at least one event                  rows=4
  MISMATCH  [us1] filter narrows to the named event                      terminal=[null,"auc-sm-vic-2026-10-10-more-markets","ja2-jub-con-2026-10-10-more-markets","val-nrg-loud-2026-10-10","cs2-dep5-vita-2026-10-10"] want=[val-nrg-loud-2026-10-10]
  match  [us1] us1-04-filter-narrows.png painted                      non-white 19.72%
  MISMATCH  [us1] us1-04-filter-narrows.png holds                        missing: events: 1/4
  match  [us2] the chosen event row is clickable                      slug=val-nrg-loud-2026-10-10
  MISMATCH  [us2] the product's event answer names a token               token=none
  MISMATCH  [us2] detail names the chosen event                          terminal=Collected events store=val-nrg-loud-2026-10-10
  MISMATCH  [us2] detail names the moment T it printed (the store's span end) terminal=MISSING store=2026-10-10T08:34:25.889Z
  MISMATCH  [us2] detail names the window it printed                     terminal=MISSING
  MISMATCH  [us2] orderbook.at_T best_bid == store                       terminal=undefined store=n/a
  MISMATCH  [us2] orderbook.at_T best_ask == store                       terminal=undefined store=n/a
  match  [us2] trades.around_T count == store                         terminal=0 store=0
  MISMATCH  [us2] the chart drew a canvas                                canvas=none
  MISMATCH  [us2] the chart canvas painted a series                      series-coloured pixels=n/a of ?
  MISMATCH  [us2] the chart canvas is not a flat fill                    distinct colours=n/a
  MISMATCH  [us2] chart series points == store trades over the span      terminal=n/a store=0
  match  [us2] us2-01-event-detail-T.png painted                      non-white 2.1%
  MISMATCH  [us2] us2-01-event-detail-T.png holds                        missing: Order book at the moment
  MISMATCH  [us2] us2-01-event-detail-T.png holds                        missing: Trades around the moment
  MISMATCH  [us2] us2-01-event-detail-T.png holds                        missing: best_bid
  MISMATCH  [us2] us2-01-event-detail-T.png holds                        missing: best_ask
  MISMATCH  [us2] us2-01-event-detail-T.png holds                        missing: moment T: 2026-10-10T08:34:25.889Z
  MISMATCH  [us2] us2-02-chart.png painted                               non-white 0%
  MISMATCH  [us2] us2-02-chart.png holds                                 missing selector #chart
  MISMATCH  [us2] us2-02-chart.png holds                                 blank frame: 0.00% non-white
  MISMATCH  [us2] us2-03-orderbook-at-T.png painted                      non-white 0%
  MISMATCH  [us2] us2-03-orderbook-at-T.png holds                        missing: best_bid
  MISMATCH  [us2] us2-03-orderbook-at-T.png holds                        missing: best_ask
  MISMATCH  [us2] us2-03-orderbook-at-T.png holds                        missing selector #orderbook
  MISMATCH  [us2] us2-03-orderbook-at-T.png holds                        blank frame: 0.00% non-white
  MISMATCH  [us2] us2-04-trades-around-T.png painted                     non-white 0%
  MISMATCH  [us2] us2-04-trades-around-T.png holds                       missing selector #trades
  MISMATCH  [us2] us2-04-trades-around-T.png holds                       blank frame: 0.00% non-white
user-verify-m4: FAIL

reverse-b.txt

FAIL us1-01-terminal-list.png              100%  full page  missing: span: 2026-10-07T21:33:10.552Z .. 2026-10-07T21:36:09.447Z
ok   us1-02-list-counts.png                100%  #events
FAIL us1-03-recorded-span.png              100%  #span  missing: span: 2026-10-07T21:33:10.552Z .. 2026-10-07T21:36:09.447Z
ok   us1-04-filter-narrows.png             100%  #events
FAIL us2-01-event-detail-T.png             100%  full page  missing: moment T: 2026-10-07T21:36:09.447Z
ok   us2-02-chart.png                      100%  #chart
ok   us2-03-orderbook-at-T.png             100%  #orderbook
ok   us2-04-trades-around-T.png            100%  #trades

== M4 user verification: the product terminal beside the collection store, field for field ==
terminal      : http://127.0.0.1:8899 (the Kotlin/JS + React bundle the product serves, driven in a real browser)
store         : http://127.0.0.1:8123 / default
listed events : 4 (the terminal) / 2 (the store has rows for)
chosen event  : val-nrg-loud-2026-10-10
chosen token  : 54766275672533937226534147411813036045778021882156237046712863902839579722640
moment T      : 2026-10-07T21:36:09.447Z
window        : ±10s
fonts         : /tmp/m4-fontconfig/fonts.conf
browser flags : --no-sandbox --disable-gpu --disable-dev-shm-usage

-- the store's own rows (read from ClickHouse, not from the DOM) --
  will-elise-stefanik-win-the-2028-republican-presid   order_book=4 trade=1
  will-shakhtar-donetsk-win-the-2026-27-uefa-champio   order_book=360 trade=179
  recorded span   : 2026-10-07T21:33:10.552Z .. 2026-10-07T21:36:09.447Z
  book at T       : best_bid=n/a best_ask=n/a
  trades around T : 0
  series (span)   : 0

-- the frames (each asserted: DOM before the shot, non-blank after) --
  FAIL  us1-01-terminal-list.png              100%  full page
  ok    us1-02-list-counts.png                100%  #events
  FAIL  us1-03-recorded-span.png              100%  #span
  ok    us1-04-filter-narrows.png             100%  #events
  FAIL  us2-01-event-detail-T.png             100%  full page
  ok    us2-02-chart.png                      100%  #chart
  ok    us2-03-orderbook-at-T.png             100%  #orderbook
  ok    us2-04-trades-around-T.png            100%  #trades

-- field for field: the terminal against the store --
  match  [us1] the page is the product terminal, not the readable fallback class="terminal" present: true
  MISMATCH  [us1] row is a store event [auc-sm-vic-2026-10-10-more]      store has rows: order_book=0 trade=0
  MISMATCH  [us1] list.order_book rows == store [auc-sm-vic-2026-10-10-more] terminal=2 store=absent
  MISMATCH  [us1] list.trade rows == store [auc-sm-vic-2026-10-10-more]  terminal=1 store=absent
  MISMATCH  [us1] row is a store event [ja2-jub-con-2026-10-10-mor]      store has rows: order_book=0 trade=0
  MISMATCH  [us1] list.order_book rows == store [ja2-jub-con-2026-10-10-mor] terminal=2 store=absent
  MISMATCH  [us1] list.trade rows == store [ja2-jub-con-2026-10-10-mor]  terminal=1 store=absent
  MISMATCH  [us1] row is a store event [val-nrg-loud-2026-10-10]         store has rows: order_book=0 trade=0
  MISMATCH  [us1] list.order_book rows == store [val-nrg-loud-2026-10-10] terminal=10 store=absent
  MISMATCH  [us1] list.trade rows == store [val-nrg-loud-2026-10-10]     terminal=5 store=absent
  MISMATCH  [us1] row is a store event [cs2-dep5-vita-2026-10-10]        store has rows: order_book=0 trade=0
  MISMATCH  [us1] list.order_book rows == store [cs2-dep5-vita-2026-10-10] terminal=2 store=absent
  MISMATCH  [us1] list.trade rows == store [cs2-dep5-vita-2026-10-10]    terminal=1 store=absent
  match  [us1] us1-01-terminal-list.png painted                       non-white 100%
  MISMATCH  [us1] us1-01-terminal-list.png holds                         missing: span: 2026-10-07T21:33:10.552Z .. 2026-10-07T21:36:09.447Z
  match  [us1] us1-02-list-counts.png painted                         non-white 100%
  match  [us1] us1-03-recorded-span.png painted                       non-white 100%
  MISMATCH  [us1] us1-03-recorded-span.png holds                         missing: span: 2026-10-07T21:33:10.552Z .. 2026-10-07T21:36:09.447Z
  match  [us1] the terminal lists at least one event                  rows=4
  match  [us1] filter narrows to the named event                      terminal=["val-nrg-loud-2026-10-10"] want=[val-nrg-loud-2026-10-10]
  match  [us1] us1-04-filter-narrows.png painted                      non-white 100%
  match  [us2] the chosen event row is clickable                      slug=val-nrg-loud-2026-10-10
  match  [us2] the product's event answer names a token               token=54766275672533937226534147411813036045778021882156237046712863902839579722640
  match  [us2] detail names the chosen event                          terminal=val-nrg-loud-2026-10-10 store=val-nrg-loud-2026-10-10
  MISMATCH  [us2] detail names the moment T it printed (the store's span end) terminal=moment T: 2026-10-10T08:34:25.889Z · window: ±10s store=2026-10-07T21:36:09.447Z
  match  [us2] detail names the window it printed                     terminal=moment T: 2026-10-10T08:34:25.889Z · window: ±10s
  MISMATCH  [us2] orderbook.at_T best_bid == store                       terminal=0.57 store=n/a
  MISMATCH  [us2] orderbook.at_T best_ask == store                       terminal=0.58 store=n/a
  MISMATCH  [us2] trades.around_T count == store                         terminal=3 store=0
  match  [us2] the chart drew a canvas                                canvas=614x212
  match  [us2] the chart canvas painted a series                      series-coloured pixels=1839 of 614x212
  match  [us2] the chart canvas is not a flat fill                    distinct colours=53
  MISMATCH  [us2] chart series points == store trades over the span      terminal=5 store=0
  match  [us2] us2-01-event-detail-T.png painted                      non-white 100%
  MISMATCH  [us2] us2-01-event-detail-T.png holds                        missing: moment T: 2026-10-07T21:36:09.447Z
  match  [us2] us2-02-chart.png painted                               non-white 100%
  match  [us2] us2-03-orderbook-at-T.png painted                      non-white 100%
  match  [us2] us2-04-trades-around-T.png painted                     non-white 100%
user-verify-m4: FAIL

reverse-c.txt

FAIL us1-01-terminal-list.png              100%  full page  missing: Collected events; missing: store: up; missing: span: 2026-10-10T08:33:53.313Z .. 2026-10-10T08:34:25.889Z
FAIL us1-02-list-counts.png                  0%  #events  missing: events: 0/0; missing selector #events; blank frame: 0.00% non-white
FAIL us1-03-recorded-span.png                0%  #span  missing: span: 2026-10-10T08:33:53.313Z .. 2026-10-10T08:34:25.889Z; missing selector #span; blank frame: 0.00% non-white

== M4 user verification: the product terminal beside the collection store, field for field ==
terminal      : http://127.0.0.1:8898 (the Kotlin/JS + React bundle the product serves, driven in a real browser)
store         : http://127.0.0.1:8123 / liveui
listed events : 0 (the terminal) / 4 (the store has rows for)
chosen event  : null
chosen token  : none
moment T      : 2026-10-10T08:34:25.889Z
window        : ±10s
fonts         : /tmp/m4-fontconfig/fonts.conf
browser flags : --no-sandbox --disable-gpu --disable-dev-shm-usage

-- the store's own rows (read from ClickHouse, not from the DOM) --
  cs2-dep5-vita-2026-10-10                             order_book=2 trade=1
  val-nrg-loud-2026-10-10                              order_book=10 trade=5
  ja2-jub-con-2026-10-10-more-markets                  order_book=2 trade=1
  auc-sm-vic-2026-10-10-more-markets                   order_book=2 trade=1
  recorded span   : 2026-10-10T08:33:53.313Z .. 2026-10-10T08:34:25.889Z
  book at T       : best_bid=n/a best_ask=n/a
  trades around T : 0
  series (span)   : 0

-- the frames (each asserted: DOM before the shot, non-blank after) --
  FAIL  us1-01-terminal-list.png              100%  full page
  FAIL  us1-02-list-counts.png                  0%  #events
  FAIL  us1-03-recorded-span.png                0%  #span

-- field for field: the terminal against the store --
  match  [us1] the page is the product terminal, not the readable fallback class="terminal" present: true
  match  [us1] us1-01-terminal-list.png painted                       non-white 100%
  MISMATCH  [us1] us1-01-terminal-list.png holds                         missing: Collected events
  MISMATCH  [us1] us1-01-terminal-list.png holds                         missing: store: up
  MISMATCH  [us1] us1-01-terminal-list.png holds                         missing: span: 2026-10-10T08:33:53.313Z .. 2026-10-10T08:34:25.889Z
  MISMATCH  [us1] us1-02-list-counts.png painted                         non-white 0%
  MISMATCH  [us1] us1-02-list-counts.png holds                           missing: events: 0/0
  MISMATCH  [us1] us1-02-list-counts.png holds                           missing selector #events
  MISMATCH  [us1] us1-02-list-counts.png holds                           blank frame: 0.00% non-white
  MISMATCH  [us1] us1-03-recorded-span.png painted                       non-white 0%
  MISMATCH  [us1] us1-03-recorded-span.png holds                         missing: span: 2026-10-10T08:33:53.313Z .. 2026-10-10T08:34:25.889Z
  MISMATCH  [us1] us1-03-recorded-span.png holds                         missing selector #span
  MISMATCH  [us1] us1-03-recorded-span.png holds                         blank frame: 0.00% non-white
  MISMATCH  [us1] the terminal lists at least one event                  rows=0
user-verify-m4: FAIL

Layers

What this run does not cover

Links