5 Commits
Author SHA1 Message Date
Ryan Gregg fab331e67e Virtualize the message list
A conversation rendered every message it had loaded. Nothing bounded that
work, so cost grew with history: at 2000 messages the list mounted ~20k DOM
nodes, took ~285ms to mount, and ~212ms to re-render on any update. While
that runs, nothing else on the main thread gets a turn, typing included.

Only the visible window is mounted now, measured in Chromium with the real
component tree:

  history    mount            re-render        DOM nodes
    2000     285ms -> 4ms     212ms -> 2.2ms   22038 -> 225

Rendered rows stay at ~17 regardless of how much history is loaded.

Row heights are measured rather than assumed, since messages vary from one line
to a wrapped paragraph with path badges, so the scroll behaviors that used
to do height arithmetic now anchor by index instead, which is exact even
when off-screen rows still carry estimated heights:

- opening a conversation, and following new messages, anchor to the last row
- loading older messages anchors to the message that was on top
- jump-to-message and jump-to-unread scroll by index, since the target row
  may not be mounted yet

Two degenerate cases are handled explicitly: a viewport that measures zero
(before first layout, hidden tab) falls back to a nominal height instead of
collapsing the window to nothing, and a row that measures zero keeps its
estimate instead of ballooning the window.

Most of the diff is re-indentation from the new wrapper element; the real
change is 112 added / 36 removed lines (git diff -w).
2026-07-25 12:32:14 -07:00
Ryan Gregg 5e5ab7db4d Keep overheard packet traffic out of the chat render path
Typing in a conversation got progressively slower as its history grew.
The cause was not the input: every packet the node overhears was stored
in App state, so each one re-rendered the whole tree, including the
un-memoized MessageList. That cost scales with history length, and on a
busy mesh it saturated the main thread so keystrokes queued behind it.

Measured in Chromium with the real components, cost per overheard packet:

  history    before    after
      50      7.3ms    ~0ms
     500     48.5ms    ~0ms
    1000     97.7ms    ~0ms
    2000    228.4ms    ~0ms

The packet stream now lives in a small external store that views subscribe
to individually, so only the map, visualizer, raw feed, and cracker re-render
when a packet arrives, so the chat view is no longer along for the ride.
This also retires useRawPacketStatsSession, whose session state was App
state for the same reason.
2026-07-25 12:32:14 -07:00
Ryan Gregg 4919f551f8 feat(sidebar): add collapsible sections and split repeaters list 2026-02-14 17:50:57 -08:00
Ryan Gregg 61de2584e9 Fix for running as PWA 2026-02-04 21:45:30 -08:00
Ryan Gregg 89a2fca7fd Adjust how app dispalys as PWA to enable safe areas 2026-02-04 21:24:43 -08:00