The web/public/*.fcast fixtures predate the wire reset and no longer decode #1

Open
opened 2026-09-07 21:01:36 +09:00 by natsukium · 0 comments
Owner

Summary

Every recording under web/public/ was written against the pre-reset felis
wire and no longer decodes. applyJson rejects the first frame of each file,
so the demo gallery (web/index.html), the mobile demo's replay tabs and
bench/render.html's fixtures all show an empty grid plus a console error.

Found while following felis to 105b089979369fd310757375af3df7900ae7e523
(branch follow-felis-105b0899).

Reproduction

The stored frames still spell the old shapes:

$ head -2 web/public/hello.fcast
{"capabilities":[],"capability_bits":0,"encoding":"json","format":"felis-fcast","grid":{"cols":48,"rows":8},"meta":{"title":"felis-web demo"},"proto_version":1,"version":1}
[0,"Grid",{"RehydrateBegin":{"dims":{"cols":48,"rows":8}}}]

Deserializing those two frame bodies against felis at that rev
(felis_protocol::messages::GridMsg):

ERR  {"RehydrateBegin":{"dims":{"cols":48,"ro… -> invalid type: map, expected unit at line 1 column 18
ERR  {"RowDelta":{"packed_cells":{"Rle":{"att… -> missing field `rows` at line 1 column 121

What changed

  • GridMsg::RehydrateBegin is payload-free; the dimensions come from the
    preceding SessionMsg::Attached or the latest GridMsg::Size.
  • GridMsg::RowDelta carries rows: [(row, payload)] and absorbed
    RowDeltaBatch; a payload is an opaque byte string, so the structural-JSON
    row travels as the bytes of its own JSON text.
  • GridMsg::GridSize is GridMsg::Size { dims }.
  • The header is .fcast format v2: proto_major / proto_minor replace
    proto_version and the capabilities / capability_bits pair, which named
    a bitmap felis no longer has (see felis-fcast, branch
    follow-felis-105b0899). fcastSource already reads proto_minor and
    falls back to 0, so a v1 header degrades rather than throwing — but its
    frames still do not decode.

Why it is out of scope for the follow PR

Regenerating a recording needs felis-fcast at the same pin plus a live
felis-daemon driving each scenario (tte.fcast is a recorded animation,
glyphs.fcast comes from web/tools/gen-glyphs-fcast.py, sessions.fcast
needs several sessions). None of that can be hand-edited into the files, and
felis-fcast's own follow has not landed on its main yet. The follow PR
therefore leaves the fixtures alone rather than shipping a half-migrated set.

Work

  1. Wait for felis-fcast's follow to land on main.
  2. Re-record each of btm, cursor, kitty, links, scroll, session,
    sessions, sizing, theme, tte, hello per README "Record a
    .fcast", and regenerate glyphs.fcast with web/tools/gen-glyphs-fcast.py.
  3. Add a check that a stale fixture fails CI instead of only the demo page: a
    wasm test that applies the first frame of each web/public/*.fcast would
    have caught this at the pin bump.
## Summary Every recording under `web/public/` was written against the pre-reset felis wire and no longer decodes. `applyJson` rejects the first frame of each file, so the demo gallery (`web/index.html`), the mobile demo's replay tabs and `bench/render.html`'s fixtures all show an empty grid plus a console error. Found while following felis to `105b089979369fd310757375af3df7900ae7e523` (branch `follow-felis-105b0899`). ## Reproduction The stored frames still spell the old shapes: ```console $ head -2 web/public/hello.fcast {"capabilities":[],"capability_bits":0,"encoding":"json","format":"felis-fcast","grid":{"cols":48,"rows":8},"meta":{"title":"felis-web demo"},"proto_version":1,"version":1} [0,"Grid",{"RehydrateBegin":{"dims":{"cols":48,"rows":8}}}] ``` Deserializing those two frame bodies against felis at that rev (`felis_protocol::messages::GridMsg`): ``` ERR {"RehydrateBegin":{"dims":{"cols":48,"ro… -> invalid type: map, expected unit at line 1 column 18 ERR {"RowDelta":{"packed_cells":{"Rle":{"att… -> missing field `rows` at line 1 column 121 ``` ## What changed - `GridMsg::RehydrateBegin` is payload-free; the dimensions come from the preceding `SessionMsg::Attached` or the latest `GridMsg::Size`. - `GridMsg::RowDelta` carries `rows: [(row, payload)]` and absorbed `RowDeltaBatch`; a payload is an opaque byte string, so the structural-JSON row travels as the bytes of its own JSON text. - `GridMsg::GridSize` is `GridMsg::Size { dims }`. - The header is `.fcast` format **v2**: `proto_major` / `proto_minor` replace `proto_version` and the `capabilities` / `capability_bits` pair, which named a bitmap felis no longer has (see felis-fcast, branch `follow-felis-105b0899`). `fcastSource` already reads `proto_minor` and falls back to `0`, so a v1 header degrades rather than throwing — but its frames still do not decode. ## Why it is out of scope for the follow PR Regenerating a recording needs `felis-fcast` at the same pin plus a live `felis-daemon` driving each scenario (`tte.fcast` is a recorded animation, `glyphs.fcast` comes from `web/tools/gen-glyphs-fcast.py`, `sessions.fcast` needs several sessions). None of that can be hand-edited into the files, and `felis-fcast`'s own follow has not landed on its `main` yet. The follow PR therefore leaves the fixtures alone rather than shipping a half-migrated set. ## Work 1. Wait for `felis-fcast`'s follow to land on `main`. 2. Re-record each of `btm`, `cursor`, `kitty`, `links`, `scroll`, `session`, `sessions`, `sizing`, `theme`, `tte`, `hello` per README "Record a `.fcast`", and regenerate `glyphs.fcast` with `web/tools/gen-glyphs-fcast.py`. 3. Add a check that a stale fixture fails CI instead of only the demo page: a wasm test that applies the first frame of each `web/public/*.fcast` would have caught this at the pin bump.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
natsukium/felis-web-component#1
No description provided.