···141141 tags.push(tag_str).ok();
142142 }
143143 }
144144+145145+ // Restore existing embeds from the entry
146146+ if let Some(ref embeds) = loaded.entry.embeds {
147147+ let embeds_map = doc.get_map("embeds");
148148+149149+ // Restore images
150150+ if let Some(ref images) = embeds.images {
151151+ let images_list = embeds_map
152152+ .get_or_create_container("images", loro::LoroList::new())
153153+ .expect("images list");
154154+ for image in &images.images {
155155+ // Serialize image to JSON and add to list
156156+ // No publishedBlobUri since these are already published
157157+ let json = serde_json::to_value(image)
158158+ .expect("Image serializes");
159159+ images_list.push(json).ok();
160160+ }
161161+ }
162162+163163+ // Restore record embeds
164164+ if let Some(ref records) = embeds.records {
165165+ let records_list = embeds_map
166166+ .get_or_create_container("records", loro::LoroList::new())
167167+ .expect("records list");
168168+ for record in &records.records {
169169+ let json = serde_json::to_value(record)
170170+ .expect("RecordEmbed serializes");
171171+ records_list.push(json).ok();
172172+ }
173173+ }
174174+ }
175175+144176 doc.commit();
145177146178 return LoadResult::Loaded(LoadedDocState {
···233265 let auth_state = use_context::<Signal<AuthState>>();
234266235267 let mut document = use_hook(|| {
236236- let doc = EditorDocument::from_loaded_state(loaded_state.clone());
268268+ let mut doc = EditorDocument::from_loaded_state(loaded_state.clone());
269269+270270+ // Seed collected_refs with existing record embeds so they get fetched/rendered
271271+ let record_embeds = doc.record_embeds();
272272+ if !record_embeds.is_empty() {
273273+ let refs: Vec<weaver_common::ExtractedRef> = record_embeds
274274+ .into_iter()
275275+ .filter_map(|embed| {
276276+ embed.name.map(|name| weaver_common::ExtractedRef::AtEmbed {
277277+ uri: name.to_string(),
278278+ alt_text: None,
279279+ })
280280+ })
281281+ .collect();
282282+ doc.set_collected_refs(refs);
283283+ }
284284+237285 storage::save_to_storage(&doc, &draft_key).ok();
238286 doc
239287 });
240288 let editor_id = "markdown-editor";
241289 let mut render_cache = use_signal(|| render::RenderCache::default());
242242- let mut image_resolver = use_signal(EditorImageResolver::default);
290290+291291+ // Populate resolver from existing images if editing a published entry
292292+ let mut image_resolver: Signal<EditorImageResolver> = use_signal(|| {
293293+ let images = document.images();
294294+ if let (false, Some(ref r)) = (images.is_empty(), document.entry_ref()) {
295295+ let ident = r.uri.authority().clone().into_static();
296296+ let entry_rkey = r.uri.rkey().map(|rk| rk.0.clone().into_static());
297297+ EditorImageResolver::from_images(&images, ident, entry_rkey)
298298+ } else {
299299+ EditorImageResolver::default()
300300+ }
301301+ });
243302 let resolved_content = use_signal(weaver_common::ResolvedContent::default);
244303245304 let doc_for_memo = document.clone();
···10951154 } else {
10961155 uploaded.alt.clone()
10971156 };
10981098- let markdown = format!("", alt_text, name);
11571157+11581158+ // Check if authenticated and get DID for draft path
11591159+ let auth = auth_state.read();
11601160+ let did_for_path = auth.did.clone();
11611161+ let is_authenticated = auth.is_authenticated();
11621162+ drop(auth);
11631163+11641164+ // Pre-generate TID for the blob rkey (used in draft path and upload)
11651165+ let blob_tid = jacquard::types::tid::Ticker::new().next(None);
11661166+11671167+ // Build markdown with proper draft path if authenticated
11681168+ let markdown = if let Some(ref did) = did_for_path {
11691169+ format!("", alt_text, did, blob_tid.as_str(), name)
11701170+ } else {
11711171+ // Fallback for unauthenticated - simple path (won't be publishable anyway)
11721172+ format!("", alt_text, name)
11731173+ };
1099117411001175 let pos = doc.cursor.read().offset;
11011176 let _ = doc.insert_tracked(pos, &markdown);
11021177 doc.cursor.write().offset = pos + markdown.chars().count();
1103117811041179 // Upload to PDS in background if authenticated
11051105- let is_authenticated = auth_state.read().is_authenticated();
11061180 if is_authenticated {
11071181 let fetcher = fetcher.clone();
11081182 let name_for_upload = name.clone();
···11161190 // Clone data for cache pre-warming
11171191 let data_for_cache = data.clone();
1118119211931193+ // Use pre-generated TID as rkey for the blob record
11941194+ let rkey = jacquard::types::recordkey::RecordKey::any(blob_tid.as_str())
11951195+ .expect("TID is valid record key");
11961196+11191197 // Upload blob and create temporary PublishedBlob record
11201120- match client.publish_blob(data, &name_for_upload, None).await {
11981198+ match client.publish_blob(data, &name_for_upload, Some(rkey)).await {
11211199 Ok((strong_ref, published_blob)) => {
11221200 // Get DID from fetcher
11231201 let did = match fetcher.current_did().await {
···2626use jacquard::types::string::AtUri;
2727use weaver_api::com_atproto::repo::strong_ref::StrongRef;
2828use weaver_api::sh_weaver::embed::images::Image;
2929+use weaver_api::sh_weaver::embed::records::RecordEmbed;
2930use weaver_api::sh_weaver::notebook::entry::Entry;
30313132/// Helper for working with editor images.
···612613 }
613614 }
614615 }
616616+ }
617617+618618+ // --- Record embed methods ---
619619+620620+ /// Get the records LoroList from embeds, creating it if needed.
621621+ fn get_records_list(&self) -> LoroList {
622622+ self.embeds
623623+ .get_or_create_container("records", LoroList::new())
624624+ .unwrap()
625625+ }
626626+627627+ /// Get all record embeds as a Vec.
628628+ pub fn record_embeds(&self) -> Vec<RecordEmbed<'static>> {
629629+ let records_list = self.get_records_list();
630630+ let mut result = Vec::new();
631631+632632+ for i in 0..records_list.len() {
633633+ if let Some(record_embed) = self.loro_value_to_record_embed(&records_list, i) {
634634+ result.push(record_embed);
635635+ }
636636+ }
637637+638638+ result
639639+ }
640640+641641+ /// Convert a LoroValue at the given index to a RecordEmbed.
642642+ fn loro_value_to_record_embed(&self, list: &LoroList, index: usize) -> Option<RecordEmbed<'static>> {
643643+ let value = list.get(index)?;
644644+ let loro_value = value.as_value()?;
645645+ let json = loro_value.to_json_value();
646646+ from_json_value::<RecordEmbed>(json).ok().map(|r| r.into_static())
615647 }
616648617649 /// Insert text into content and record edit info for incremental rendering.