[fix](trx-frontend-http): restore decode history from the stored records #36

Merged
sjg merged 1 commits from fix/decode-history-replay into main 2026-08-03 18:39:37 +02:00
Owner

The bug

Replay required every restored record to carry a string type:

data.messages.filter((message) => isRecord(message) && typeof message.type === "string")

Stored records do not have one. Confirmed against a live server — the first restored group is AIS, and its records hold only decoder fields:

first record keys: ["bit_len","channel","cog_deg","crc_ok","lat","lon",
                    "message_type","mmsi","raw_bytes","repeat", …] (13)
has type field:    undefined

So the filter discarded 100% of them, and did it silently: /decode/history returned its full 340KB, the worker decoded it without error, and nothing was logged. The history simply never appeared.

type identifies live SSE frames, which do carry it — which is why live decoding worked and only replay broke. History arrives already grouped, and the group's kind is delivered alongside the messages, so type was never needed to route them. The filter now requires only that a record is an object.

Why it took so long to find

Three earlier theories were excluded by evidence before this one: the queue flush dropping restore for late-registering plugins (every decoder in the history set implements restore), an hf_aprs kind/id mismatch (they match), and the strict CBOR decoder rejecting indefinite lengths (the server emits definite lengths only).

The reason none of it was visible from here: the smoke fixture serves no stored history, so no test exercises this path at all. It was found by driving the real worker from the browser console against a live server.

Follow-up worth doing

Put stored history into the smoke fixture, as PR #35 did for the decoder registry. A single record per group would have caught this immediately — and would guard the replay path, which currently has no coverage whatsoever.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz

## The bug Replay required every restored record to carry a string `type`: ```ts data.messages.filter((message) => isRecord(message) && typeof message.type === "string") ``` Stored records do not have one. Confirmed against a live server — the first restored group is AIS, and its records hold only decoder fields: ``` first record keys: ["bit_len","channel","cog_deg","crc_ok","lat","lon", "message_type","mmsi","raw_bytes","repeat", …] (13) has type field: undefined ``` So the filter discarded **100%** of them, and did it **silently**: `/decode/history` returned its full 340KB, the worker decoded it without error, and nothing was logged. The history simply never appeared. `type` identifies live SSE frames, which do carry it — which is why live decoding worked and only replay broke. History arrives already grouped, and the group's `kind` is delivered alongside the messages, so `type` was never needed to route them. The filter now requires only that a record is an object. ## Why it took so long to find Three earlier theories were excluded by evidence before this one: the queue flush dropping `restore` for late-registering plugins (every decoder in the history set implements `restore`), an `hf_aprs` kind/id mismatch (they match), and the strict CBOR decoder rejecting indefinite lengths (the server emits definite lengths only). The reason none of it was visible from here: **the smoke fixture serves no stored history**, so no test exercises this path at all. It was found by driving the real worker from the browser console against a live server. ## Follow-up worth doing Put stored history into the smoke fixture, as PR #35 did for the decoder registry. A single record per group would have caught this immediately — and would guard the replay path, which currently has no coverage whatsoever. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz
sjg added 1 commit 2026-08-03 18:39:15 +02:00
[fix](trx-frontend-http): restore decode history from the stored records
CI / lint (pull_request) Successful in 2m21s
CI / test (pull_request) Successful in 8m19s
CI / frontend (pull_request) Successful in 3m1s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m16s
CI / test (push) Successful in 7m26s
CI / frontend (push) Successful in 2m11s
CI / reuse (push) Successful in 2s
863a6d8fd4
Replay required every restored record to carry a string `type`, and
stored records do not have one: an AIS entry holds mmsi, lat, lon,
crc_ok and its decoder's own fields, nothing more.  The filter therefore
discarded all of them, and did it silently — the fetch returned its full
payload, the worker decoded it, and no error was logged, so the history
simply never appeared.

That field identifies live SSE frames, which do carry it, which is why
only replay was affected.  History arrives already grouped and the
group's kind is delivered alongside the messages, so `type` was never
needed to route them.  Require only that a record is an object.

Confirmed against a live server: the first restored group is AIS, and
its records expose their decoder fields with `type` undefined.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz
Signed-off-by: Stan Grams <sjg@haxx.space>
sjg merged commit 863a6d8fd4 into main 2026-08-03 18:39:37 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sjg/trx-rs#36