Make the board responsive: connection lanes, deduped reads, audio and Desk fixes - #10
Merged
Merged
Conversation
Every mounted listening page (the selected one and both receded neighbours) gave each sibling in its Compare list its own preloading <audio>, and decoded its waveform from a whole-file read at the same time. On Music that is 36 metadata players and three 6 MB downloads at once. Over HTTP/1.1 (the desktop board at 127.0.0.1:4000) a browser holds six connections per host, so the selected page's own media request queued behind them: Safari took 9-17 s from Play to sound on a local daemon. The phone reaches the board through the tailnet's HTTP/2 front, which multiplexes, so it never queued. Background listening reads now share two slots, newest request first, and each sibling's duration comes from one transient metadata read cached by path and provenance. Play to sound measured 0.08-1.5 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every Desk render ended with the keyboard selection's scroll into view, and the selection is the card last opened. So each poll that changed the feed scrolled that card back into view, smoothly. On the phone it also paged the Desk back to that card's column. A person who had scrolled or paged on to another card and pressed it during that scroll opened whatever card had slid under the pointer. A redraw now only repaints the selection. Moving the selection, and returning from the reader, still reveal it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While the Board was the active view, each 15 s poll refreshed the overview, and every refresh set every metadata retry back to zero. A receipt whose owner had already answered "not found" was read again on every poll, by every open tab, forever. Each of those reads made the daemon ask each host in turn: 1.2 s of daemon work per read. Measured on the live board before the fix: 7 such fibers, each read 7 times in 95 s by one tab. The daemon's log held 13k "no fiber found" lines for the day. A poll now retries only transient failures at once. A confirmed miss waits 30 s, doubling to 5 min, and a fresh visit to the Board retries it at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A playwright-core script that drives a running board read-only: Desk card to first settled page, page steps, channel switches, the Board overview's first thumbnails, the phone reader, every /api request per phase (count, bytes, duplicate reads of one document, time queued before send) and long tasks, cold and warm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
…onnection Over HTTP/1.1 the browser opens six connections to the daemon. At load the overview's fiber preloads (four at a time, 2-20 s each when relayed to a remote host), the 8 MB wikilink index, one agent registry per host and the body-link probes took all six, and the page someone opened queued 2-20 s behind them. Preloads and the index now share a slow lane of one connection, registries and probes a quiet lane of two; over HTTP/2 the lanes widen. A click on a folio still reads at once, taking over its queued preload, and a host that refused its agent registry is asked again after a minute instead of on every render. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
The channel waited for one file-info read per document (four at a time) before it selected its report, and did again every 15 s. The reads now follow the first show in the quiet lane, at most one pass per channel, and redraw only when a time moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
A report mounted on the stage reads its whole body and names itself, so its 64 KiB peek was a second read of the same bytes racing the first. Peeks also run in the quiet lane, and a first owner file time for unchanged receipts no longer counts as a new version to peek again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
Files above one MiB carried a weak size/mtime/inode ETag, which the reader's
live poll will not trust, so a selected 4-21 MB report was downloaded whole
every 4 s, through the relay when its owner was remote. A whole GET of a
document up to 64 MiB (not media) now carries its SHA-256, read once per
settled version: FileDigests remembers it under {path, size, mtime, ctime,
inode} only after the file has been still for two seconds, so a later write's
new ctime can never meet a stale digest. Ranged, HEAD and media reads stay
bounded and never read a file to make one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
The first read of a watched document used no-store, so reopening the board downloaded every report again. It now uses no-cache: the browser sends its stored validator and an unchanged report comes back as a 304 from its cache. Later polls and forced refreshes still go to the owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
The reader re-reads its channel's fiber every 15 s. A body relayed from a remote host takes up to 20 s, so the refresh held a connection nearly all the time. It now waits in the slow lane. Opening a channel still reads at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
A stalled open or switch dropped its whole run, so the worst stalls vanished from the medians. It now counts as 30 s, and a crashed browser is relaunched for the next run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
Every waveform decode constructed a realtime AudioContext, which opens a session with the system audio server (coreaudiod on macOS) for a file nobody was playing, and a closed tab, killed browser or abandoned decode never closed it. Decoding now uses an OfflineAudioContext, which opens no device, at 8 kHz: a 26-minute recording decodes to about a sixth of the samples it did at 48 kHz, and its 1000 peaks differ from a 44.1 kHz decode by a median of 0.02 (in Chrome, on three real mp3s). Playback stays on the <audio> element. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
A receded neighbour downloaded and decoded its whole recording for a poster nobody was listening to. A neighbour now draws peaks only when this session already decoded them, and a page decodes when it is first selected. Both reads wait in the listening page's shared read slots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
The title probe, each thumbnail family and a hover preview each read a document's first 64 KiB on their own, so one report was peeked two or three times within seconds. documentResources.peekDocument reads it once for everyone asking at the same time, keeps it 30 s, and queues peeks in the quiet lane by what they serve (selected, neighbours, titles, thumbnails, durations). A version that moved past an earlier peek reads fresh. It is the seed of the document resource cache the next refactor grows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
…eRefresh The probe asks whether a page is already reading a document; the mocks lacked that export and the suite reported unhandled errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Shuttle had become close to unusable on the desktop. Opening a channel took 10–20 s, mp3s took 9–17 s to start, Desk cards jumped under the pointer, and the board fetched the same documents over and over. This PR fixes the causes found in round 3's bug and performance lanes. The reader's layout changes come separately.
Claude Opus 5.5 on behalf of Cail.
The main cause: six connections
On
127.0.0.1:4000the board speaks HTTP/1.1, so Chrome opens at most six connections to the daemon. Background reads held all six for seconds at a time:agentsreads;file-infoper document every 15 s;<audio>elements on Music.The page you'd just opened queued behind them. The phone goes through tailscale serve (HTTP/2), which is why it felt better there.
9f7cb3ba).223d4e83).1bedbfc3).b709c751).9a074428).e60f79fa). A live document's first read revalidates the browser's copy (18b452b7).a8ef1710). Play reaches sound in 0.1–1.5 s, down from 9–17 s in WebKit.OfflineAudioContextat 8 kHz, never a realtimeAudioContext. Before, every decode opened a coreaudiod session that leaked when a tab or browser died; Cail's coreaudiod had grown to 13 GB. A test asserts zero realtime contexts across 13 recordings (1c61e51b).f1d82fda).Bugs
ded8fd35).no fiber founddaemon calls, which fed the hub's fd exhaustion. A missing fiber now backs off across polls (9970ec38).Measured
The numbers come from
ui/scripts/measure-workspace.mjs(4429c3b2): headless Chrome against the live daemon over HTTP/1.1. Each cell is the median of 3 runs; "phone" is a 390×844 viewport.Caveats:
Gates
tsc --noEmitpasses.mix test: 1680, 0 failures.🤖 Generated with Claude Code
https://claude.ai/code/session_01XTQ9CSGaUs1DbFHxME9BB3