Routing and Navigation
Curiosity workspaces are single-page apps. The URL hash (#/path?key=value) is the entire state — Router resolves it to a view, and App.Sidebar exposes hooks to inject your own navigation buttons.
Registering routes
Router.Register(path, handler) maps a route to an action. Register inside the same place the built-in routes are defined — the partial App.DefaultRouting.Define() method.
public static partial class App
{
public static class CustomRouting
{
public static void Define()
{
Router.Register("devices", state => App.ShowDefault(new DevicesListView()));
Router.Register("device", state =>
{
var key = state.TryGetValue("key", out var k) ? k : null;
if (key is null) { Router.Navigate("#/devices"); return; }
App.ShowDefault(new DeviceDetailView(key));
});
}
}
}
Call App.CustomRouting.Define() from the same module-level initialization that runs App.DefaultRouting.Define().
state is a Dictionary<string,string> of the hash query parameters — #/device?key=MBP14 becomes state["key"] == "MBP14".
Navigating
Router.Navigate("#/devices"); // Just the path
Router.Navigate("#/device", ("key", "MBP14")); // Path + params
Router.Back(); // Browser history back
For external links, fall back to window.location.href — Router is for app-internal navigation only.
Customizing the sidebar
Attach to App.Sidebar lifecycle events to add buttons. The two most useful ones:
| Event | Fires when |
|---|---|
OnSidebarRebuild_BeforeFooter |
After main entries, before the footer (Settings, Help). |
OnSidebarRebuild_AfterHeader |
After the workspace logo, before the search box. |
App.Sidebar.OnSidebarRebuild_BeforeFooter += (sidebar, mode, tracker) =>
{
sidebar.AddContent(
new SidebarButton("devices", UIcons.Box, "Devices")
.OnClick(() => Router.Navigate("#/devices")));
sidebar.AddContent(
new SidebarButton("cases", UIcons.Headset, "Cases")
.OnClick(() => Router.Navigate("#/cases")));
};
mode tells you whether the sidebar is in expanded or collapsed form — useful if you want to hide labels in collapsed mode.
Highlighting the active route
SidebarButton.IsActiveWhen(path) keeps the button highlighted when the route matches:
new SidebarButton("devices", UIcons.Box, "Devices")
.OnClick(() => Router.Navigate("#/devices"))
.IsActiveWhen("devices");
Hiding the default navigation
For full-screen views (onboarding flows, fullscreen visualizations) call:
App.HideNavigation();
App.Show(new MyFullScreenView(), title: "Onboarding");
To restore: App.ShowNavigation().
Patterns
- Tabs as routes. A
Tabscomponent can driveRouter.Navigateon tab change so the URL reflects the visible tab. Deep links survive reload. - Modals don't change the route. Use
Modal.Show(...)for transient UI; reserve routes for state worth bookmarking. - Parameter validation. Validate
statevalues inside the route handler before passing them to a view — bad URLs are common (users share them).
Cross-links
- State management — observables for reactive views.
- Tesserae basics — layout and composition.
- Curiosity components — the pre-built widgets you'll wire into your routes.