···12 let window = web_sys::window().ok_or_else(|| JsValue::from_str("no window"))?;
13 let navigator = window.navigator();
14 let sw_container = navigator.service_worker();
15-16 let promise = sw_container.register("/sw.js");
17 JsFuture::from(promise).await?;
01819 Ok(())
20}
···30 use jacquard::prelude::IdentityResolver;
31 use std::collections::HashMap;
32033 let mut blob_mappings = HashMap::new();
3435 // Resolve DID and PDS URL
···61 blob_mappings.insert(name.as_ref().to_string(), blob_url);
62 }
63 }
06465- // Send mappings to service worker
66- if !blob_mappings.is_empty() {
67- send_blob_mappings(book_title, blob_mappings)?;
68- }
69 }
07071 Ok(())
72}
···80 let navigator = window.navigator();
81 let sw_container = navigator.service_worker();
82083 let controller = sw_container
84 .controller()
85 .ok_or_else(|| JsValue::from_str("no service worker controller"))?;
···97 js_sys::Reflect::set(&msg, &"blobs".into(), &blobs_obj)?;
9899 controller.post_message(&msg)?;
0100101 Ok(())
102}
···117}
118119// #[used]
120-// static BINDINGS_JS: Asset = asset!("/assets/sw.js", AssetOptions::js().with_hash_suffix(false));
000000
···12 let window = web_sys::window().ok_or_else(|| JsValue::from_str("no window"))?;
13 let navigator = window.navigator();
14 let sw_container = navigator.service_worker();
15+ tracing::debug!("Registering service worker");
16 let promise = sw_container.register("/sw.js");
17 JsFuture::from(promise).await?;
18+ tracing::debug!("Service worker registered");
1920 Ok(())
21}
···31 use jacquard::prelude::IdentityResolver;
32 use std::collections::HashMap;
3334+ tracing::debug!("registering blobs for {}", book_title);
35 let mut blob_mappings = HashMap::new();
3637 // Resolve DID and PDS URL
···63 blob_mappings.insert(name.as_ref().to_string(), blob_url);
64 }
65 }
66+ }
6768+ // Send mappings to service worker
69+ if !blob_mappings.is_empty() {
70+ send_blob_mappings(book_title, blob_mappings)?;
071 }
72+ //}
7374 Ok(())
75}
···83 let navigator = window.navigator();
84 let sw_container = navigator.service_worker();
8586+ tracing::debug!("sending blob mappings for {}", notebook);
87 let controller = sw_container
88 .controller()
89 .ok_or_else(|| JsValue::from_str("no service worker controller"))?;
···101 js_sys::Reflect::set(&msg, &"blobs".into(), &blobs_obj)?;
102103 controller.post_message(&msg)?;
104+ tracing::debug!("sent blob mappings for {}", notebook);
105106 Ok(())
107}
···122}
123124// #[used]
125+// static BINDINGS_JS: Asset = asset!(
126+// "/sw.js",
127+// AssetOptions::js()
128+// .with_hash_suffix(false)
129+// .with_minify(false)
130+// .with_preload(true)
131+// );
+3-4
crates/weaver-app/src/views/home.rs
···8#[component]
9pub fn Home() -> Element {
10 // Fetch notebooks from UFOS with SSR support
11- let notebooks = data::use_notebooks_from_ufos()?;
12 let navigator = use_navigator();
13 let mut uri_input = use_signal(|| String::new());
14···16 let input_uri = uri_input.read().clone();
17 if !input_uri.is_empty() {
18 if let Ok(parsed) = AtUri::new(&input_uri) {
19- navigator.push(Route::RecordPage {
20- uri: vec![parsed.to_string()],
21- });
22 }
23 }
24 };
···8#[component]
9pub fn Home() -> Element {
10 // Fetch notebooks from UFOS with SSR support
11+ let notebooks = data::use_notebooks_from_ufos();
12 let navigator = use_navigator();
13 let mut uri_input = use_signal(|| String::new());
14···16 let input_uri = uri_input.read().clone();
17 if !input_uri.is_empty() {
18 if let Ok(parsed) = AtUri::new(&input_uri) {
19+ let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, parsed);
20+ navigator.push(link);
021 }
22 }
23 };
+45-52
crates/weaver-app/src/views/navbar.rs
···1use crate::Route;
02use crate::components::button::{Button, ButtonVariant};
3use crate::components::login::LoginModal;
4-use crate::data::{use_get_handle, use_notebook_handle};
5use crate::fetch::Fetcher;
6use dioxus::prelude::*;
7-use jacquard::types::did::Did;
89-const THEME_DEFAULTS_CSS: Asset = asset!("/assets/styling/theme-defaults.css");
10const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css");
1112/// The Navbar component that will be rendered on all pages of our app since every page is under the layout.
···16/// routes will be rendered under the outlet inside this component
17#[component]
18pub fn Navbar() -> Element {
019 let route = use_route::<Route>();
02021- let route_handle = use_signal(|| match &route {
022 Route::EntryPage { ident, .. } => Some(ident.clone()),
23 Route::RepositoryIndex { ident } => Some(ident.clone()),
24 Route::NotebookIndex { ident, .. } => Some(ident.clone()),
25 _ => None,
26 });
27- let notebook_handle = use_notebook_handle(route_handle);
0002829 rsx! {
30- document::Link { rel: "stylesheet", href: THEME_DEFAULTS_CSS }
31 document::Link { rel: "stylesheet", href: NAVBAR_CSS }
32-33 div {
34 id: "navbar",
35 nav { class: "breadcrumbs",
···4142 // Show repository breadcrumb if we're on a repository page
43 match route {
44- Route::RepositoryIndex { .. } => {
45- let handle = notebook_handle.as_ref().unwrap();
046 rsx! {
47 span { class: "breadcrumb-separator", " > " }
48 span { class: "breadcrumb breadcrumb-current", "@{handle}" }
49 }
50 },
51 Route::NotebookIndex { ident, book_title } => {
52- let handle = notebook_handle.as_ref().unwrap();
053 rsx! {
54 span { class: "breadcrumb-separator", " > " }
55 Link {
···62 }
63 },
64 Route::EntryPage { ident, book_title, .. } => {
65- let handle = notebook_handle.as_ref().unwrap();
066 rsx! {
67 span { class: "breadcrumb-separator", " > " }
68 Link {
···81 _ => rsx! {}
82 }
83 }
84- LoginButton {}
85-86- }
87-88- Outlet::<Route> {}
89- }
90-}
91-92-#[component]
93-fn LoginButton() -> Element {
94- let fetcher = use_context::<Fetcher>();
95- let mut show_login_modal = use_signal(|| false);
96- let mut auth_state = use_context::<Signal<crate::auth::AuthState>>();
97- let did_signal = use_signal(|| auth_state.read().did.clone());
98- if let Some(did) = &*did_signal.read() {
99- rsx! {
100- Button {
101- variant: ButtonVariant::Ghost,
102- onclick: move |_| {
103- let fetcher = fetcher.clone();
104- auth_state.write().clear();
105- async move {
106- fetcher.downgrade_to_unauthenticated().await;
107 }
108- },
109- span { class: "auth-handle", "@{use_get_handle(did.clone())}" }
110- }
111- LoginModal {
112- open: show_login_modal
113- }
114- }
115- } else {
116- rsx! {
117- div {
118- class: "auth-button",
119- Button {
120- variant: ButtonVariant::Ghost,
121- onclick: move |_| show_login_modal.set(true),
122- span { class: "auth-handle", "Sign In" }
123 }
124- }
125- LoginModal {
126- open: show_login_modal
127 }
128 }
00129 }
130}
···1use crate::Route;
2+use crate::auth::AuthState;
3use crate::components::button::{Button, ButtonVariant};
4use crate::components::login::LoginModal;
5+use crate::data::{use_get_handle, use_load_handle};
6use crate::fetch::Fetcher;
7use dioxus::prelude::*;
0809const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css");
1011/// The Navbar component that will be rendered on all pages of our app since every page is under the layout.
···15/// routes will be rendered under the outlet inside this component
16#[component]
17pub fn Navbar() -> Element {
18+ tracing::debug!("Navbar component rendering");
19 let route = use_route::<Route>();
20+ tracing::debug!("Route: {:?}", route);
2122+ let mut auth_state = use_context::<Signal<crate::auth::AuthState>>();
23+ let route_handle = use_load_handle(match &route {
24 Route::EntryPage { ident, .. } => Some(ident.clone()),
25 Route::RepositoryIndex { ident } => Some(ident.clone()),
26 Route::NotebookIndex { ident, .. } => Some(ident.clone()),
27 _ => None,
28 });
29+ let fetcher = use_context::<Fetcher>();
30+ let mut show_login_modal = use_signal(|| false);
31+32+ tracing::debug!("Navbar got route_handle: {:?}", route_handle);
3334 rsx! {
035 document::Link { rel: "stylesheet", href: NAVBAR_CSS }
36+ document::Link { rel: "stylesheet", href: asset!("/assets/styling/button.css") }
37 div {
38 id: "navbar",
39 nav { class: "breadcrumbs",
···4546 // Show repository breadcrumb if we're on a repository page
47 match route {
48+ Route::RepositoryIndex { ident } => {
49+ let route_handle = route_handle.read().clone();
50+ let handle = route_handle.unwrap_or(ident.clone());
51 rsx! {
52 span { class: "breadcrumb-separator", " > " }
53 span { class: "breadcrumb breadcrumb-current", "@{handle}" }
54 }
55 },
56 Route::NotebookIndex { ident, book_title } => {
57+ let route_handle = route_handle.read().clone();
58+ let handle = route_handle.unwrap_or(ident.clone());
59 rsx! {
60 span { class: "breadcrumb-separator", " > " }
61 Link {
···68 }
69 },
70 Route::EntryPage { ident, book_title, .. } => {
71+ let route_handle = route_handle.read().clone();
72+ let handle = route_handle.unwrap_or(ident.clone());
73 rsx! {
74 span { class: "breadcrumb-separator", " > " }
75 Link {
···88 _ => rsx! {}
89 }
90 }
91+ if auth_state.read().is_authenticated() {
92+ if let Some(did) = &auth_state.read().did {
93+ Button {
94+ variant: ButtonVariant::Ghost,
95+ onclick: move |_| {
96+ let fetcher = fetcher.clone();
97+ auth_state.write().clear();
98+ async move {
99+ fetcher.downgrade_to_unauthenticated().await;
100+ }
101+ },
102+ span { class: "auth-handle", "@{use_get_handle(did.clone())}" }
103+ }
104+ }
105+ } else {
106+ div {
107+ class: "auth-button",
108+ Button {
109+ variant: ButtonVariant::Ghost,
110+ onclick: move |_| show_login_modal.set(true),
111+ span { class: "auth-handle", "Sign In" }
00112 }
113+00000000000000114 }
115+ LoginModal {
116+ open: show_login_modal
117+ }
118 }
119 }
120+121+ Outlet::<Route> {}
122 }
123}
+13-5
crates/weaver-app/src/views/notebook.rs
···17/// re-run and the rendered HTML will be updated.
18#[component]
19pub fn Notebook(ident: ReadSignal<AtIdentifier<'static>>, book_title: SmolStr) -> Element {
0000020 rsx! {
21 NotebookCss { ident: ident.to_smolstr(), notebook: book_title }
22 Outlet::<Route> {}
···28 ident: ReadSignal<AtIdentifier<'static>>,
29 book_title: ReadSignal<SmolStr>,
30) -> Element {
0000031 // Fetch full notebook metadata with SSR support
32 // IMPORTANT: Call ALL hooks before any ? early returns to maintain hook order
33 let notebook_data = data::use_notebook(ident, book_title);
34 let entries_resource = data::use_notebook_entries(ident, book_title);
35-36- // Now check for errors
37- let notebook_data = notebook_data?;
38- let entries_resource = entries_resource?;
3940 rsx! {
41 document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS }
···60 EntryCard {
61 entry: entry.clone(),
62 book_title: book_title(),
63- author_count
064 }
65 }
66 }
···17/// re-run and the rendered HTML will be updated.
18#[component]
19pub fn Notebook(ident: ReadSignal<AtIdentifier<'static>>, book_title: SmolStr) -> Element {
20+ tracing::debug!(
21+ "Notebook component rendering for ident: {:?}, book: {}",
22+ ident(),
23+ book_title
24+ );
25 rsx! {
26 NotebookCss { ident: ident.to_smolstr(), notebook: book_title }
27 Outlet::<Route> {}
···33 ident: ReadSignal<AtIdentifier<'static>>,
34 book_title: ReadSignal<SmolStr>,
35) -> Element {
36+ tracing::debug!(
37+ "NotebookIndex component rendering for ident: {:?}, book: {}",
38+ ident(),
39+ book_title()
40+ );
41 // Fetch full notebook metadata with SSR support
42 // IMPORTANT: Call ALL hooks before any ? early returns to maintain hook order
43 let notebook_data = data::use_notebook(ident, book_title);
44 let entries_resource = data::use_notebook_entries(ident, book_title);
45+ tracing::debug!("NotebookIndex got notebook data and entries");
0004647 rsx! {
48 document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS }
···67 EntryCard {
68 entry: entry.clone(),
69 book_title: book_title(),
70+ author_count,
71+ ident: ident(),
72 }
73 }
74 }
···203 if let Some(embeds) = &entry.embeds {
204 if let Some(images) = &embeds.images {
205 for img in &images.images {
206- tracing::info!("Image: {:?}", img);
207 if let Some(name) = &img.name {
208 let blob_name = BlobName::from_filename(name.as_ref());
209 map.insert(blob_name, img.image.blob().cid().clone().into_static());
···203 if let Some(embeds) = &entry.embeds {
204 if let Some(images) = &embeds.images {
205 for img in &images.images {
0206 if let Some(name) = &img.name {
207 let blob_name = BlobName::from_filename(name.as_ref());
208 map.insert(blob_name, img.image.blob().cid().clone().into_static());