experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: offline reader content fetching on iOS

save_url was missing offline content fetch — tags were stored but
fetch_and_store_offline_content was never called. Also added offline
content fetching for items arriving via sync pull, and on-demand
fetch fallback in the UI when content is missing.

+48 -13
+32 -2
backend/tauri-mobile/src-tauri/src/lib.rs
··· 1515 1515 println!("[Rust] save_url called with url: {}, tags: {:?}", url, tags); 1516 1516 1517 1517 let store = get_store()?; 1518 - store.save_url(&url, &tags, metadata)?; 1518 + let result = store.save_url(&url, &tags, metadata)?; 1519 1519 1520 1520 println!("[Rust] Page saved successfully"); 1521 + 1522 + // Handle offline tag — fetch content if the offline tag was included 1523 + let config = load_profile_config(); 1524 + if tags.contains(&config.offline_tag) { 1525 + let item_id = result.item_id.clone(); 1526 + let fetch_url = url.clone(); 1527 + tauri::async_runtime::spawn(async move { 1528 + if let Err(e) = fetch_and_store_offline_content(&item_id, &fetch_url).await { 1529 + println!("[Rust] Failed to fetch offline content: {}", e); 1530 + } 1531 + }); 1532 + } 1521 1533 1522 1534 // Trigger auto-sync if enabled (fire and forget) 1523 1535 tauri::async_runtime::spawn(async move { ··· 2836 2848 let mut pulled = 0; 2837 2849 let mut conflicts = 0; 2838 2850 2851 + let offline_tag = config.offline_tag.clone(); 2852 + 2839 2853 for server_item in &server_response.items { 2840 2854 match store.merge_server_item(server_item)? { 2841 - "pulled" => pulled += 1, 2855 + "pulled" => { 2856 + pulled += 1; 2857 + // Fetch offline content for pulled items that have the offline tag 2858 + if server_item.tags.contains(&offline_tag) { 2859 + if let Some(ref url) = server_item.content { 2860 + if !url.is_empty() && server_item.item_type == "url" { 2861 + let item_id = server_item.id.clone(); 2862 + let fetch_url = url.clone(); 2863 + tauri::async_runtime::spawn(async move { 2864 + if let Err(e) = fetch_and_store_offline_content(&item_id, &fetch_url).await { 2865 + println!("[Rust] Failed to fetch offline content for synced item: {}", e); 2866 + } 2867 + }); 2868 + } 2869 + } 2870 + } 2871 + } 2842 2872 "conflict" => conflicts += 1, 2843 2873 _ => {} 2844 2874 }
+16 -11
backend/tauri-mobile/src/App.tsx
··· 768 768 } | null>(null); 769 769 const [webviewAnimated, setWebviewAnimated] = useState(false); // drives height transition 770 770 const [webviewLoaded, setWebviewLoaded] = useState(false); 771 - const [webviewError, setWebviewError] = useState(false); 771 + const [webviewError, setWebviewError] = useState<string | false>(false); 772 772 773 773 // Camera capture state 774 774 const [capturedImage, setCapturedImage] = useState<string | null>(null); // base64 data URL ··· 2637 2637 setWebviewLoaded(true); 2638 2638 } catch (error) { 2639 2639 console.error("[App] Failed to create inline webview:", error); 2640 - setWebviewError(true); 2640 + setWebviewError(String(error)); 2641 2641 } 2642 2642 }; 2643 2643 ··· 2655 2655 {webviewError ? ( 2656 2656 <div className="webview-error"> 2657 2657 <p>This site can't be displayed inline.</p> 2658 + <p style={{fontSize: '11px', opacity: 0.6, wordBreak: 'break-all'}}>{webviewError}</p> 2658 2659 <button onClick={(e) => { e.stopPropagation(); openUrl(_url); }}>Open in Safari</button> 2659 2660 </div> 2660 2661 ) : null} ··· 3076 3077 } 3077 3078 console.log("[App] Opening offline reader at topOffset:", topOffset, "for item:", itemId); 3078 3079 // Fetch article content + metadata from Rust, build HTML in frontend for HMR 3079 - const content = await invoke<{ 3080 - html: string; 3081 - title: string; 3082 - byline: string; 3083 - dir: string; 3084 - domain: string; 3085 - url: string; 3086 - }>("get_reader_content", { itemId }); 3080 + let content: { html: string; title: string; byline: string; dir: string; domain: string; url: string }; 3081 + try { 3082 + content = await invoke("get_reader_content", { itemId }); 3083 + } catch (fetchErr) { 3084 + // Content not cached yet (e.g. synced from desktop without fetching) — fetch on demand 3085 + console.log("[App] No cached content, fetching on demand for:", itemId, fetchErr); 3086 + // Find the item's URL from our local state 3087 + const item = savedUrls.find((u: SavedUrl) => u.id === itemId); 3088 + if (!item?.url) throw new Error("No URL for this item"); 3089 + await invoke("fetch_offline_content", { itemId, url: item.url }); 3090 + content = await invoke("get_reader_content", { itemId }); 3091 + } 3087 3092 const dirAttr = content.dir ? ` dir="${content.dir}"` : ""; 3088 3093 const baseTag = content.url ? `<base href="${content.url.replace(/"/g, '&quot;')}">` : ""; 3089 3094 const domainLink = content.url ··· 3118 3123 setWebviewLoaded(true); 3119 3124 } catch (error) { 3120 3125 console.error("[App] Failed to open offline reader:", error); 3121 - setWebviewError(true); 3126 + setWebviewError(String(error)); 3122 3127 } 3123 3128 }, WEBVIEW_ANIM_MS); 3124 3129 });