···6#[cfg(all(target_family = "wasm", target_os = "unknown"))]
7fn main() {
8 console_error_panic_hook::set_once();
9+ use tracing::Level;
10+ use tracing::subscriber::set_global_default;
11+ use tracing_subscriber::Registry;
12+ use tracing_subscriber::filter::EnvFilter;
13+ use tracing_subscriber::layer::SubscriberExt;
14+15+ let console_level = if cfg!(debug_assertions) {
16+ Level::DEBUG
17+ } else {
18+ Level::DEBUG
19+ };
20+21+ let wasm_layer = tracing_wasm::WASMLayer::new(
22+ tracing_wasm::WASMLayerConfigBuilder::new()
23+ .set_max_level(console_level)
24+ .build(),
25+ );
26+27+ // Filter out noisy crates
28+ let filter = EnvFilter::new(
29+ "debug,loro_internal=warn,jacquard_identity=info,jacquard_common=info,iroh=info",
30+ );
31+32+ let reg = Registry::default().with(filter).with(wasm_layer);
33+34+ let _ = set_global_default(reg);
3536 use gloo_worker::Registrable;
37+ use weaver_app::components::editor::EditorReactor;
3839+ EditorReactor::registrar().register();
40}
4142#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
+4-95
crates/weaver-app/src/collab_context.rs
···1-//! Real-time collaboration context for P2P editing sessions.
2-//!
3-//! This module provides the CollabNode as a Dioxus context, allowing editor
4-//! components to join gossip sessions for real-time collaboration.
5//!
6-//! The CollabNode is only active in WASM builds where iroh works via relays.
078use dioxus::prelude::*;
9-use std::sync::Arc;
10-use weaver_common::transport::CollabNode;
1112/// Debug state for the collab session, displayed in editor debug panel.
13#[derive(Clone, Default)]
···28 pub last_error: Option<String>,
29}
3031-/// Context state for the collaboration node.
32-///
33-/// This is provided as a Dioxus context and can be accessed by editor components
34-/// to join/leave collaborative editing sessions.
35-#[derive(Clone)]
36-pub struct CollabContext {
37- /// The collaboration node, if successfully spawned.
38- /// None while loading or if spawn failed.
39- pub node: Option<Arc<CollabNode>>,
40- /// Error message if spawn failed.
41- pub error: Option<String>,
42-}
43-44-impl Default for CollabContext {
45- fn default() -> Self {
46- Self {
47- node: None,
48- error: None,
49- }
50- }
51-}
52-53-/// Provider component that spawns the CollabNode and provides it as context.
54-///
55-/// Should be placed near the root of the app, wrapping any components that
56-/// need access to real-time collaboration.
57-#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
58-#[component]
59-pub fn CollabProvider(children: Element) -> Element {
60- let mut collab_ctx = use_signal(CollabContext::default);
61- let debug_state = use_signal(CollabDebugState::default);
62-63- // Spawn the CollabNode on mount
64- let _spawn_result = use_resource(move || async move {
65- tracing::info!("Spawning CollabNode...");
66-67- match CollabNode::spawn(None).await {
68- Ok(node) => {
69- tracing::info!(node_id = %node.node_id_string(), "CollabNode spawned");
70- collab_ctx.set(CollabContext {
71- node: Some(node),
72- error: None,
73- });
74- }
75- Err(e) => {
76- tracing::error!("Failed to spawn CollabNode: {}", e);
77- collab_ctx.set(CollabContext {
78- node: None,
79- error: Some(e.to_string()),
80- });
81- }
82- }
83- });
84-85- // Provide the contexts
86- use_context_provider(|| collab_ctx);
87- use_context_provider(|| debug_state);
88-89- rsx! { {children} }
90-}
91-92-/// No-op provider for non-WASM builds.
93-#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
94-#[component]
95-pub fn CollabProvider(children: Element) -> Element {
96- // On server/native, provide an empty context (collab happens in browser)
97- let collab_ctx = use_signal(CollabContext::default);
98- let debug_state = use_signal(CollabDebugState::default);
99- use_context_provider(|| collab_ctx);
100- use_context_provider(|| debug_state);
101- rsx! { {children} }
102-}
103-104-/// Hook to get the CollabNode from context.
105-///
106-/// Returns None if the node hasn't spawned yet or failed to spawn.
107-pub fn use_collab_node() -> Option<Arc<CollabNode>> {
108- let ctx = use_context::<Signal<CollabContext>>();
109- ctx.read().node.clone()
110-}
111-112-/// Hook to check if collab is available.
113-pub fn use_collab_available() -> bool {
114- let ctx = use_context::<Signal<CollabContext>>();
115- ctx.read().node.is_some()
116-}
117-118/// Hook to get the collab debug state signal.
119-/// Returns None if called outside CollabProvider.
120pub fn try_use_collab_debug() -> Option<Signal<CollabDebugState>> {
121 try_use_context::<Signal<CollabDebugState>>()
122}
···1+//! Real-time collaboration debug state.
0002//!
3+//! This module provides CollabDebugState which is set as context by
4+//! the CollabCoordinator component for display in the editor debug panel.
56use dioxus::prelude::*;
0078/// Debug state for the collab session, displayed in editor debug panel.
9#[derive(Clone, Default)]
···24 pub last_error: Option<String>,
25}
2600000000000000000000000000000000000000000000000000000000000000000000000000000000000000027/// Hook to get the collab debug state signal.
28+/// Returns None if called outside CollabCoordinator.
29pub fn try_use_collab_debug() -> Option<Signal<CollabDebugState>> {
30 try_use_context::<Signal<CollabDebugState>>()
31}
···10// Re-export jacquard for convenience
11pub use agent::{SessionPeer, WeaverExt};
12pub use error::WeaverError;
00013pub use resolve::{EntryIndex, ExtractedRef, RefCollector, ResolvedContent, ResolvedEntry};
1415pub use jacquard;
···10// Re-export jacquard for convenience
11pub use agent::{SessionPeer, WeaverExt};
12pub use error::WeaverError;
13+14+// Re-export blake3 for topic hashing
15+pub use blake3;
16pub use resolve::{EntryIndex, ExtractedRef, RefCollector, ResolvedContent, ResolvedEntry};
1718pub use jacquard;
+1-1
crates/weaver-common/src/transport/presence.rs
···38}
3940/// Tracks all collaborators in a session.
41-#[derive(Debug, Default)]
42pub struct PresenceTracker {
43 /// Collaborators by EndpointId.
44 collaborators: HashMap<EndpointId, Collaborator>,
···38}
3940/// Tracks all collaborators in a session.
41+#[derive(Debug, Default, Clone)]
42pub struct PresenceTracker {
43 /// Collaborators by EndpointId.
44 collaborators: HashMap<EndpointId, Collaborator>,
+14-6
crates/weaver-common/src/transport/session.rs
···92 }
9394 // Subscribe to the gossip topic
95- let (sender, receiver) = node
96- .gossip()
97- .subscribe_and_join(topic, bootstrap_peers)
98- .await
99- .map_err(|e| SessionError::Subscribe(Box::new(e)))?
100- .split();
00000000101102 tracing::info!("CollabSession: subscribed to gossip topic");
103
···92 }
9394 // Subscribe to the gossip topic
95+ // Use subscribe (non-blocking) if no bootstrap peers, otherwise subscribe_and_join
96+ let (sender, receiver) = if bootstrap_peers.is_empty() {
97+ node.gossip()
98+ .subscribe(topic, vec![])
99+ .await
100+ .map_err(|e| SessionError::Subscribe(Box::new(e)))?
101+ .split()
102+ } else {
103+ node.gossip()
104+ .subscribe_and_join(topic, bootstrap_peers)
105+ .await
106+ .map_err(|e| SessionError::Subscribe(Box::new(e)))?
107+ .split()
108+ };
109110 tracing::info!("CollabSession: subscribed to gossip topic");
111