**Category:** fix
**User Impact:** Previously selected community themes are preserved when
opening a relay through onboarding, while first-time theme migration
still completes for communities with no saved theme.
**Problem:** During community initialization, desktop queried theme
history before establishing live delivery. If a replacement event
arrived while an empty history query was in flight, the client could
incorrectly treat the theme as absent and publish the default over the
user's saved selection.
**Solution:** Subscribe before fetching history, expose whether live
readiness reached EOSE, flush buffered live events before resolving
EOSE, and retain the newest delivered replacement through hydration.
Seed the inherited/default theme only when both live and history
snapshots reach EOSE with no valid or unreadable event; subscription
failures, CLOSED, readiness timeout, relay failure, and unreadable
events fail closed without publishing.
<details>
<summary>File changes</summary>
**desktop/src/shared/api/relayClientSession.ts / relayClientShared.ts /
relayClosedRecovery.ts**
Distinguish EOSE from CLOSED/timeout readiness and flush buffered events
before resolving an EOSE fence.
**desktop/src/shared/theme/CommunityThemeController.tsx**
Seed and complete first-community migration only for confirmed absence;
uncertain hydration remains non-publishing.
**desktop/src/shared/theme/communityThemePreference.ts**
Keep the inherited appearance for the first migrated community and the
stable default for later empty communities.
**desktop/src/shared/theme/communityThemeSync.ts**
Arbitrate live and history results into valid, confirmed-absent,
invalid, or unavailable hydration outcomes.
**Tests**
Cover EOSE/CLOSED readiness, subscription failure, timeout, unreadable
and live-racing events, no-op initialization, and first-to-later
community fallback isolation.
</details>
## Reproduction steps
1. Save a non-default appearance for a community relay.
2. Remove the community locally, then open the same relay again through
onboarding.
3. Arrange for the saved replacement event to arrive live while the
initial history query returns empty.
4. Confirm the saved appearance remains selected and the client does not
publish the default theme over it.
5. On an account with no theme records, open a first empty community and
confirm its inherited appearance is migrated; open a later empty
community and confirm it starts from the stable default.
## Validation
- Pre-push desktop checks, typecheck, and full desktop tests: passed at
`f79556b0e`
- Focused theme/relay readiness tests: 38 passed
- Desktop file-size ratchet and diff check: passed
---------
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
## Problem
Users on v0.5.5 report "Can't reach the relay" toggling with brief
"connected" flashes (field reports; also the macOS confirmation in
#4908). #4737 closed the stuck-reconnect gaps; this is the opposite
failure: the client redials fine, but then kills its own healthy socket.
Mechanism (all on `main`):
1. AUTH succeeds → session emits `connected`
(`relayClientSession.ts:583`), then awaits `replayLiveSubscriptions()`.
2. Paged channel backfill issues history REQs
(`relayReconnectReplay.ts`, page limit 500).
3. A `CLOSED rate-limited:` on a **history** REQ arms the rate-limit
gate but still rejects the history promise
(`relayClosedRecovery.ts:38-51`).
4. The rejection escapes `replayLiveSubscriptions()` →
`resetConnection()` tears down the authenticated socket.
5. Reconnect → AUTH OK → replay rate-limited again → loop. Each
iteration re-spends the rate-limit budget, so the loop is
self-sustaining.
## Fix
Contain backfill failures inside the replay. Each subscription's paged
backfill now retries behind the rate-limit gate up to
`PAGE_REPLAY_MAX_ATTEMPTS` (3), then degrades to live-only **for this
connection**. Socket health no longer depends on backfill success.
Nothing is lost: the replay cursor (`lastSeenCreatedAt`) only advances
on delivered events, so the next reconnect replays the same missed
window.
## Red/green proof
- Commit 1 (Pinky): e2e injecting `CLOSED rate-limited:` into the
mid-replay history REQ — **red on main** (expected 1 reconnect dial,
observed 2; connected-flash then teardown).
- Commit 2 (this fix): same test **green unchanged** — one dial, state
stays `connected` through the rate-limit hint plus the next backoff
window.
Why existing coverage missed it: the prior rate-limit e2e pre-armed the
gate *before* replay (replay politely waits), and the CLOSED-injection
test targeted a *live* subscription (which has its own retry path).
Nobody injected back-pressure from the history REQ itself.
## Verification
- `pnpm test`: 4374/4374 pass.
- `playwright test tests/e2e/relay-reconnect.spec.ts`: 14/14 pass,
including the new spec.
- `tsc --noEmit` clean; Biome clean on touched files (pre-existing
warnings on main in `personaCatalogRelay.test.mjs` / `terminal.css`
untouched).
## Not addressed here (follow-ups from the same field reports)
- AUTH terminal latch is too aggressive for relay-internal `error:`
rejections (3 strikes during a relay bad window → stuck until
click/relaunch; #4908).
- Server-side: `relay.drainJitterMs` (#4542) defaults to 0 — enabling it
on the hosted relay removes the deploy thundering herd that triggers
these rate-limit storms.
---------
Signed-off-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@buzz.block.builderlab.xyz>
Signed-off-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@buzz.block.builderlab.xyz>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
## Summary
- align the multi-community rail with the content surface and balance
its visible 10px side gutters
- center Mobile pairing, start sessions on demand, and keep retry states
inside the QR area
- reveal the QR code and copy action with 250ms motion and use the
standard loading spinner
The rail was centered within its own box, but the adjacent sidebar added
another 11px to the visible right gap. Mobile pairing also started
before user intent, which could leave an idle session waiting for EOSE.
## Validation
- `pnpm -C desktop build:e2e`
- `pnpm -C desktop test` — 3,516 passed
- `pnpm -C desktop exec playwright test tests/e2e/community-rail.spec.ts
--project=smoke` — 19 passed
- `pnpm -C desktop exec playwright test
tests/e2e/mobile-pairing-qr.spec.ts --project=smoke` — 1 passed
`pnpm -C desktop check` is currently blocked by the existing
`src-tauri/src/managed_agents/runtime.rs` file-size baseline (2,220
lines; limit 2,216).
## Summary
- Replace all references to a specific corporate VPN product name with
generic "VPN" / "corporate VPN" / "VPN tunnel" / "VPN CLI" language
across 15 files (21 lines)
- Comment-only and doc-only changes — zero functional impact
- Keeps the OSS repo free of vendor-specific assumptions
`rg -i warp` returns zero hits after this change (excluding
`node_modules`, lockfiles).