went live with new editor

Orual 44a27928 0c330aaa

+72 -31
+1
.gitignore
··· 9 9 .env 10 10 .devenv 11 11 CLAUDE.md 12 + AGENTS.md 12 13 grant_proposal.md 13 14 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 3 use dioxus::prelude::*; 4 4 use jacquard::IntoStatic; 5 5 use jacquard::cowstr::ToCowStr; 6 + use jacquard::identity::resolver::IdentityResolver; 6 7 use jacquard::types::blob::BlobRef; 7 8 use jacquard::types::ident::AtIdentifier; 8 9 use weaver_api::sh_weaver::embed::images::Image; ··· 88 89 Ok(None) => { 89 90 // No existing state - check if we need to load entry content 90 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 + 91 116 // Try to load the entry content from PDS 92 117 match load_entry_for_editing(&fetcher, uri).await { 93 118 Ok(loaded) => { ··· 114 139 entry_ref: Some(loaded.entry_ref), 115 140 edit_root: None, 116 141 last_diff: None, 117 - is_synced: false, 142 + synced_version: None, // Fresh from entry, never synced 118 143 }); 119 144 } 120 145 Err(e) => { ··· 137 162 entry_ref: None, 138 163 edit_root: None, 139 164 last_diff: None, 140 - is_synced: false, 165 + synced_version: None, // New doc, never synced 141 166 }) 142 167 } 143 168 Err(e) => {
+6 -9
crates/weaver-app/src/components/editor/document.rs
··· 212 212 pub edit_root: Option<StrongRef<'static>>, 213 213 /// StrongRef to the most recent sh.weaver.edit.diff record. 214 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, 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>, 218 219 } 219 220 220 221 impl PartialEq for LoadedDocState { ··· 993 994 }; 994 995 let loro_cursor = content.get_cursor(cursor_offset, Side::default()); 995 996 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 - }; 997 + // Use the synced version from state (tracks the PDS version vector) 998 + let last_synced_version = state.synced_version; 1002 999 1003 1000 Self { 1004 1001 doc,
+21 -3
crates/weaver-app/src/components/editor/sync.rs
··· 671 671 entry_ref: local.entry_ref, // Restored from localStorage 672 672 edit_root: None, 673 673 last_diff: None, 674 - is_synced: false, // Local-only, not synced to PDS 674 + synced_version: None, // Local-only, never synced to PDS 675 675 })) 676 676 } 677 677 ··· 692 692 } 693 693 } 694 694 695 + // Capture the version after loading all PDS state - this is our sync baseline 696 + let synced_version = Some(doc.oplog_vv()); 697 + 695 698 Ok(Some(LoadedDocState { 696 699 doc, 697 700 entry_ref: None, // Entry ref comes from the entry itself, not edit state 698 701 edit_root: Some(pds.root_ref), 699 702 last_diff: pds.last_diff_ref, 700 - is_synced: true, // Just loaded from PDS, fully synced 703 + synced_version, // Just loaded from PDS, fully synced 701 704 })) 702 705 } 703 706 ··· 705 708 // Both exist - merge using CRDT 706 709 tracing::debug!("Merging document from localStorage and PDS"); 707 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 708 724 let doc = LoroDoc::new(); 709 725 710 726 // Import local snapshot first ··· 724 740 } 725 741 } 726 742 743 + // Use the PDS version as our sync baseline - any local changes 744 + // beyond this will be detected as unsynced 727 745 Ok(Some(LoadedDocState { 728 746 doc, 729 747 entry_ref: local.entry_ref, // Restored from localStorage 730 748 edit_root: Some(pds.root_ref), 731 749 last_diff: pds.last_diff_ref, 732 - is_synced: false, // Local had state, may have unsynced changes 750 + synced_version: Some(pds_version), 733 751 })) 734 752 } 735 753 }
+4 -4
crates/weaver-app/src/env.rs
··· 1 1 // This file is automatically generated by build.rs 2 2 3 3 #[allow(unused)] 4 - pub const WEAVER_APP_ENV: &'static str = "dev"; 4 + pub const WEAVER_APP_ENV: &'static str = "prod"; 5 5 #[allow(unused)] 6 - pub const WEAVER_APP_HOST: &'static str = "http://localhost"; 6 + pub const WEAVER_APP_HOST: &'static str = "https://alpha.weaver.sh"; 7 7 #[allow(unused)] 8 - pub const WEAVER_APP_DOMAIN: &'static str = ""; 8 + pub const WEAVER_APP_DOMAIN: &'static str = "https://alpha.weaver.sh"; 9 9 #[allow(unused)] 10 10 pub const WEAVER_PORT: &'static str = "8080"; 11 11 #[allow(unused)] ··· 13 13 #[allow(unused)] 14 14 pub const WEAVER_CLIENT_NAME: &'static str = "Weaver"; 15 15 #[allow(unused)] 16 - pub const WEAVER_LOGO_URI: &'static str = ""; 16 + pub const WEAVER_LOGO_URI: &'static str = "https://alpha.weaver.sh/favicon.ico"; 17 17 #[allow(unused)] 18 18 pub const WEAVER_TOS_URI: &'static str = ""; 19 19 #[allow(unused)]
+3 -3
crates/weaver-cli/src/main.rs
··· 388 388 // Use WeaverExt to upsert entry (handles notebook + entry creation/updates) 389 389 use jacquard::http_client::HttpClient; 390 390 use weaver_common::WeaverExt; 391 - let (entry_uri, was_created) = agent 391 + let (entry_ref, was_created) = agent 392 392 .upsert_entry(&title, entry_title.as_ref(), entry) 393 393 .await?; 394 394 395 395 if was_created { 396 - println!(" ✓ Created new entry: {}", entry_uri.as_ref()); 396 + println!(" ✓ Created new entry: {}", entry_ref.uri.as_ref()); 397 397 } else { 398 - println!(" ✓ Updated existing entry: {}", entry_uri.as_ref()); 398 + println!(" ✓ Updated existing entry: {}", entry_ref.uri.as_ref()); 399 399 } 400 400 } 401 401