From 8b6741eb872185f0b16369df6f52f984a273bd4a Mon Sep 17 00:00:00 2001 From: Orual Date: Sat, 10 Jan 2026 18:23:28 +0000 Subject: [PATCH] subdomain routing works --- docker-compose.yml | 4 ++-- crates/weaver-app/src/data.rs | 40 ++++++++++++++++++++++++++++++++++------ crates/weaver-app/src/lib.rs | 23 +++++++++++++---------- crates/weaver-app/src/main.rs | 21 +++++++-------------- crates/weaver-app/src/subdomain_app.rs | 51 ++++++++++++++++++++++++++++----------------------- crates/weaver-app/src/components/app_link.rs | 1 + crates/weaver-app/src/components/entry.rs | 16 ++++++++-------- crates/weaver-app/src/components/login.rs | 31 ++++++++++++++----------------- crates/weaver-app/src/components/profile.rs | 15 ++++----------- crates/weaver-app/src/components/profile_actions.rs | 32 ++++++++++++++++---------------- crates/weaver-app/src/views/footer.rs | 16 +++++++++++----- crates/weaver-app/src/views/mod.rs | 2 +- crates/weaver-app/src/views/navbar.rs | 8 +++++--- crates/weaver-app/src/views/subdomain_navbar.rs | 84 +++++++++++++++++++++++++++++++++++++++--------------------------------------------- crates/weaver-index/migrations/clickhouse/009_handle_mappings_identity_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/010_handle_mappings_account_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/012_profiles_weaver_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/014_profiles_bsky_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/018_notebooks_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/021_entries_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/024_drafts_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/026_edit_roots_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/027_edit_diffs_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/030_collab_invites_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/032_collab_accepts_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/034_collab_sessions_mv.sql | 2 +- crates/weaver-index/migrations/clickhouse/039_notebook_entries_mv.sql | 2 +- 27 file(s) changed, 196 insertion(s)(+), 174 deletion(s)(-) diff --git a/docker-compose.yml b/docker-compose.yml --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,7 +54,7 @@ #TAP_FULL_NETWORK: true #TAP_SIGNAL_COLLECTION: place.stream.chat.profile TAP_SIGNAL_COLLECTION: sh.weaver.actor.profile - TAP_COLLECTION_FILTERS: "sh.weaver.*,app.bsky.actor.profile,sh.tangled.*,pub.leaflet.*,net.anisota.*,place.stream.*" + TAP_COLLECTION_FILTERS: "sh.weaver.*,app.bsky.actor.profile,sh.tangled.*,pub.leaflet.*,net.anisota.*,place.stream.*,site.standard.*" healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:2480/health"] interval: 20s @@ -81,7 +81,7 @@ TAP_URL: ws://tap:2480/channel TAP_SEND_ACKS: "true" FIREHOSE_RELAY_URL: wss://bsky.network - INDEXER_COLLECTIONS: "sh.weaver.*,app.bsky.actor.profile,sh.tangled.*,pub.leaflet.*,net.anisota.*,place.stream.*" + INDEXER_COLLECTIONS: "sh.weaver.*,app.bsky.actor.profile,sh.tangled.*,pub.leaflet.*,net.anisota.*,place.stream.*,site.standard.*" depends_on: tap: condition: service_healthy diff --git a/crates/weaver-app/src/data.rs b/crates/weaver-app/src/data.rs --- a/crates/weaver-app/src/data.rs +++ b/crates/weaver-app/src/data.rs @@ -1585,7 +1585,17 @@ ident: ReadSignal>, rkey: ReadSignal, ) -> ( - Result, Option)>>, RenderError>, + Result< + Resource< + Option<( + serde_json::Value, + serde_json::Value, + Option, + Option, + )>, + >, + RenderError, + >, Memo>, ) { use weaver_api::pub_leaflet::document::Document; @@ -1661,13 +1671,17 @@ for page in &record.value.pages { match page { DocumentPagesItem::LinearDocument(linear_doc) => { - html.push_str(&render_linear_document(linear_doc, &ctx, &fetcher).await); + html.push_str( + &render_linear_document(linear_doc, &ctx, &fetcher).await, + ); } DocumentPagesItem::Canvas(_) => { html.push_str("
[Canvas layout not yet supported]
"); } DocumentPagesItem::Unknown(_) => { - html.push_str("
[Unknown page type]
"); + html.push_str( + "
[Unknown page type]
", + ); } } } @@ -1773,13 +1787,17 @@ for page in &record.value.pages { match page { DocumentPagesItem::LinearDocument(linear_doc) => { - html.push_str(&render_linear_document(linear_doc, &ctx, &fetcher).await); + html.push_str( + &render_linear_document(linear_doc, &ctx, &fetcher).await, + ); } DocumentPagesItem::Canvas(_) => { html.push_str("
[Canvas layout not yet supported]
"); } DocumentPagesItem::Unknown(_) => { - html.push_str("
[Unknown page type]
"); + html.push_str( + "
[Unknown page type]
", + ); } } } @@ -1810,7 +1828,17 @@ ident: ReadSignal>, rkey: ReadSignal, ) -> ( - Result, Option)>>, RenderError>, + Result< + Resource< + Option<( + serde_json::Value, + serde_json::Value, + Option, + Option, + )>, + >, + RenderError, + >, Memo>, ) { let fetcher = use_context::(); diff --git a/crates/weaver-app/src/lib.rs b/crates/weaver-app/src/lib.rs --- a/crates/weaver-app/src/lib.rs +++ b/crates/weaver-app/src/lib.rs @@ -72,7 +72,7 @@ #[layout(RecordIndex)] #[route("/:..uri")] RecordPage { uri: Vec }, - #[end_layout] + #[end_layout] #[end_nest] #[route("/callback?:state&:iss&:code")] Callback { state: SmolStr, iss: SmolStr, code: SmolStr }, @@ -124,8 +124,7 @@ #[route("/e/:rkey")] NotebookEntryByRkey { ident: AtIdentifier<'static>, book_title: SmolStr, rkey: SmolStr }, #[route("/e/:rkey/edit")] - NotebookEntryEdit { ident: AtIdentifier<'static>, book_title: SmolStr, rkey: SmolStr } - + NotebookEntryEdit { ident: AtIdentifier<'static>, book_title: SmolStr, rkey: SmolStr }, } pub static CONFIG: LazyLock = LazyLock::new(|| Config { @@ -170,15 +169,16 @@ tracing::warn!("Host header not valid UTF-8"); return None; }; - tracing::info!(host, "Subdomain detection: got host"); let host_str = host.split(':').next().unwrap_or(host); let Some(subdomain) = extract_subdomain(host_str, WEAVER_APP_DOMAIN) else { - tracing::info!(host_str, domain = WEAVER_APP_DOMAIN, "Not a subdomain request"); + tracing::info!( + host_str, + domain = WEAVER_APP_DOMAIN, + "Not a subdomain request" + ); return None; }; - tracing::info!(subdomain, "Subdomain detection: extracted subdomain"); - // Look up notebook by global path let result = lookup_subdomain_context(&fetcher, &subdomain).await; if result.is_none() { @@ -192,11 +192,13 @@ #[cfg(feature = "fullstack-server")] let ctx = match &*ctx_resource.read() { Some(ctx) => ctx.clone(), - None => return rsx! { div { "Loading..." } }, + None => { + return rsx! { div { "Loading..." } }; + } }; #[cfg(not(feature = "fullstack-server"))] - let ctx = None::; + let ctx = { None:: }; let auth_state = use_signal(|| AuthState::default()); #[allow(unused)] @@ -235,15 +237,16 @@ }); } - #[cfg(all(target_family = "wasm", target_os = "unknown"))] use_context_provider(|| restore_result); if sub == LinkMode::Subdomain { + tracing::info!("App: rendering SubdomainApp"); use_context_provider(|| ctx.unwrap()); rsx! { SubdomainApp {} } } else { + tracing::info!("App: rendering MainDomainApp"); rsx! { MainDomainApp {} } diff --git a/crates/weaver-app/src/main.rs b/crates/weaver-app/src/main.rs --- a/crates/weaver-app/src/main.rs +++ b/crates/weaver-app/src/main.rs @@ -1,22 +1,15 @@ //! Weaver App main binary. +#[allow(unused)] use dioxus::prelude::*; - -#[cfg(feature = "server")] -use std::sync::Arc; - -#[cfg(feature = "server")] -use tower::Service; - -#[cfg(feature = "server")] -use weaver_app::{App, CONFIG, SubdomainApp, SubdomainContext, fetch}; - -#[cfg(not(feature = "server"))] -use weaver_app::{App, SubdomainApp}; - #[cfg(target_arch = "wasm32")] use lol_alloc::{FreeListAllocator, LockedAllocator}; - +#[cfg(feature = "server")] +use std::sync::Arc; +#[cfg(feature = "server")] +use tower::Service; +#[allow(unused)] +use weaver_app::{App, CONFIG, SubdomainApp, SubdomainContext, fetch}; #[cfg(target_arch = "wasm32")] #[global_allocator] static ALLOCATOR: LockedAllocator = diff --git a/crates/weaver-app/src/subdomain_app.rs b/crates/weaver-app/src/subdomain_app.rs --- a/crates/weaver-app/src/subdomain_app.rs +++ b/crates/weaver-app/src/subdomain_app.rs @@ -3,18 +3,13 @@ //! Separate router for subdomain hosting with simpler route structure. use dioxus::prelude::*; -use jacquard::oauth::client::OAuthClient; -use jacquard::oauth::session::ClientData; use jacquard::smol_str::{SmolStr, ToSmolStr}; use jacquard::types::string::AtIdentifier; -use crate::auth; -use crate::auth::{AuthState, AuthStore}; use crate::components::identity::RepositoryIndex; use crate::components::{EntryPage, NotebookCss}; -use crate::host_mode::{LinkMode, SubdomainContext}; +use crate::host_mode::SubdomainContext; use crate::views::{NotebookEntryByRkey, NotebookEntryEdit, NotebookIndex, SubdomainNavbar}; -use crate::{CONFIG, fetch}; /// Subdomain route enum - simpler paths without /:ident/:notebook prefix. #[derive(Debug, Clone, Routable, PartialEq)] @@ -54,26 +49,36 @@ let request = ResolveGlobalNotebook::new().path(path).build(); match fetcher.send(request).await { - Ok(response) => { - let output = response.into_output().ok()?; - let notebook = output.notebook; + Ok(response) => match response.into_output() { + Ok(output) => { + let notebook = output.notebook; - let owner = notebook.uri.authority().clone().into_static(); - let rkey = notebook.uri.rkey()?.0.to_smolstr(); - let notebook_path = notebook - .path - .map(|p| SmolStr::new(p.as_ref())) - .unwrap_or_else(|| SmolStr::new(path)); + let owner = notebook.uri.authority().clone().into_static(); + let Some(rkey) = notebook.uri.rkey() else { + tracing::warn!(path, uri = %notebook.uri, "Notebook URI missing rkey"); + return None; + }; + let rkey = rkey.0.to_smolstr(); + let notebook_path = notebook + .path + .map(|p| SmolStr::new(p.as_ref())) + .unwrap_or_else(|| SmolStr::new(path)); - Some(SubdomainContext { - owner, - notebook_path, - notebook_rkey: rkey, - notebook_title: notebook.title.clone().unwrap_or_default().to_smolstr(), - }) - } + tracing::info!(path, %owner, %rkey, "Notebook lookup succeeded"); + Some(SubdomainContext { + owner, + notebook_path, + notebook_rkey: rkey, + notebook_title: notebook.title.clone().unwrap_or_default().to_smolstr(), + }) + } + Err(e) => { + tracing::warn!(path, error = %e, "Failed to parse notebook response"); + None + } + }, Err(e) => { - tracing::debug!(path = path, error = %e, "Global notebook lookup failed"); + tracing::warn!(path, error = %e, "Global notebook lookup request failed"); None } } diff --git a/crates/weaver-app/src/components/app_link.rs b/crates/weaver-app/src/components/app_link.rs --- a/crates/weaver-app/src/components/app_link.rs +++ b/crates/weaver-app/src/components/app_link.rs @@ -75,6 +75,7 @@ #[component] pub fn AppLink(props: AppLinkProps) -> Element { let link_mode = use_context::(); + tracing::info!(?link_mode, "AppLink: reading LinkMode context"); let class = props.class.clone().unwrap_or_default(); match link_mode { diff --git a/crates/weaver-app/src/components/entry.rs b/crates/weaver-app/src/components/entry.rs --- a/crates/weaver-app/src/components/entry.rs +++ b/crates/weaver-app/src/components/entry.rs @@ -1,10 +1,9 @@ #![allow(non_snake_case)] -use crate::components::{AppLink, AppLinkTarget}; -use crate::Route; #[cfg(feature = "server")] use crate::blobcache::BlobCache; use crate::components::AuthorList; +use crate::components::{AppLink, AppLinkTarget}; use crate::{components::EntryActions, data::use_handle}; use dioxus::prelude::*; use jacquard::types::aturi::AtUri; @@ -32,8 +31,9 @@ ) -> Element { // Use feature-gated hook for SSR support let (entry_res, entry) = crate::data::use_entry_data(ident, book_title, title); - let route = use_route::(); - let mut last_route = use_signal(|| route.clone()); + + // Track props for change detection (works with both Route and SubdomainRoute) + let mut last_title = use_signal(|| title().clone()); #[cfg(all( target_family = "wasm", @@ -47,11 +47,11 @@ let mut entry_res = entry_res?; #[cfg(feature = "fullstack-server")] - use_effect(use_reactive!(|route| { - if route != last_route() { - tracing::debug!("[EntryPage] route changed, restarting resource"); + use_effect(use_reactive!(|title| { + if title != last_title() { + tracing::debug!("[EntryPage] title changed, restarting resource"); entry_res.restart(); - last_route.set(route.clone()); + last_title.set(title()); } })); diff --git a/crates/weaver-app/src/components/login.rs b/crates/weaver-app/src/components/login.rs --- a/crates/weaver-app/src/components/login.rs +++ b/crates/weaver-app/src/components/login.rs @@ -4,18 +4,16 @@ use jacquard::oauth::session::ClientData; use jacquard::{oauth::types::AuthorizeOptions, smol_str::SmolStr}; -use crate::{CONFIG, Route}; -use crate::{ - components::{ - button::{Button, ButtonVariant}, - dialog::{DialogContent, DialogRoot, DialogTitle}, - input::Input, - }, - fetch::Fetcher, +use crate::components::{ + button::{Button, ButtonVariant}, + dialog::{DialogContent, DialogRoot, DialogTitle}, + input::Input, }; +use crate::fetch::Fetcher; +use crate::CONFIG; fn handle_submit( - full_route: Route, + cached_route: String, fetcher: Fetcher, mut error: Signal>, mut is_loading: Signal, @@ -34,7 +32,7 @@ #[cfg(target_arch = "wasm32")] { use gloo_storage::Storage; - gloo_storage::LocalStorage::set("cached_route", format!("{}", full_route)).ok(); + gloo_storage::LocalStorage::set("cached_route", cached_route).ok(); spawn(async move { match start_oauth_flow(handle, fetcher).await { Ok(_) => { @@ -51,19 +49,18 @@ } #[component] -pub fn LoginModal(open: Signal) -> Element { +pub fn LoginModal(open: Signal, cached_route: String) -> Element { let mut handle_input = use_signal(|| String::new()); let error = use_signal(|| Option::::None); let is_loading = use_signal(|| false); - let full_route = use_route::(); let fetcher = use_context::(); - let submit_route = full_route.clone(); + let cached_route_clone = cached_route.clone(); let submit_fetcher = fetcher.clone(); let submit_closure1 = move || { - let submit_route = submit_route.clone(); + let cached_route = cached_route_clone.clone(); let submit_fetcher = submit_fetcher.clone(); handle_submit( - submit_route, + cached_route, submit_fetcher, error, is_loading, @@ -73,10 +70,10 @@ }; let submit_closure2 = move || { - let submit_route = full_route.clone(); + let cached_route = cached_route.clone(); let submit_fetcher = fetcher.clone(); handle_submit( - submit_route, + cached_route, submit_fetcher, error, is_loading, diff --git a/crates/weaver-app/src/components/profile.rs b/crates/weaver-app/src/components/profile.rs --- a/crates/weaver-app/src/components/profile.rs +++ b/crates/weaver-app/src/components/profile.rs @@ -2,13 +2,13 @@ use std::sync::Arc; -use crate::Route; use crate::components::button::{Button, ButtonVariant}; use crate::components::collab::api::{ReceivedInvite, accept_invite, fetch_received_invites}; use crate::components::{ BskyIcon, TangledIcon, avatar::{Avatar, AvatarImage}, }; +use crate::env::WEAVER_APP_HOST; use crate::fetch::Fetcher; use dioxus::prelude::*; use weaver_api::com_atproto::repo::strong_ref::StrongRef; @@ -406,16 +406,9 @@ use gloo_timers::future::TimeoutFuture; TimeoutFuture::new(500).await; } - // Navigate to the entry - parse AT-URI into path segments - // at://did/collection/rkey -> ["did", "collection", "rkey"] - let uri_str = resource_uri_nav.to_string(); - let uri_parts: Vec = uri_str - .strip_prefix("at://") - .unwrap_or(&uri_str) - .split('/') - .map(|s| s.to_string()) - .collect(); - nav.push(Route::RecordPage { uri: uri_parts }); + // Navigate to record page on main domain + let url = format!("{}/record/{}", WEAVER_APP_HOST, resource_uri_nav); + nav.push(url); } Err(e) => { error.set(Some(format!("Failed: {}", e))); diff --git a/crates/weaver-app/src/components/profile_actions.rs b/crates/weaver-app/src/components/profile_actions.rs --- a/crates/weaver-app/src/components/profile_actions.rs +++ b/crates/weaver-app/src/components/profile_actions.rs @@ -1,7 +1,7 @@ //! Actions sidebar/menubar for profile page. -use crate::Route; use crate::auth::AuthState; +use crate::components::app_link::{AppLink, AppLinkTarget}; use crate::components::button::{Button, ButtonVariant}; use dioxus::prelude::*; use jacquard::types::ident::AtIdentifier; @@ -32,9 +32,9 @@ aside { class: "profile-actions", div { class: "profile-actions-container", div { class: "profile-actions-list", - Link { - to: Route::NewDraft { ident: ident(), notebook: None }, - class: "profile-action-link", + AppLink { + to: AppLinkTarget::NewDraft { ident: ident(), notebook: None }, + class: "profile-action-link".to_string(), Button { variant: ButtonVariant::Outline, "New Entry" @@ -48,18 +48,18 @@ "New Notebook" } - Link { - to: Route::DraftsList { ident: ident() }, - class: "profile-action-link", + AppLink { + to: AppLinkTarget::Drafts { ident: ident() }, + class: "profile-action-link".to_string(), Button { variant: ButtonVariant::Ghost, "Drafts" } } - Link { - to: Route::InvitesPage { ident: ident() }, - class: "profile-action-link", + AppLink { + to: AppLinkTarget::Invites { ident: ident() }, + class: "profile-action-link".to_string(), Button { variant: ButtonVariant::Ghost, "Invites" @@ -90,24 +90,24 @@ rsx! { div { class: "profile-actions-menubar", - Link { - to: Route::NewDraft { ident: ident(), notebook: None }, + AppLink { + to: AppLinkTarget::NewDraft { ident: ident(), notebook: None }, Button { variant: ButtonVariant::Primary, "New Entry" } } - Link { - to: Route::DraftsList { ident: ident() }, + AppLink { + to: AppLinkTarget::Drafts { ident: ident() }, Button { variant: ButtonVariant::Ghost, "Drafts" } } - Link { - to: Route::InvitesPage { ident: ident() }, + AppLink { + to: AppLinkTarget::Invites { ident: ident() }, Button { variant: ButtonVariant::Ghost, "Invites" diff --git a/crates/weaver-app/src/views/footer.rs b/crates/weaver-app/src/views/footer.rs --- a/crates/weaver-app/src/views/footer.rs +++ b/crates/weaver-app/src/views/footer.rs @@ -12,7 +12,7 @@ /// Determines if the current route should show the full footer or just the minimal version. /// Full footer shows on shell pages (Home, Editor) and on owner's content pages. -fn should_show_full_footer(route: &Route) -> bool { +pub fn should_show_full_footer(route: &Route) -> bool { match route { // Shell pages: always show full footer Route::Home {} @@ -67,10 +67,7 @@ } #[component] -pub fn Footer() -> Element { - let route = use_route::(); - let show_full = should_show_full_footer(&route); - +pub fn Footer(#[props(default = true)] show_full: bool) -> Element { rsx! { document::Link { rel: "stylesheet", href: FOOTER_CSS } @@ -174,6 +171,15 @@ rel: "noopener", "Report Bug" } + + span { class: "footer-separator", "|" } + + Link { + to: crate::env::WEAVER_APP_HOST, + class: "footer-link", + "weaver.sh" + } + } } } diff --git a/crates/weaver-app/src/views/mod.rs b/crates/weaver-app/src/views/mod.rs --- a/crates/weaver-app/src/views/mod.rs +++ b/crates/weaver-app/src/views/mod.rs @@ -39,7 +39,7 @@ pub use invites::InvitesPage; mod footer; -pub use footer::Footer; +pub use footer::{Footer, should_show_full_footer}; mod static_page; pub use static_page::{AboutPage, PrivacyPage, TermsPage}; diff --git a/crates/weaver-app/src/views/navbar.rs b/crates/weaver-app/src/views/navbar.rs --- a/crates/weaver-app/src/views/navbar.rs +++ b/crates/weaver-app/src/views/navbar.rs @@ -4,7 +4,7 @@ use crate::components::login::LoginModal; use crate::data::{use_get_handle, use_load_handle}; use crate::fetch::Fetcher; -use crate::views::Footer; +use crate::views::{Footer, should_show_full_footer}; use dioxus::prelude::*; use dioxus_primitives::toast::{ToastOptions, use_toast}; use jacquard::types::ident::AtIdentifier; @@ -62,6 +62,7 @@ #[cfg(feature = "fullstack-server")] route_handle_res?; + #[allow(unused)] let fetcher = use_context::(); let mut show_login_modal = use_signal(|| false); @@ -292,7 +293,8 @@ } LoginModal { - open: show_login_modal + open: show_login_modal, + cached_route: format!("{}", route), } } } @@ -301,7 +303,7 @@ Outlet:: {} } - Footer {} + Footer { show_full: should_show_full_footer(&route) } } } } diff --git a/crates/weaver-app/src/views/subdomain_navbar.rs b/crates/weaver-app/src/views/subdomain_navbar.rs --- a/crates/weaver-app/src/views/subdomain_navbar.rs +++ b/crates/weaver-app/src/views/subdomain_navbar.rs @@ -4,10 +4,13 @@ use jacquard::types::string::{AtIdentifier, Did}; use crate::SubdomainRoute; +#[allow(unused_imports)] use crate::auth::{AuthState, RestoreResult}; use crate::components::button::{Button, ButtonVariant}; use crate::components::login::LoginModal; use crate::data::{use_get_handle, use_handle}; +#[allow(unused_imports)] +use crate::env::WEAVER_APP_HOST; use crate::fetch::Fetcher; use crate::host_mode::SubdomainContext; use crate::views::Footer; @@ -26,6 +29,8 @@ let ctx = use_context::(); let route = use_route::(); let auth_state = use_context::>(); + + #[allow(unused)] let fetcher = use_context::(); let mut show_login_modal = use_signal(|| false); @@ -61,55 +66,40 @@ div { id: "navbar", nav { class: "breadcrumbs", - // Notebook title links to index - Link { - to: SubdomainRoute::SubdomainLanding {}, - class: "breadcrumb", - "{ctx.notebook_title}" - } + // Notebook title links to index + Link { + to: SubdomainRoute::SubdomainLanding {}, + class: "breadcrumb", + "{ctx.notebook_title}" + } - // Show current location breadcrumb based on route - match &route { - SubdomainRoute::SubdomainLanding {} | SubdomainRoute::SubdomainIndexPage {} => { - rsx! {} - } - SubdomainRoute::SubdomainEntry { title } => { - rsx! { - span { class: "breadcrumb-separator", " > " } - span { class: "breadcrumb breadcrumb-current", "{title}" } + // Show current location breadcrumb based on route + match &route { + SubdomainRoute::SubdomainLanding {} | SubdomainRoute::SubdomainIndexPage {} | SubdomainRoute::SubdomainEntryByRkey { .. } | SubdomainRoute::SubdomainEntry { .. } => { + rsx! {} } - } - SubdomainRoute::SubdomainEntryByRkey { rkey } => { - rsx! { - span { class: "breadcrumb-separator", " > " } - span { class: "breadcrumb breadcrumb-current", "{rkey}" } - } - } - SubdomainRoute::SubdomainEntryEdit { rkey } => { - rsx! { - span { class: "breadcrumb-separator", " > " } - Link { - to: SubdomainRoute::SubdomainEntryByRkey { rkey: rkey.clone() }, - class: "breadcrumb", - "{rkey}" + SubdomainRoute::SubdomainEntryEdit { rkey } => { + rsx! { + span { class: "breadcrumb-separator", " > " } + Link { + to: SubdomainRoute::SubdomainEntryByRkey { rkey: rkey.clone() }, + class: "breadcrumb", + "{rkey}" + } } - span { class: "breadcrumb-separator", " > " } - span { class: "breadcrumb breadcrumb-current", "Edit" } } - } - SubdomainRoute::SubdomainProfile { ident } => { - rsx! { - span { class: "breadcrumb-separator", " > " } - span { class: "breadcrumb breadcrumb-current", "@{ident}" } + SubdomainRoute::SubdomainProfile { ident } => { + rsx! { + span { class: "breadcrumb-separator", " > " } + span { class: "breadcrumb breadcrumb-current", "@{ident}" } + } } } } - } - - // Author profile link - nav { class: "nav-tools", - AuthorProfileLink { ident: ctx.owner.clone() } - } + // Author profile link + nav { class: "nav-tools", + AuthorProfileLink { ident: ctx.owner.clone() } + } // Auth button if auth_state.read().is_authenticated() { @@ -126,16 +116,20 @@ } } LoginModal { - open: show_login_modal + open: show_login_modal, + cached_route: format!("{}", route), } } } + + + main { class: "app-main", Outlet:: {} } - Footer {} + Footer { show_full: false } } } } @@ -189,7 +183,7 @@ let (handle_res, handle) = use_handle(ident); #[cfg(feature = "fullstack-server")] - let mut handle_res = handle_res?; + handle_res?; rsx! { Link { diff --git a/crates/weaver-index/migrations/clickhouse/009_handle_mappings_identity_mv.sql b/crates/weaver-index/migrations/clickhouse/009_handle_mappings_identity_mv.sql --- a/crates/weaver-index/migrations/clickhouse/009_handle_mappings_identity_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/009_handle_mappings_identity_mv.sql @@ -8,6 +8,6 @@ 'active' as account_status, 'identity' as source, event_time, - now64(3) as indexed_at + indexed_at FROM raw_identity_events WHERE handle != '' diff --git a/crates/weaver-index/migrations/clickhouse/010_handle_mappings_account_mv.sql b/crates/weaver-index/migrations/clickhouse/010_handle_mappings_account_mv.sql --- a/crates/weaver-index/migrations/clickhouse/010_handle_mappings_account_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/010_handle_mappings_account_mv.sql @@ -8,7 +8,7 @@ a.status as account_status, 'account' as source, a.event_time, - now64(3) as indexed_at + a.indexed_at FROM raw_account_events a INNER JOIN handle_mappings h ON h.did = a.did AND h.freed = 0 WHERE a.active = 0 AND a.status != '' diff --git a/crates/weaver-index/migrations/clickhouse/012_profiles_weaver_mv.sql b/crates/weaver-index/migrations/clickhouse/012_profiles_weaver_mv.sql --- a/crates/weaver-index/migrations/clickhouse/012_profiles_weaver_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/012_profiles_weaver_mv.sql @@ -10,7 +10,7 @@ coalesce(record.banner.ref.`$link`, '') as banner_cid, parseDateTime64BestEffortOrZero(toString(record.createdAt), 3) as created_at, event_time, - now64(3) as indexed_at, + indexed_at, if(operation = 'delete', event_time, toDateTime64(0, 3)) as deleted_at FROM raw_records WHERE collection = 'sh.weaver.actor.profile' diff --git a/crates/weaver-index/migrations/clickhouse/014_profiles_bsky_mv.sql b/crates/weaver-index/migrations/clickhouse/014_profiles_bsky_mv.sql --- a/crates/weaver-index/migrations/clickhouse/014_profiles_bsky_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/014_profiles_bsky_mv.sql @@ -10,7 +10,7 @@ coalesce(record.banner.ref.`$link`, '') as banner_cid, parseDateTime64BestEffortOrZero(toString(record.createdAt), 3) as created_at, event_time, - now64(3) as indexed_at, + indexed_at, if(operation = 'delete', event_time, toDateTime64(0, 3)) as deleted_at FROM raw_records WHERE collection = 'app.bsky.actor.profile' diff --git a/crates/weaver-index/migrations/clickhouse/018_notebooks_mv.sql b/crates/weaver-index/migrations/clickhouse/018_notebooks_mv.sql --- a/crates/weaver-index/migrations/clickhouse/018_notebooks_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/018_notebooks_mv.sql @@ -13,7 +13,7 @@ parseDateTime64BestEffortOrZero(toString(record.createdAt), 3) as created_at, parseDateTime64BestEffortOrZero(toString(record.updatedAt), 3) as updated_at, event_time, - now64(3) as indexed_at, + indexed_at, if(operation = 'delete', event_time, toDateTime64(0, 3)) as deleted_at, record FROM raw_records diff --git a/crates/weaver-index/migrations/clickhouse/021_entries_mv.sql b/crates/weaver-index/migrations/clickhouse/021_entries_mv.sql --- a/crates/weaver-index/migrations/clickhouse/021_entries_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/021_entries_mv.sql @@ -12,7 +12,7 @@ parseDateTime64BestEffortOrZero(toString(record.createdAt), 3) as created_at, parseDateTime64BestEffortOrZero(toString(record.updatedAt), 3) as updated_at, event_time, - now64(3) as indexed_at, + indexed_at, if(operation = 'delete', event_time, toDateTime64(0, 3)) as deleted_at, record FROM raw_records diff --git a/crates/weaver-index/migrations/clickhouse/024_drafts_mv.sql b/crates/weaver-index/migrations/clickhouse/024_drafts_mv.sql --- a/crates/weaver-index/migrations/clickhouse/024_drafts_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/024_drafts_mv.sql @@ -7,7 +7,7 @@ cid, parseDateTime64BestEffortOrZero(toString(record.createdAt), 3) as created_at, event_time, - now64(3) as indexed_at, + indexed_at, if(operation = 'delete', event_time, toDateTime64(0, 3)) as deleted_at FROM raw_records WHERE collection = 'sh.weaver.edit.draft' diff --git a/crates/weaver-index/migrations/clickhouse/026_edit_roots_mv.sql b/crates/weaver-index/migrations/clickhouse/026_edit_roots_mv.sql --- a/crates/weaver-index/migrations/clickhouse/026_edit_roots_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/026_edit_roots_mv.sql @@ -60,7 +60,7 @@ event_time as created_at, event_time, - now64(3) as indexed_at, + indexed_at, if(operation = 'delete', event_time, toDateTime64(0, 3)) as deleted_at FROM raw_records WHERE collection = 'sh.weaver.edit.root' diff --git a/crates/weaver-index/migrations/clickhouse/027_edit_diffs_mv.sql b/crates/weaver-index/migrations/clickhouse/027_edit_diffs_mv.sql --- a/crates/weaver-index/migrations/clickhouse/027_edit_diffs_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/027_edit_diffs_mv.sql @@ -68,7 +68,7 @@ coalesce(parseDateTime64BestEffortOrNull(toString(record.createdAt), 3), event_time) as created_at, event_time, - now64(3) as indexed_at, + indexed_at, if(operation = 'delete', event_time, toDateTime64(0, 3)) as deleted_at FROM raw_records WHERE collection = 'sh.weaver.edit.diff' diff --git a/crates/weaver-index/migrations/clickhouse/030_collab_invites_mv.sql b/crates/weaver-index/migrations/clickhouse/030_collab_invites_mv.sql --- a/crates/weaver-index/migrations/clickhouse/030_collab_invites_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/030_collab_invites_mv.sql @@ -17,7 +17,7 @@ parseDateTime64BestEffortOrZero(toString(record.expiresAt), 3) as expires_at, coalesce(parseDateTime64BestEffortOrNull(toString(record.createdAt), 3), event_time) as created_at, event_time, - now64(3) as indexed_at, + indexed_at, if(operation = 'delete', event_time, toDateTime64(0, 3)) as deleted_at FROM raw_records WHERE collection = 'sh.weaver.collab.invite' diff --git a/crates/weaver-index/migrations/clickhouse/032_collab_accepts_mv.sql b/crates/weaver-index/migrations/clickhouse/032_collab_accepts_mv.sql --- a/crates/weaver-index/migrations/clickhouse/032_collab_accepts_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/032_collab_accepts_mv.sql @@ -18,7 +18,7 @@ coalesce(parseDateTime64BestEffortOrNull(toString(record.createdAt), 3), event_time) as created_at, event_time, - now64(3) as indexed_at, + indexed_at, if(operation = 'delete', event_time, toDateTime64(0, 3)) as deleted_at FROM raw_records WHERE collection = 'sh.weaver.collab.accept' diff --git a/crates/weaver-index/migrations/clickhouse/034_collab_sessions_mv.sql b/crates/weaver-index/migrations/clickhouse/034_collab_sessions_mv.sql --- a/crates/weaver-index/migrations/clickhouse/034_collab_sessions_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/034_collab_sessions_mv.sql @@ -16,7 +16,7 @@ coalesce(parseDateTime64BestEffortOrNull(toString(record.createdAt), 3), event_time) as created_at, parseDateTime64BestEffortOrZero(toString(record.expiresAt), 3) as expires_at, event_time, - now64(3) as indexed_at, + indexed_at, if(operation = 'delete', event_time, toDateTime64(0, 3)) as deleted_at FROM raw_records WHERE collection = 'sh.weaver.collab.session' diff --git a/crates/weaver-index/migrations/clickhouse/039_notebook_entries_mv.sql b/crates/weaver-index/migrations/clickhouse/039_notebook_entries_mv.sql --- a/crates/weaver-index/migrations/clickhouse/039_notebook_entries_mv.sql +++ b/crates/weaver-index/migrations/clickhouse/039_notebook_entries_mv.sql @@ -14,7 +14,7 @@ -- Position from array index assumeNotNull(entry_position) as position, - now64(3) as indexed_at + indexed_at FROM notebooks ARRAY JOIN record.entryList[].uri as entry_uri, -- tangled.sh