went live with new editor

Orual 44a27928 0c330aaa

+72 -31
+1
.gitignore
··· 9 .env 10 .devenv 11 CLAUDE.md 12 grant_proposal.md 13 14 **/**.db
··· 9 .env 10 .devenv 11 CLAUDE.md 12 + AGENTS.md 13 grant_proposal.md 14 15 **/**.db
+10
crates/weaver-app/.env-example
···
··· 1 + WEAVER_APP_ENV="dev" 2 + WEAVER_APP_HOST="http://localhost" 3 + WEAVER_APP_DOMAIN="" 4 + WEAVER_PORT=8080 5 + WEAVER_APP_SCOPES="atproto transition:generic" 6 + WEAVER_CLIENT_NAME="Weaver" 7 + 8 + WEAVER_LOGO_URI="" 9 + WEAVER_TOS_URI="" 10 + WEAVER_PRIVACY_POLICY_URI=""
-10
crates/weaver-app/.env-prod
··· 1 - WEAVER_APP_ENV="prod" 2 - WEAVER_APP_HOST="https://alpha.weaver.sh" 3 - WEAVER_APP_DOMAIN="https://alpha.weaver.sh" 4 - WEAVER_PORT=8080 5 - WEAVER_APP_SCOPES="atproto transition:generic" 6 - WEAVER_CLIENT_NAME="Weaver" 7 - 8 - WEAVER_LOGO_URI="https://alpha.weaver.sh/favicon.ico" 9 - WEAVER_TOS_URI="" 10 - WEAVER_PRIVACY_POLICY_URI=""
···
+27 -2
crates/weaver-app/src/components/editor/component.rs
··· 3 use dioxus::prelude::*; 4 use jacquard::IntoStatic; 5 use jacquard::cowstr::ToCowStr; 6 use jacquard::types::blob::BlobRef; 7 use jacquard::types::ident::AtIdentifier; 8 use weaver_api::sh_weaver::embed::images::Image; ··· 88 Ok(None) => { 89 // No existing state - check if we need to load entry content 90 if let Some(ref uri) = entry_uri { 91 // Try to load the entry content from PDS 92 match load_entry_for_editing(&fetcher, uri).await { 93 Ok(loaded) => { ··· 114 entry_ref: Some(loaded.entry_ref), 115 edit_root: None, 116 last_diff: None, 117 - is_synced: false, 118 }); 119 } 120 Err(e) => { ··· 137 entry_ref: None, 138 edit_root: None, 139 last_diff: None, 140 - is_synced: false, 141 }) 142 } 143 Err(e) => {
··· 3 use dioxus::prelude::*; 4 use jacquard::IntoStatic; 5 use jacquard::cowstr::ToCowStr; 6 + use jacquard::identity::resolver::IdentityResolver; 7 use jacquard::types::blob::BlobRef; 8 use jacquard::types::ident::AtIdentifier; 9 use weaver_api::sh_weaver::embed::images::Image; ··· 89 Ok(None) => { 90 // No existing state - check if we need to load entry content 91 if let Some(ref uri) = entry_uri { 92 + // Check that this entry belongs to the current user 93 + if let Some(current_did) = fetcher.current_did().await { 94 + let entry_authority = uri.authority(); 95 + let is_own_entry = match entry_authority { 96 + AtIdentifier::Did(did) => did == &current_did, 97 + AtIdentifier::Handle(handle) => { 98 + // Resolve handle to DID and compare 99 + match fetcher.client.resolve_handle(handle).await { 100 + Ok(resolved_did) => resolved_did == current_did, 101 + Err(_) => false, 102 + } 103 + } 104 + }; 105 + if !is_own_entry { 106 + tracing::warn!( 107 + "Cannot edit entry belonging to another user: {}", 108 + entry_authority 109 + ); 110 + return LoadResult::Failed( 111 + "You can only edit your own entries".to_string() 112 + ); 113 + } 114 + } 115 + 116 // Try to load the entry content from PDS 117 match load_entry_for_editing(&fetcher, uri).await { 118 Ok(loaded) => { ··· 139 entry_ref: Some(loaded.entry_ref), 140 edit_root: None, 141 last_diff: None, 142 + synced_version: None, // Fresh from entry, never synced 143 }); 144 } 145 Err(e) => { ··· 162 entry_ref: None, 163 edit_root: None, 164 last_diff: None, 165 + synced_version: None, // New doc, never synced 166 }) 167 } 168 Err(e) => {
+6 -9
crates/weaver-app/src/components/editor/document.rs
··· 212 pub edit_root: Option<StrongRef<'static>>, 213 /// StrongRef to the most recent sh.weaver.edit.diff record. 214 pub last_diff: Option<StrongRef<'static>>, 215 - /// Whether the current doc state is synced with PDS. 216 - /// False if local has changes not yet pushed to PDS. 217 - pub is_synced: bool, 218 } 219 220 impl PartialEq for LoadedDocState { ··· 993 }; 994 let loro_cursor = content.get_cursor(cursor_offset, Side::default()); 995 996 - // Track sync state - if synced, record current version 997 - let last_synced_version = if state.is_synced { 998 - Some(doc.oplog_vv()) 999 - } else { 1000 - None 1001 - }; 1002 1003 Self { 1004 doc,
··· 212 pub edit_root: Option<StrongRef<'static>>, 213 /// StrongRef to the most recent sh.weaver.edit.diff record. 214 pub last_diff: Option<StrongRef<'static>>, 215 + /// Version vector of the last known PDS state. 216 + /// Used to determine what changes need to be synced. 217 + /// None if never synced to PDS. 218 + pub synced_version: Option<VersionVector>, 219 } 220 221 impl PartialEq for LoadedDocState { ··· 994 }; 995 let loro_cursor = content.get_cursor(cursor_offset, Side::default()); 996 997 + // Use the synced version from state (tracks the PDS version vector) 998 + let last_synced_version = state.synced_version; 999 1000 Self { 1001 doc,
+21 -3
crates/weaver-app/src/components/editor/sync.rs
··· 671 entry_ref: local.entry_ref, // Restored from localStorage 672 edit_root: None, 673 last_diff: None, 674 - is_synced: false, // Local-only, not synced to PDS 675 })) 676 } 677 ··· 692 } 693 } 694 695 Ok(Some(LoadedDocState { 696 doc, 697 entry_ref: None, // Entry ref comes from the entry itself, not edit state 698 edit_root: Some(pds.root_ref), 699 last_diff: pds.last_diff_ref, 700 - is_synced: true, // Just loaded from PDS, fully synced 701 })) 702 } 703 ··· 705 // Both exist - merge using CRDT 706 tracing::debug!("Merging document from localStorage and PDS"); 707 708 let doc = LoroDoc::new(); 709 710 // Import local snapshot first ··· 724 } 725 } 726 727 Ok(Some(LoadedDocState { 728 doc, 729 entry_ref: local.entry_ref, // Restored from localStorage 730 edit_root: Some(pds.root_ref), 731 last_diff: pds.last_diff_ref, 732 - is_synced: false, // Local had state, may have unsynced changes 733 })) 734 } 735 }
··· 671 entry_ref: local.entry_ref, // Restored from localStorage 672 edit_root: None, 673 last_diff: None, 674 + synced_version: None, // Local-only, never synced to PDS 675 })) 676 } 677 ··· 692 } 693 } 694 695 + // Capture the version after loading all PDS state - this is our sync baseline 696 + let synced_version = Some(doc.oplog_vv()); 697 + 698 Ok(Some(LoadedDocState { 699 doc, 700 entry_ref: None, // Entry ref comes from the entry itself, not edit state 701 edit_root: Some(pds.root_ref), 702 last_diff: pds.last_diff_ref, 703 + synced_version, // Just loaded from PDS, fully synced 704 })) 705 } 706 ··· 708 // Both exist - merge using CRDT 709 tracing::debug!("Merging document from localStorage and PDS"); 710 711 + // First, reconstruct the PDS state to get its version vector 712 + let pds_doc = LoroDoc::new(); 713 + if let Err(e) = pds_doc.import(&pds.root_snapshot) { 714 + tracing::warn!("Failed to import PDS root snapshot for VV: {:?}", e); 715 + } 716 + for updates in &pds.diff_updates { 717 + if let Err(e) = pds_doc.import(updates) { 718 + tracing::warn!("Failed to apply PDS diff for VV: {:?}", e); 719 + } 720 + } 721 + let pds_version = pds_doc.oplog_vv(); 722 + 723 + // Now create the merged doc 724 let doc = LoroDoc::new(); 725 726 // Import local snapshot first ··· 740 } 741 } 742 743 + // Use the PDS version as our sync baseline - any local changes 744 + // beyond this will be detected as unsynced 745 Ok(Some(LoadedDocState { 746 doc, 747 entry_ref: local.entry_ref, // Restored from localStorage 748 edit_root: Some(pds.root_ref), 749 last_diff: pds.last_diff_ref, 750 + synced_version: Some(pds_version), 751 })) 752 } 753 }
+4 -4
crates/weaver-app/src/env.rs
··· 1 // This file is automatically generated by build.rs 2 3 #[allow(unused)] 4 - pub const WEAVER_APP_ENV: &'static str = "dev"; 5 #[allow(unused)] 6 - pub const WEAVER_APP_HOST: &'static str = "http://localhost"; 7 #[allow(unused)] 8 - pub const WEAVER_APP_DOMAIN: &'static str = ""; 9 #[allow(unused)] 10 pub const WEAVER_PORT: &'static str = "8080"; 11 #[allow(unused)] ··· 13 #[allow(unused)] 14 pub const WEAVER_CLIENT_NAME: &'static str = "Weaver"; 15 #[allow(unused)] 16 - pub const WEAVER_LOGO_URI: &'static str = ""; 17 #[allow(unused)] 18 pub const WEAVER_TOS_URI: &'static str = ""; 19 #[allow(unused)]
··· 1 // This file is automatically generated by build.rs 2 3 #[allow(unused)] 4 + pub const WEAVER_APP_ENV: &'static str = "prod"; 5 #[allow(unused)] 6 + pub const WEAVER_APP_HOST: &'static str = "https://alpha.weaver.sh"; 7 #[allow(unused)] 8 + pub const WEAVER_APP_DOMAIN: &'static str = "https://alpha.weaver.sh"; 9 #[allow(unused)] 10 pub const WEAVER_PORT: &'static str = "8080"; 11 #[allow(unused)] ··· 13 #[allow(unused)] 14 pub const WEAVER_CLIENT_NAME: &'static str = "Weaver"; 15 #[allow(unused)] 16 + pub const WEAVER_LOGO_URI: &'static str = "https://alpha.weaver.sh/favicon.ico"; 17 #[allow(unused)] 18 pub const WEAVER_TOS_URI: &'static str = ""; 19 #[allow(unused)]
+3 -3
crates/weaver-cli/src/main.rs
··· 388 // Use WeaverExt to upsert entry (handles notebook + entry creation/updates) 389 use jacquard::http_client::HttpClient; 390 use weaver_common::WeaverExt; 391 - let (entry_uri, was_created) = agent 392 .upsert_entry(&title, entry_title.as_ref(), entry) 393 .await?; 394 395 if was_created { 396 - println!(" ✓ Created new entry: {}", entry_uri.as_ref()); 397 } else { 398 - println!(" ✓ Updated existing entry: {}", entry_uri.as_ref()); 399 } 400 } 401
··· 388 // Use WeaverExt to upsert entry (handles notebook + entry creation/updates) 389 use jacquard::http_client::HttpClient; 390 use weaver_common::WeaverExt; 391 + let (entry_ref, was_created) = agent 392 .upsert_entry(&title, entry_title.as_ref(), entry) 393 .await?; 394 395 if was_created { 396 + println!(" ✓ Created new entry: {}", entry_ref.uri.as_ref()); 397 } else { 398 + println!(" ✓ Updated existing entry: {}", entry_ref.uri.as_ref()); 399 } 400 } 401