Skip to content

Full Outbox Implementation#1493

Open
kernelkind wants to merge 10 commits into
damus-io:masterfrom
kernelkind:outbox/impl
Open

Full Outbox Implementation#1493
kernelkind wants to merge 10 commits into
damus-io:masterfrom
kernelkind:outbox/impl

Conversation

@kernelkind

Copy link
Copy Markdown
Member

This PR moves Notedeck’s relay/outbox backend from a UI-frame-driven model to an async bridge-owned runtime.

Previously, Notedeck::tick directly advanced OutboxPool: relay receive, keepalive, NIP-11, full-history maintenance, scoped-sub realization, one-shot fetches, publish work, and NDB ingestion all shared the frame loop boundary. This PR replaces that with a remote bridge: UI code submits typed remote intents, the bridge owns outbox execution, and the UI consumes committed facts through a read model.

Also, the app now starts under an explicitly sized Tokio runtime instead of relying on #[tokio::main], so Notedeck can reserve threads deliberately: one for UI, one for the outbox bridge, a small fixed job pool for synchronous work, and the remainder for app async tasks.

What changed

  • Adds a bridge-owned async outbox runtime.

    • RemoteApi now batches scoped-sub, fetch, publish, and account-change intents.
    • RemoteBridge serializes those intents, drives OutboxService, and emits committed OutboxEvent / ScopedSubFact updates back to the UI thread.
    • RemoteOutboxReadModel becomes the UI-side source for relay status, request status, and sub EOSE state.
  • Splits outbox protocol state from outbox execution.

    • OutboxPool retains subscription/coordinator state.
    • OutboxService owns transport, websocket admission, reconnects, NIP-11, full-history, negentropy, multicast, and async capability completion.
    • Relay EVENT ingestion now crosses the service capability boundary and is ingested by NDB, avoiding app-side event deserialization.
  • Reworks scoped subscriptions around the new backend.

    • UI declarations are retained separately from bridge runtime realization.
    • Runtime owns effective scoped-sub state, account-switch restore, readiness facts, author-outbox planning, and outbox ops.
    • SubConfig now describes the intended execution mode: selected account read relays, explicit relays, selected account read plus explicit coverage, or selected account read plus author-outbox augmentation.
  • Adds author-outbox routing for eligible Columns timeline/thread subscriptions.

    • The selected account read relays remain the baseline.
    • When enabled, known author relay-list data adds remote-advertised relay coverage for author-filtered timeline work.
    • Threads can add observed relay coverage from local note data.
    • Notifications keep dedicated selected-account read behavior.
  • Moves full-history / negentropy work into the async runtime.

    • Local set building and note-presence checks run through bridge capabilities.
    • Full-history retry, pending-ingestion presence, relay failures, and negentropy output are handled inside the service/runtime path instead of the UI frame.
  • Updates transient fetches, UnknownIds, and profile fetch behavior for the new model.

    • UnknownIds still uses selected account read relays as the baseline.
    • When outbox relays are enabled, visible unknown IDs can include remote-advertised relay coverage.
    • UnknownId sends are paced and retained so large visible batches do not drop unsent IDs.
  • Adds websocket admission and user-configurable connection limits.

    • The backend enforces the configured max websocket count as a hard cap.
    • Concurrent Connecting websockets are separately bounded so high-breadth outbox usage does not overload the platform connect path.
    • Relay status facts are cleaned up so the sidebar indicator and relay view read from the same committed state.
  • Migrates app surfaces to the new remote APIs.

    • Columns timelines, threads, relay UI, settings, onboarding, profile paths, publish paths, Messages relay ensure/prefetch, Dave tests, and snapshot hosts are updated to use the bridge-backed APIs.
    • Existing Messages relay-list fallback behavior is preserved; it is not newly introduced here.

User-visible effect

Large timeline and thread loads now run through an async outbox backend instead of depending on the UI frame to advance relay work. The app can keep rendering while relay connections, NIP-11, full-history, EVENT ingestion, and scoped-sub routing progress in the bridge.

