fix(desktop): kill WebSocket flood and fix Markdown <p><div> nesting#368
Merged
wesbillman merged 4 commits intomainfrom Apr 20, 2026
Merged
Conversation
… <p><div> nesting The img renderer always emits block-level markup (lightbox div or video wrapper), so any paragraph containing an image produced an invalid <p><div> tree when the paragraph also had text or links. Switch the paragraph renderer to <div> whenever any image child is present, and preserve paragraph line-height styling on the fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ription The Home-feed mention subscription fanned out one REQ per channel (~100 for heavy users) and tore down + resubscribed whenever the channels list refetched, resulting in hundreds of plugin:websocket|send IPC calls per second and an unresponsive WebView. Replace the fan-out with a single subscription filtered by #p: [currentPubkey] so the relay does the cross-channel matching. Also: - Reset reconnectDelayMs only after replayLiveSubscriptions succeeds, so a replay crash-loop properly backs off instead of retrying at 1s. - Back the global stream subscription effect on a boolean (hasLiveChannels) so channels-query refetches with identical IDs no longer churn the subscription. - Exponential backoff (1s → 30s) on mention-subscription retry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex adversarial review caught two regressions from the previous relay-flood commit: 1. Removing #h turned subscribeToMentionsForPubkey into a global subscription, and the relay intentionally does not fan out channel-scoped events to global subs (sprout-relay subscription.rs:136). New live mentions silently stopped arriving, breaking the "live forum mentions refetch home feed" integration test. Restore subscribeToChannelMentionEvents with #h. 2. Resetting reconnectDelayMs after replayLiveSubscriptions meant a single transient post-auth replay failure could inherit up to a 30s backoff instead of recovering in 1s. Restore the reset-before-replay position. Replace the per-channel fan-out tear-down+resubscribe pattern with a diff-based subscription manager: a ref-backed Map<channelId, dispose> means channels refetches with identical IDs do zero network work, and a legitimate channel add/remove only subs/unsubs the delta rather than cycling all N subs. Per-channel subscribe failures retry with exponential backoff (1s → 30s). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Integration tests "live mentions refetch the home feed" and "live forum mentions refetch the home feed" were failing because the onEvent callback for each mention sub was wrapped in an isCancelled check that closed over a local variable from the effect run that created the sub. When the effect re-ran and the sub persisted in the map (the common case — channels unchanged), the original callback was retained, but its isCancelled flag had been flipped to true by the prior effect's cleanup, so events on the long-lived sub were silently dropped. Pass handleMentionEvent directly. It is a useEffectEvent and always captures the latest state; no per-run cancellation wrapper is needed. The outer isCancelled check after subscribe resolves is still required to dispose in-flight subscribes whose effect was torn down. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fsola-sq
added a commit
that referenced
this pull request
Apr 20, 2026
…-binding * origin/main: fix(desktop): eliminate agent startup beachball (#374) fix(desktop): resolve agent command path for DMG builds (#372) fix(desktop): remove stale sprout-admin prereq, add sidecar tooling (#371) Add server cross-compile and macOS desktop build CI jobs (#369) Fix forum post card bugs on desktop and mobile (#370) fix(desktop): kill WebSocket flood and fix Markdown <p><div> nesting (#368) perf: caching, batched DM resolution, bounded audit, global kind index (#367) fix: staging to generate stubs as needed (#366) chore(deps): update rust crate axum to v0.8.9 (#365) chore(deps): update dependency @tanstack/react-router to v1.168.22 (#364) feat(desktop): autoscroll thread sidebar for new replies (#363) fix(desktop): eliminate 10+ second UI freeze on startup (#361) feat(desktop): bundle sprout-acp and sprout-mcp-server as Tauri sidecars (#362) Remove release pipeline from public repo (#360) Amp-Thread-ID: https://ampcode.com/threads/T-019dab7a-5979-7401-83a1-509b9adfe4a0 Co-authored-by: Amp <amp@ampcode.com> # Conflicts: # crates/sprout-relay/src/state.rs
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.
Summary
Two desktop bugfixes from the same debugging session, with an adversarial-review follow-up that caught regressions in the original approach.
plugin:websocket|sendIPC calls per second and saturating the Tauri WebView. Fixed with a diff-based subscription manager: a ref-backedMap<channelId, dispose>that only subs/unsubs the delta. Channels refetch with identical IDs does zero network work.<p><div>hydration error: paragraphs containing an image + text/link rendered as<p>wrapping a<div>because the renderer only switched to<div>when the paragraph had zero non-image children. Now switches whenever any image is present.Details
Relay flood fix (
src/features/channels/useLiveChannelUpdates.ts,src/shared/api/relayClientSession.ts)Diff-based mention sub manager:
mentionSubsRef: Map<channelId, dispose>persists across renders.Why not a single global sub: an earlier attempt dropped the
#hfilter for one global#p: [pubkey]subscription. Codex review and CI caught that the relay intentionally does not fan out channel-scoped events to global subs (sprout-relay/src/subscription.rs:136) — live mentions stopped arriving entirely and thelive forum mentions refetch the home feedintegration test failed. Reverted.Also:
hasLiveChannels(boolean) so channels refetches with identical IDs don't cycle it.Markdown fix (
src/shared/ui/markdown.tsx,src/shared/ui/markdownUtils.ts)hasBlockMedianow returns true whenever any image child is present.<div>fallback carriesparagraphClassNameso mixed text+media paragraphs keep their line-height.Commits
fix(desktop): render Markdown paragraphs with media as <div>…fix(desktop): collapse per-channel mention subs into one global subscription(original attempt — superseded by feat: add desktop app #3)fix(desktop): use diff-based per-channel mention sub managerTest plan
pnpm check,pnpm typecheck, all lefthook pre-push checks (desktop-build,rust-clippy,rust-tests,mobile-test, etc.) greenlive forum mentions refetch the home feed without waiting for polling(previously failing)<p><div>hydration warning in DevTools consoleplugin:websocket|sendtraffic remains low in steady state🤖 Generated with Claude Code