From 661edc09fa1063673e84b63d2dcb5cfbe0f91232 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Fri, 6 Feb 2026 16:24:56 -0300 Subject: [PATCH] Fix 404 errors for static assets with bot name prefixes Removed root-level static routes (/:dir/*path) that conflicted with bot name prefixes like /edu. Now all assets are served under /suite/ path, allowing the fallback handler to properly detect and strip bot prefixes before resolving file paths. Fixes: - /edu/suite/js/api-client.js now returns 200 OK - /edu/suite/css/apps-extended.css now returns 200 OK - /edu/suite/admin/admin.js now returns 200 OK - Base href injection working correctly for bot prefixes --- src/ui_server/mod.rs | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/ui_server/mod.rs b/src/ui_server/mod.rs index 98e3311..f0fce24 100644 --- a/src/ui_server/mod.rs +++ b/src/ui_server/mod.rs @@ -164,7 +164,11 @@ pub async fn index(OriginalUri(uri): OriginalUri) -> Response { let mut start_idx = 1; let known_dirs = ["suite", "js", "css", "vendor", "assets", "public", "partials", "settings", "auth", "about", "drive", "chat", "tasks", "admin", "mail", "calendar", "meet", "docs", "sheet", "slides", "paper", "research", "sources", "learn", "analytics", "dashboards", "monitoring", "people", "crm", "tickets", "billing", "products", "video", "player", "canvas", "social", "project", "goals", "workspace", "designer"]; - if path_parts.len() > start_idx && !known_dirs.contains(&path_parts[start_idx]) { + // Skip bot name if present (first segment is not a known dir, second segment is) + if path_parts.len() > start_idx + 1 + && !known_dirs.contains(&path_parts[start_idx]) + && known_dirs.contains(&path_parts[start_idx + 1]) + { start_idx += 1; } @@ -175,7 +179,7 @@ pub async fn index(OriginalUri(uri): OriginalUri) -> Response { let full_path = get_ui_root().join(&fs_path); - debug!("index: Serving static file: {} -> {:?} (fs_path: {})", path, full_path, fs_path); + info!("index: Serving static file: {} -> {:?} (fs_path: {})", path, full_path, fs_path); #[cfg(feature = "embed-ui")] { @@ -1149,36 +1153,28 @@ async fn handle_embedded_root_asset( fn add_static_routes(router: Router, _suite_path: &Path) -> Router { #[cfg(feature = "embed-ui")] { - let mut r = router - .route("/suite/:dir/*path", get(handle_embedded_asset)) - .route("/:dir/*path", get(handle_embedded_asset)); + let mut r = router.route("/suite/:dir/*path", get(handle_embedded_asset)); - // Add root files + // Add root files only under /suite/ for file in ROOT_FILES { - r = r - .route(&format!("/{}", file), get(handle_embedded_root_asset)) - .route(&format!("/suite/{}", file), get(handle_embedded_root_asset)); + r = r.route(&format!("/suite/{}", file), get(handle_embedded_root_asset)); } r } #[cfg(not(feature = "embed-ui"))] { let mut r = router; - // Serve suite directories at BOTH root level and /suite/{dir} path - // This allows HTML files to reference js/vendor/file.js directly + // Only serve suite directories under /suite/{dir} path + // Root-level paths (/:dir) are handled by fallback to support bot prefixes for dir in SUITE_DIRS { let path = _suite_path.join(dir); - info!("Adding route for /{} -> {:?}", dir, path); - r = r.nest_service(&format!("/{dir}"), ServeDir::new(path.clone())); info!("Adding route for /suite/{} -> {:?}", dir, path); r = r.nest_service(&format!("/suite/{dir}"), ServeDir::new(path.clone())); } for file in ROOT_FILES { let path = _suite_path.join(file); - r = r - .nest_service(&format!("/{}", file), ServeFile::new(path.clone())) - .nest_service(&format!("/suite/{}", file), ServeFile::new(path)); + r = r.nest_service(&format!("/suite/{}", file), ServeFile::new(path)); } r } @@ -1190,18 +1186,16 @@ pub fn configure_router() -> Router { let mut router = Router::new() .route("/health", get(health)) + .route("/favicon.ico", get(serve_favicon)) .nest("/api", create_api_router()) .nest("/ui", create_ui_router()) .nest("/ws", create_ws_router()) .nest("/apps", create_apps_router()) - .route("/favicon.ico", get(serve_favicon)); + .route("/", get(index)) + .route("/minimal", get(serve_minimal)) + .route("/suite", get(serve_suite)); router = add_static_routes(router, &suite_path); - router - .route("/", get(index)) - .route("/minimal", get(serve_minimal)) - .route("/suite", get(serve_suite)) - .fallback(get(index)) - .with_state(state) + router.fallback(get(index)).with_state(state) }