When outbox relays are enabled, timeline and thread subscriptions can use additional relay coverage from author relay lists and observed relays. When disabled, those paths stay on selected account read relays.

The relay connection limit setting is now enforced by the backend, and the relay view reflects the same committed websocket state as the sidebar indicator.

kernelkind added 10 commits July 7, 2026 17:46
Changelog-Added: Add the routed relay outbox runtime for subscriptions, fetches, publishes, and full-history sync
Changelog-Fixed: Prevent relay-list discovery from stalling when one relay never sends EOSE
Changelog-Fixed: Avoid dropping timeline notes when NDB visibility lags subscription polling
Changelog-Fixed: Keep visible profile and note lookups pending until their fetch batch is sent
Changelog-Fixed: Clear one-shot fetch relay legs as each relay finishes instead of waiting for every relay
Changelog-Changed: Show active relay inventory with stable virtualized rows
Changelog-Fixed: Prevent relay expansion from saturating websocket connection attempts
Changelog-Fixed: Fetch visible missing profiles through outbox routing with normal read priority
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 183 files, which is 33 over the limit of 150.

To get a review, narrow the scope:
• coderabbit review --type committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a59f2bc7-f853-468e-9e4a-a739a8ac077d

📥 Commits

Reviewing files that changed from the base of the PR and between e7e4819 and 6fa75de.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (183)
  • Cargo.toml
  • crates/enostr/Cargo.toml
  • crates/enostr/src/error.rs
  • crates/enostr/src/lib.rs
  • crates/enostr/src/relay/backoff.rs
  • crates/enostr/src/relay/broadcast.rs
  • crates/enostr/src/relay/compaction.rs
  • crates/enostr/src/relay/coordinator/ingest.rs
  • crates/enostr/src/relay/coordinator/mod.rs
  • crates/enostr/src/relay/coordinator/transparent_routing.rs
  • crates/enostr/src/relay/frame.rs
  • crates/enostr/src/relay/identity.rs
  • crates/enostr/src/relay/limits.rs
  • crates/enostr/src/relay/message.rs
  • crates/enostr/src/relay/mod.rs
  • crates/enostr/src/relay/multicast.rs
  • crates/enostr/src/relay/negentropy/effect.rs
  • crates/enostr/src/relay/negentropy/mod.rs
  • crates/enostr/src/relay/negentropy/relay.rs
  • crates/enostr/src/relay/negentropy/runtime.rs
  • crates/enostr/src/relay/negentropy/session.rs
  • crates/enostr/src/relay/negentropy/state.rs
  • crates/enostr/src/relay/negentropy/tests.rs
  • crates/enostr/src/relay/nip11.rs
  • crates/enostr/src/relay/outbox/admission.rs
  • crates/enostr/src/relay/outbox/eose.rs
  • crates/enostr/src/relay/outbox/fd_pressure.rs
  • crates/enostr/src/relay/outbox/full_history/fetch.rs
  • crates/enostr/src/relay/outbox/full_history/mod.rs
  • crates/enostr/src/relay/outbox/full_history/snapshot.rs
  • crates/enostr/src/relay/outbox/full_history/state.rs
  • crates/enostr/src/relay/outbox/full_history/tests.rs
  • crates/enostr/src/relay/outbox/handler.rs
  • crates/enostr/src/relay/outbox/mod.rs
  • crates/enostr/src/relay/outbox/output.rs
  • crates/enostr/src/relay/outbox/service/admission_runtime.rs
  • crates/enostr/src/relay/outbox/service/capability_runtime.rs
  • crates/enostr/src/relay/outbox/service/effect_turn.rs
  • crates/enostr/src/relay/outbox/service/full_history_runtime.rs
  • crates/enostr/src/relay/outbox/service/mod.rs
  • crates/enostr/src/relay/outbox/service/nip11.rs
  • crates/enostr/src/relay/outbox/service/relay_connection.rs
  • crates/enostr/src/relay/outbox/service/transport.rs
  • crates/enostr/src/relay/outbox/session.rs
  • crates/enostr/src/relay/queue.rs
  • crates/enostr/src/relay/subscription.rs
  • crates/enostr/src/relay/test_utils.rs
  • crates/enostr/src/relay/transparent.rs
  • crates/enostr/src/relay/websocket.rs
  • crates/enostr/src/test_support.rs
  • crates/enostr/tests/error.rs
  • crates/enostr/tests/outbox_integration.rs
  • crates/enostr_test_support/Cargo.toml
  • crates/enostr_test_support/src/lib.rs
  • crates/enostr_test_support/src/outbox.rs
  • crates/enostr_test_support/src/relay.rs
  • crates/notedeck/Cargo.toml
  • crates/notedeck/src/account/accounts.rs
  • crates/notedeck/src/account/relay.rs
  • crates/notedeck/src/app.rs
  • crates/notedeck/src/author_outbox/directory.rs
  • crates/notedeck/src/author_outbox/mod.rs
  • crates/notedeck/src/author_outbox/planner.rs
  • crates/notedeck/src/author_outbox/routing.rs
  • crates/notedeck/src/context.rs
  • crates/notedeck/src/jobs/cache.rs
  • crates/notedeck/src/jobs/job_pool.rs
  • crates/notedeck/src/jobs/media.rs
  • crates/notedeck/src/lib.rs
  • crates/notedeck/src/media/gif.rs
  • crates/notedeck/src/media/network.rs
  • crates/notedeck/src/media/static_imgs.rs
  • crates/notedeck/src/network.rs
  • crates/notedeck/src/nip05.rs
  • crates/notedeck/src/nip51_set.rs
  • crates/notedeck/src/note/context.rs
  • crates/notedeck/src/note/publish.rs
  • crates/notedeck/src/oneshot_api.rs
  • crates/notedeck/src/persist/mod.rs
  • crates/notedeck/src/persist/settings_handler.rs
  • crates/notedeck/src/private_sync.rs
  • crates/notedeck/src/publish.rs
  • crates/notedeck/src/relay_limits.rs
  • crates/notedeck/src/relayspec.rs
  • crates/notedeck/src/remote_api.rs
  • crates/notedeck/src/remote_data/mod.rs
  • crates/notedeck/src/remote_data/negentropy.rs
  • crates/notedeck/src/remote_data/outbox_read_model.rs
  • crates/notedeck/src/remote_data/remote_bridge.rs
  • crates/notedeck/src/remote_data/tests.rs
  • crates/notedeck/src/runtime.rs
  • crates/notedeck/src/scoped_sub_owners.rs
  • crates/notedeck/src/scoped_sub_state.rs
  • crates/notedeck/src/scoped_subs.rs
  • crates/notedeck/src/scoped_subs/api.rs
  • crates/notedeck/src/scoped_subs/author_index.rs
  • crates/notedeck/src/scoped_subs/author_plan/discovery.rs
  • crates/notedeck/src/scoped_subs/author_plan/mod.rs
  • crates/notedeck/src/scoped_subs/author_runtime.rs
  • crates/notedeck/src/scoped_subs/command.rs
  • crates/notedeck/src/scoped_subs/config.rs
  • crates/notedeck/src/scoped_subs/declaration_cache.rs
  • crates/notedeck/src/scoped_subs/declarations.rs
  • crates/notedeck/src/scoped_subs/fact.rs
  • crates/notedeck/src/scoped_subs/live/mod.rs
  • crates/notedeck/src/scoped_subs/mod.rs
  • crates/notedeck/src/scoped_subs/outbox.rs
  • crates/notedeck/src/scoped_subs/owner_declarations.rs
  • crates/notedeck/src/scoped_subs/planner.rs
  • crates/notedeck/src/scoped_subs/realized.rs
  • crates/notedeck/src/scoped_subs/route_work.rs
  • crates/notedeck/src/scoped_subs/runtime.rs
  • crates/notedeck/src/scoped_subs/state.rs
  • crates/notedeck/src/scoped_subs/store.rs
  • crates/notedeck/src/scoped_subs/tests.rs
  • crates/notedeck/src/scoped_subs/transition.rs
  • crates/notedeck/src/test_utils.rs
  • crates/notedeck/src/unknowns.rs
  • crates/notedeck/tests/runtime_and_settings.rs
  • crates/notedeck/tests/update_install.rs
  • crates/notedeck_chrome/src/android.rs
  • crates/notedeck_chrome/src/chrome.rs
  • crates/notedeck_chrome/src/notedeck.rs
  • crates/notedeck_chrome/src/preview.rs
  • crates/notedeck_chrome/tests/ui_tests.rs
  • crates/notedeck_columns/src/accounts/mod.rs
  • crates/notedeck_columns/src/actionbar.rs
  • crates/notedeck_columns/src/app.rs
  • crates/notedeck_columns/src/column.rs
  • crates/notedeck_columns/src/decks.rs
  • crates/notedeck_columns/src/nav.rs
  • crates/notedeck_columns/src/onboarding.rs
  • crates/notedeck_columns/src/profile.rs
  • crates/notedeck_columns/src/repost.rs
  • crates/notedeck_columns/src/route.rs
  • crates/notedeck_columns/src/scoped_sub_owner_keys.rs
  • crates/notedeck_columns/src/test_data.rs
  • crates/notedeck_columns/src/timeline/cache.rs
  • crates/notedeck_columns/src/timeline/mod.rs
  • crates/notedeck_columns/src/timeline/note_units.rs
  • crates/notedeck_columns/src/timeline/route.rs
  • crates/notedeck_columns/src/timeline/sub/mod.rs
  • crates/notedeck_columns/src/timeline/sub/thread_sub.rs
  • crates/notedeck_columns/src/timeline/sub/timeline_remote.rs
  • crates/notedeck_columns/src/timeline/sub/timeline_sub.rs
  • crates/notedeck_columns/src/timeline/thread.rs
  • crates/notedeck_columns/src/timeline/timeline_units.rs
  • crates/notedeck_columns/src/timeline_loader.rs
  • crates/notedeck_columns/src/toolbar.rs
  • crates/notedeck_columns/src/ui/add_column.rs
  • crates/notedeck_columns/src/ui/note/post.rs
  • crates/notedeck_columns/src/ui/profile/mod.rs
  • crates/notedeck_columns/src/ui/relay.rs
  • crates/notedeck_columns/src/ui/settings.rs
  • crates/notedeck_columns/src/ui/side_panel.rs
  • crates/notedeck_columns/src/ui/thread.rs
  • crates/notedeck_columns/src/ui/timeline.rs
  • crates/notedeck_columns/src/view_state.rs
  • crates/notedeck_columns/tests/columns_e2e.rs
  • crates/notedeck_dashboard/tests/snapshot_tests.rs
  • crates/notedeck_dave/src/lib.rs
  • crates/notedeck_dave/tests/pns_outbox_e2e.rs
  • crates/notedeck_headway/src/lib.rs
  • crates/notedeck_headway/tests/snapshot_tests.rs
  • crates/notedeck_horizon/tests/snapshot_tests.rs
  • crates/notedeck_messages/Cargo.toml
  • crates/notedeck_messages/src/cache/conversation.rs
  • crates/notedeck_messages/src/lib.rs
  • crates/notedeck_messages/src/relay_ensure.rs
  • crates/notedeck_messages/src/relay_prefetch.rs
  • crates/notedeck_messages/tests/harness/fixtures.rs
  • crates/notedeck_messages/tests/harness/mod.rs
  • crates/notedeck_messages/tests/messages_e2e.rs
  • crates/notedeck_messages/tests/relay_delivery.rs
  • crates/notedeck_nostrverse/src/lib.rs
  • crates/notedeck_notebook/src/lib.rs
  • crates/notedeck_notebook/tests/snapshot_tests.rs
  • crates/notedeck_testing/src/device.rs
  • crates/notedeck_testing/src/fixtures.rs
  • crates/notedeck_testing/src/ndb.rs
  • crates/notedeck_testing/src/negentropy_relay.rs
  • crates/notedeck_ui/src/note/media.rs
  • crates/notedeck_ui/src/note/mod.rs

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

1 participant