Skip to content
Africa OS Knowledgebase
Esc
navigateopen⌘Jpreview
On this page

Unified shell

How the web app composes authentication, the sidebar, the organization switcher, and application mounts into one navigable product surface.

The unified shell is what users actually see: the web app (apps/web) that composes every application module into one navigable product. It provides the chrome - sidebar, header, organization switcher - and mounts applications at /:organization/:application.

The route tree

The shell is built on TanStack Start file-based routing (apps/web/src/routes):

routes/
├── _authenticated.tsx              auth guard: redirects unauthenticated users
├── _authenticated/index.tsx        the launcher (application cards)
├── _authenticated/$organization.tsx  org shell: sidebar + header + outlet
└── _authenticated/$organization/$application.tsx  mounts an application

The _authenticated group is the shell’s front door. Its guard checks the session server-side before any shell page renders.

The route lifecycle

The organization shell

routes/_authenticated/$organization.tsx renders the shell for one organization:

  • beforeLoad calls resolveOrganizationFn with the slug, which runs the request scope server-side. If the slug is not one of the user’s active memberships, it redirects to / (which lands on the first organization).
  • The layout composes SidebarProvider > AppSidebar + SidebarInset > Header + an Outlet.

The slug is verified server-side, so the shell never trusts the URL segment on its own.

The sidebar and header

  • AppSidebar (apps/web/src/components/app-sidebar.tsx) renders the brand, the organization switcher, and navigation. It takes the organizationSlug so links point into the current tenant.
  • Header (apps/web/src/components/header.tsx) carries the page chrome and actions.

Both are built from @africaos/ui primitives - see Frontend.

The launcher

routes/_authenticated/index.tsx is the dashboard. It queries RegistryService.listForOrganization (through a server function) and renders one card per enabled application, using registry metadata - icon, color, name, description, route. Selecting a card navigates to /:organization/:application.

The launcher is fully data-driven. There is no application-specific code in it.

Mounting an application

The $application route resolves the mount from app-registry.tsx:

  • If the slug has a lazy mount, it renders that component with organizationSlug as the only prop.
  • If not, it renders the “not available yet” state.

Each application renders through ApplicationMount, which is host-aware: inside the web app (embedded mode) it renders content only, because the shell already provides the sidebar. See The shell package for how applications structure themselves.

Composition responsibilities

The web app is the composition root. It wires together:

  • getSessionFn and auth server functions from @africaos/auth.
  • resolveOrganizationFn and org server functions from @africaos/organizations.
  • The request runtime from @africaos/request-context (every server function runs through it).
  • The application mounts from @africaos/* application packages.
  • The shell primitives from @africaos/shell.

Application modules depend on the shell and platform packages, never on the web app. This keeps every vertical swappable.

Next steps

Last updated on August 18, 2026