···2323use jacquard::bytes::Bytes;
2424use jacquard::cowstr::ToCowStr;
2525use jacquard::prelude::*;
2626+use jacquard::smol_str::format_smolstr;
2627use jacquard::types::blob::MimeType;
2728use jacquard::types::collection::Collection;
2829use jacquard::types::ident::AtIdentifier;
···225226 };
226227227228 // Build AT-URI pointing to actual draft record: at://{did}/sh.weaver.edit.draft/{rkey}
228228- let canonical_uri = format!("at://{}/{}/{}", did, DRAFT_NSID, rkey);
229229+ let canonical_uri = format_smolstr!("at://{}/{}/{}", did, DRAFT_NSID, rkey);
229230230231 DocRef {
231232 value: DocRefValue::DraftRef(Box::new(DraftRef {
···283284284285 let query = GetBacklinksQuery {
285286 subject: Uri::At(entry_uri.clone().into_static()),
286286- source: format!("{}:doc.value.entry.uri", ROOT_NSID).into(),
287287+ source: format_smolstr!("{}:doc.value.entry.uri", ROOT_NSID).into(),
287288 cursor: None,
288289 did: vec![],
289290 limit: 1,
···342343 // Query for edit.root records from this DID that reference entry_uri
343344 let query = GetBacklinksQuery {
344345 subject: Uri::At(entry_uri.clone().into_static()),
345345- source: format!("{}:doc.value.entry.uri", ROOT_NSID).into(),
346346+ source: format_smolstr!("{}:doc.value.entry.uri", ROOT_NSID).into(),
346347 cursor: None,
347348 did: all_dids.clone(),
348349 limit: 10,
···388389389390 let query = GetBacklinksQuery {
390391 subject: Uri::At(draft_uri.clone().into_static()),
391391- source: format!("{}:doc.value.draft_key", ROOT_NSID).into(),
392392+ source: format_smolstr!("{}:doc.value.draft_key", ROOT_NSID).into(),
392393 cursor: None,
393394 did: vec![],
394395 limit: 1,
···421422 draft_key.to_string()
422423 };
423424424424- let uri_str = format!("at://{}/{}/{}", did, DRAFT_NSID, rkey);
425425+ let uri_str = format_smolstr!("at://{}/{}/{}", did, DRAFT_NSID, rkey);
425426 // Safe to unwrap: we're constructing a valid AT-URI
426427 AtUri::new(&uri_str).unwrap().into_static()
427428}
···564565 loop {
565566 let query = GetBacklinksQuery {
566567 subject: Uri::At(root_uri.clone().into_static()),
567567- source: format!("{}:root.uri", DIFF_NSID).into(),
568568+ source: format_smolstr!("{}:root.uri", DIFF_NSID).into(),
568569 cursor: cursor.map(Into::into),
569570 did: vec![],
570571 limit: 100,
···977978 let root_did = root_id.did.clone();
978979979980 // Build root URI to look up last seen diff
980980- let root_uri = AtUri::new(&format!(
981981+ let root_uri = AtUri::new(&format_smolstr!(
981982 "at://{}/{}/{}",
982983 root_id.did,
983984 ROOT_NSID,
···10631064 after_rkey: Option<&str>,
10641065) -> Result<Option<PdsEditState>, WeaverError> {
10651066 // Build root URI
10661066- let root_uri = AtUri::new(&format!(
10671067+ let root_uri = AtUri::new(&format_smolstr!(
10671068 "at://{}/{}/{}",
10681069 root_id.did,
10691070 ROOT_NSID,
···11311132 }
11321133 }
1133113411341134- let diff_uri = AtUri::new(&format!("at://{}/{}/{}", diff_id.did, DIFF_NSID, rkey_str))
11351135+ let diff_uri = AtUri::new(&format_smolstr!("at://{}/{}/{}", diff_id.did, DIFF_NSID, rkey_str))
11351136 .map_err(|e| WeaverError::InvalidNotebook(format!("Invalid diff URI: {}", e)))?
11361137 .into_static();
11371138
+11-8
crates/weaver-app/src/components/editor/worker.rs
···1515use std::collections::HashMap;
1616use weaver_common::transport::PresenceSnapshot;
17171818+#[cfg(all(target_family = "wasm", target_os = "unknown"))]
1919+use jacquard::smol_str::format_smolstr;
2020+1821/// Input messages to the editor worker.
1922#[derive(Serialize, Deserialize, Debug, Clone)]
2023pub enum WorkerInput {
···224227 if let Err(e) = new_doc.import(&snapshot) {
225228 if let Err(send_err) = scope
226229 .send(WorkerOutput::Error {
227227- message: format!("Failed to import snapshot: {e}"),
230230+ message: format_smolstr!("Failed to import snapshot: {e}").to_string(),
228231 })
229232 .await
230233 {
···271274 Err(e) => {
272275 if let Err(send_err) = scope
273276 .send(WorkerOutput::Error {
274274- message: format!("Export failed: {e}"),
277277+ message: format_smolstr!("Export failed: {e}").to_string(),
275278 })
276279 .await
277280 {
···321324 Err(e) => {
322325 if let Err(send_err) = scope
323326 .send(WorkerOutput::Error {
324324- message: format!("Failed to spawn CollabNode: {e}"),
327327+ message: format_smolstr!("Failed to spawn CollabNode: {e}").to_string(),
325328 })
326329 .await
327330 {
···450453 Err(e) => {
451454 if let Err(send_err) = scope
452455 .send(WorkerOutput::Error {
453453- message: format!("Failed to join session: {e}"),
456456+ message: format_smolstr!("Failed to join session: {e}").to_string(),
454457 })
455458 .await
456459 {
···551554 if let Err(e) = new_doc.import(&snapshot) {
552555 if let Err(send_err) = scope
553556 .send(WorkerOutput::Error {
554554- message: format!("Failed to import snapshot: {e}"),
557557+ message: format_smolstr!("Failed to import snapshot: {e}").to_string(),
555558 })
556559 .await
557560 {
···584587 let snapshot_bytes = match doc.export(loro::ExportMode::Snapshot) {
585588 Ok(bytes) => bytes,
586589 Err(e) => {
587587- if let Err(send_err) = scope.send(WorkerOutput::Error { message: format!("Export failed: {e}") }).await {
590590+ if let Err(send_err) = scope.send(WorkerOutput::Error { message: format_smolstr!("Export failed: {e}").to_string() }).await {
588591 tracing::error!("Failed to send Error to coordinator: {send_err}");
589592 }
590593 continue;
···728731 let at_uri = match AtUri::new_owned(uri_str.clone()) {
729732 Ok(u) => u,
730733 Err(e) => {
731731- errors.insert(uri_str, format!("Invalid AT URI: {e}"));
734734+ errors.insert(uri_str, format_smolstr!("Invalid AT URI: {e}").to_string());
732735 continue;
733736 }
734737 };
···770773 results.insert(uri_str, html);
771774 }
772775 Err(e) => {
773773- errors.insert(uri_str, format!("{:?}", e));
776776+ errors.insert(uri_str, format_smolstr!("{:?}", e).to_string());
774777 }
775778 }
776779 }
+3-1
crates/weaver-app/src/components/entry.rs
···145145 if cleaned.len() <= max_len {
146146 cleaned
147147 } else {
148148- format!("{}...", &cleaned[..max_len - 3])
148148+ // Use char boundary-safe truncation to avoid panic on multibyte chars
149149+ let truncated: String = cleaned.chars().take(max_len - 3).collect();
150150+ format!("{}...", truncated)
149151 }
150152}
151153
+3-2
crates/weaver-app/src/components/record_editor.rs
···13501350 if let Some(new_collection_str) = data.type_discriminator() {
13511351 let new_collection = Nsid::new(new_collection_str).ok();
13521352 if let Some(new_collection) = new_collection {
13531353- // Create new record
13531353+ // Create new record first - if this fails, user keeps their old record
13541354+ // If delete fails after, user has duplicates (recoverable) rather than data loss
13541355 let create_req = CreateRecord::new()
13551356 .repo(AtIdentifier::Did(did.clone()))
13561357 .collection(new_collection)
···13601361 match fetcher.send(create_req).await {
13611362 Ok(response) => {
13621363 if let Ok(create_output) = response.into_output() {
13631363- // Delete old record
13641364+ // Delete old record after successful create
13641365 if let (Some(old_collection_str), Some(old_rkey)) = (uri.collection(), uri.rkey()) {
13651366 let old_collection = Nsid::new(old_collection_str.as_str()).ok();
13661367 if let Some(old_collection) = old_collection {