Skip to content

Warm channel heads in the background before they are opened - #54

Merged
comp615 merged 4 commits into
block:mainfrom
comp615:warm-channels
Sep 14, 2026
Merged

comp615 merged 4 commits into
block:mainfrom
comp615:warm-channels

Conversation

@comp615

@comp615 comp615 commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

What

Sessions created with warm: true (enabled in provideRelay, alongside prepared: true) warm every roster channel's head after discovery, so opening a channel — even after a cold start — finds its timeline already local:

  • Ordering: starred channels first (from account sidebar preferences, now loaded at session start rather than waiting for the sidebar to mount), then the rest by recency of their retained head, never-fetched last. Restored disk heads are evaluated at drain time, so hydration finishing after discovery still reorders warmth by recency.
  • Politeness: one background-priority read at a time (never competes with demand reads for foreground slots); skips channels with an open window (demand owns those); skips heads inside their 60s freshness lease — which also makes repeated roster refreshes a no-op until leases expire, answering the earlier requery-on-every-event concern.
  • Avatar warming: each completed background head read also warms that channel's avatar URLs via media.warm(), which appends to the speculation queue after profile enrichment resolves instead of displacing focused-channel intents (capped queue, existing two-request concurrency).
  • Save-Data: when the browser reports navigator.connection.saveData, all speculative warming is disabled — the hover speculative head read, the background drain, and avatar preparation. Demand fetches are untouched.
  • Lifecycle: pending warmth is dropped when the session resets or clearCache() runs (so the Developer-tab clear-cache button from Add Developer settings tab with cache clear and broker stats #53 exercises a true cold load).

Sessions without the option keep the existing, test-encoded invariant that discovery never reads unopened channel heads — the existing relay tests pass unchanged; the new behavior is opt-in at the same boundary as prepared.

Validation

  • New src/features/relay/warm.test.ts (deterministic scripted transport, no sleeps): cold-load auto-warm with serial ordering; starred/recency/never-fetched order; freshness-lease skip on re-trigger; open-channel skip.
  • Media tests: prefetch warming appends across channels instead of replacing the queue; Save-Data disables all warming with zero requests.
  • Updated the edge-pill browser journey whose baseline assertion ("only focused-row preparation, never a roster fanout") raced the now-intended background warm drain; it now asserts its durable intent directly — every read after the baseline targets a distinct channel, so cue and scroll interactions never re-read. Verified locally on chromium and webkit projects.
  • Full relay suite: 503 passed (pnpm vitest run src/features/relay); pnpm typecheck and biome clean on touched files.
  • Live-network verification on the real community relay: cold load produced 26 avatar thumb requests from background warming with no channel opened; opening warmed channels served 7 of 8 avatar loads from cache.

Deferred checks

  • Live cold-load timing against the real relay (start → fully warmed) is best observed after deploy; unit tests cover ordering and skipping, not wall-clock warm duration.
  • Very large rosters (500-channel discovery cap) warm serially; if that's too slow in practice, a bounded "warm the N most relevant first" slice is a small follow-up.

🤖 Description written by AI (Amp); commits signed off by the agent on Charlie Croom's behalf per DCO.

Sessions created with warm: true (production wiring) warm every roster
channel's head after discovery: starred channels first, then the rest by
head recency, never-fetched last. Warming runs one background-priority
read at a time, skips channels with an open window (demand owns those),
skips heads inside their freshness lease, and drops pending work when the
session resets or the cache clears. Starred order comes from account
sidebar preferences, so the session loads them at startup and warming
waits for them to settle.

Restored disk heads are picked up at drain time, so hydration finishing
after discovery still reorders warmth by recency. Sessions without the
option keep the existing invariant that discovery never reads unopened
channel heads.

Signed-off-by: Charlie Croom <ccroom@squareup.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0a053-f85b-754c-a353-03fb77a26363
Co-authored-by: Amp <amp@ampcode.com>
@comp615
comp615 requested a review from wesbillman as a code owner September 14, 2026 15:38
comp615 and others added 2 commits September 14, 2026 12:02
The journey asserted that no reads follow the baseline beyond focused-row
preparation for two channels. With warm: true, the serial background warm
pass legitimately reads the rest of the roster during that window, so the
assertion raced the drain (webkit timing). Its durable intent — cue and
scroll interactions never re-read a channel — is now asserted directly:
every read after the baseline targets a distinct channel.

Signed-off-by: Charlie Croom <ccroom@squareup.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0a053-f85b-754c-a353-03fb77a26363
Co-authored-by: Amp <amp@ampcode.com>
Background head reads now prepare that channel's avatar URLs via
media.warm(), which appends to the speculation queue after profiles
resolve instead of displacing focused-channel intents. Speculative
warming (hover head reads, the background drain, avatar preparation)
is skipped under the Save-Data preference.

Signed-off-by: Charlie Croom <ccroom@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Charlie Croom <ccroom@squareup.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0a053-f85b-754c-a353-03fb77a26363

@baxen baxen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice nice

…d media, dev tab)

Signed-off-by: Charlie Croom <ccroom@squareup.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0a053-f85b-754c-a353-03fb77a26363
Co-authored-by: Amp <amp@ampcode.com>
@comp615
comp615 requested a review from a team as a code owner September 14, 2026 19:03
@comp615
comp615 merged commit 3f824df into block:main Sep 14, 2026
12 checks passed
@comp615
comp615 deleted the warm-channels branch September 14, 2026 19:20
delkc added a commit that referenced this pull request Sep 15, 2026
* origin/main:
  Fix inline mention styling in channels (#74)
  Fix self-replenishing background channel warming (#71)
  Add links and thread previews (#42)
  Add shared receive-only channel and thread typing (#33)
  Warm channel heads in the background before they are opened (#54)
  Add Developer settings tab with cache clear and broker stats (#53)
  Request sized relay media for avatars (#58)
  Allow parallel browser development ports (#57)
  Add worktree bootstrap for local configuration (#56)
  Make thread opens part of navigation history (#55)
  Remove the decoded-avatar hot set; respect Save-Data (#60)
  Add CODEOWNERS reviewers (#61)
  Add guarded Workflows page and session capability (#48)

Signed-off-by: Clay Delk <clay.delk@gmail.com>

# Conflicts:
#	dev/relay-broker.mjs
#	src/bundled/channels/ChannelsPage.tsx
#	src/features/messages/ChannelTimeline.tsx
#	src/features/messages/MessageComposer.tsx
#	src/features/messages/MessageRow.tsx
#	src/features/messages/ThreadPanel.test.tsx
#	src/features/messages/ThreadPanel.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants