InputMsg::Resize is still sent flat, so the gateway drops every resize report #4

Closed
opened 2026-09-07 21:11:24 +09:00 by natsukium · 0 comments
Owner

Found by cross-checking the felis-broker.v1 JSON contract between
felis-web-gateway follow-felis-105b0899 (577138be) and this repo's
follow-felis-105b0899 (edfb8dfa). Both pin felis
105b089979369fd310757375af3df7900ae7e523.

The mismatch

felis moved InputMsg::Resize's four loose axes behind a dims field in the
wire reset. crates/felis-protocol/src/messages/input.rs:

    /// Window resize.
    Resize {
        /// The window's full geometry, pixel dims included.
        dims: RequestedDims,
    },

RequestedDims is {rows: u32, cols: u32, pixel_w: u32, pixel_h: u32}, and
InputMsg is externally tagged with no serde attributes, so the wire JSON is
{"Resize":{"dims":{"rows":…,"cols":…,"pixel_w":…,"pixel_h":…}}}.

This component still emits the pre-reset flat shape —
web/felis-terminal.ts:1287:

  const sendResize = (rows: number, cols: number): void => {
    sentRows = rows;
    sentCols = cols;
    conn?.sendInput?.({
      Resize: {
        rows,
        cols,
        pixel_w: Math.round(cellWidthPx() * cols),
        pixel_h: linePx * rows,
      },
    });
  };

serde_json rejects that as missing field \dims``.

Why it breaks a live session

Neither gateway path forwards it, and neither reports the failure to the page:

  • broker: src/broker.rs parse_client_text decodes the input field with
    serde_json::from_value::<InputMsg> and returns Err("bad InputMsg: …");
    the socket loop's Err arm only does
    eprintln!("dropping unparseable broker message: {err}").
  • legacy one-session relay: src/main.rs drops anything that does not parse as
    an InputMsg, by the same rule stated in its module doc.

So every sendResize this component makes is silently discarded:

  1. The one report sent unconditionally on attach
    (web/felis-terminal.ts:2714, in connect) never lands, so the daemon
    never learns the renderer's cell pixel metrics. ws_xpixel/ws_ypixel stay
    0, which is exactly the case sendResize's own comment says Kitty-graphics
    producers (yazi's Dimension::ratio()) need — images scale off a guessed
    cell size.
  2. resize="fit" is inert end to end: scheduleFitsendResize
    (web/felis-terminal.ts:1311) is dropped, so rotating a phone or resizing
    the window never reflows the shell. The element letterboxes against a grid
    the daemon never resized.

Fix

Wrap the payload:

      Resize: {
        dims: { rows, cols, pixel_w: , pixel_h:  },
      },

There is nothing to change on the gateway: it forwards a decoded InputMsg
verbatim, and docs/broker.md / src/main.rs's module doc already name the
{"Resize":{"dims":{..}}} shape.

Not covered by a test

tests/apply.rs covers the daemon→client direction only; the input encoders
live in TypeScript and nothing exercises sendResize against a real decoder.
The follow commit (edfb8dfa) rewrote the removed Search/CaptureRegion
senders but left this one.

Found by cross-checking the `felis-broker.v1` JSON contract between felis-web-gateway `follow-felis-105b0899` (577138be) and this repo's `follow-felis-105b0899` (edfb8dfa). Both pin felis `105b089979369fd310757375af3df7900ae7e523`. ## The mismatch felis moved `InputMsg::Resize`'s four loose axes behind a `dims` field in the wire reset. `crates/felis-protocol/src/messages/input.rs`: ```rust /// Window resize. Resize { /// The window's full geometry, pixel dims included. dims: RequestedDims, }, ``` `RequestedDims` is `{rows: u32, cols: u32, pixel_w: u32, pixel_h: u32}`, and `InputMsg` is externally tagged with no serde attributes, so the wire JSON is `{"Resize":{"dims":{"rows":…,"cols":…,"pixel_w":…,"pixel_h":…}}}`. This component still emits the pre-reset flat shape — `web/felis-terminal.ts:1287`: ```ts const sendResize = (rows: number, cols: number): void => { sentRows = rows; sentCols = cols; conn?.sendInput?.({ Resize: { rows, cols, pixel_w: Math.round(cellWidthPx() * cols), pixel_h: linePx * rows, }, }); }; ``` `serde_json` rejects that as `missing field \`dims\``. ## Why it breaks a live session Neither gateway path forwards it, and neither reports the failure to the page: - broker: `src/broker.rs` `parse_client_text` decodes the `input` field with `serde_json::from_value::<InputMsg>` and returns `Err("bad InputMsg: …")`; the socket loop's `Err` arm only does `eprintln!("dropping unparseable broker message: {err}")`. - legacy one-session relay: `src/main.rs` drops anything that does not parse as an `InputMsg`, by the same rule stated in its module doc. So every `sendResize` this component makes is silently discarded: 1. The one report sent unconditionally on attach (`web/felis-terminal.ts:2714`, in `connect`) never lands, so the daemon never learns the renderer's cell pixel metrics. `ws_xpixel`/`ws_ypixel` stay 0, which is exactly the case `sendResize`'s own comment says Kitty-graphics producers (yazi's `Dimension::ratio()`) need — images scale off a guessed cell size. 2. `resize="fit"` is inert end to end: `scheduleFit` → `sendResize` (`web/felis-terminal.ts:1311`) is dropped, so rotating a phone or resizing the window never reflows the shell. The element letterboxes against a grid the daemon never resized. ## Fix Wrap the payload: ```ts Resize: { dims: { rows, cols, pixel_w: …, pixel_h: … }, }, ``` There is nothing to change on the gateway: it forwards a decoded `InputMsg` verbatim, and `docs/broker.md` / `src/main.rs`'s module doc already name the `{"Resize":{"dims":{..}}}` shape. ## Not covered by a test `tests/apply.rs` covers the daemon→client direction only; the input encoders live in TypeScript and nothing exercises `sendResize` against a real decoder. The follow commit (edfb8dfa) rewrote the removed `Search`/`CaptureRegion` senders but left this one.
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#4
No description provided.