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

Web app

The @africaos/web app - its routes, guards, shell components, and server functions, and how they compose into the product surface.

@africaos/web is the main product surface: the unified shell where users sign in, manage their organizations, and use application modules. This page documents its structure and the code that makes it work.

The route tree

src/routes/
├── __root.tsx                       root layout - providers, theme, router outlet
├── index.tsx                        landing/redirect when unauthenticated
├── login.tsx                        sign-in
├── onboarding.tsx                   organization onboarding
├── _authenticated.tsx               guard: requires a session
├── _authenticated/index.tsx         the launcher (application cards)
├── _authenticated/$organization.tsx organization shell (sidebar + header)
└── _authenticated/$organization/$application.tsx  mounts an application

The auth guard

_authenticated.tsx enforces authentication before any protected page renders:

export const Route = createFileRoute("/_authenticated")({
  beforeLoad: async () => {
    const session = await getSessionFn();
    if (!session) {
      throw redirect({ to: "/login" });
    }
  },
});

getSessionFn is a server function backed by AuthService.getSession. Unauthenticated visitors are redirected to /login.

The organization shell

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

export const Route = createFileRoute("/_authenticated/$organization")({
  beforeLoad: async ({ params }) => {
    const organization = await resolveOrganizationFn({
      data: { organizationSlug: params.organization },
    });
    if (!organization) {
      throw redirect({ to: "/" }); // not an active membership → first org
    }
  },
  component: OrganizationLayout,
});

function OrganizationLayout() {
  const { organization } = useParams({ from: Route.id });
  return (
    <SidebarProvider>
      <AppSidebar organizationSlug={organization} />
      <SidebarInset>
        <Header />
        <div className="flex flex-1 flex-col gap-4 p-4 pt-0">
          <Outlet />
        </div>
      </SidebarInset>
    </SidebarProvider>
  );
}

Two things matter:

  • beforeLoad verifies the slug server-side. resolveOrganizationFn runs the request scope, which requires the organization to exist and be an active membership. The shell never trusts the URL segment alone.
  • The layout composes the sidebar and header from @africaos/ui’s Sidebar set.

The launcher

routes/_authenticated/index.tsx is the dashboard. It calls a server function that lists the enabled applications for the active organization (RegistryService.listForOrganization) and renders a card per application from registry metadata - icon, color, name, description, route. It is fully data-driven.

The application route

routes/_authenticated/$organization/$application.tsx mounts an application:

const Mount = resolveApplicationMount(applicationSlug);
return Mount ? <Mount organizationSlug={organizationSlug} /> : <NotAvailableState />;

resolveApplicationMount looks the slug up in applicationMounts (see Application registry). Applications with no mount render the “not available yet” state.

Server functions

The web app keeps its server functions under src/features/<name>/services/:

  • features/auth/services/auth.functions.ts - session, sign-in, sign-up, sign-out.
  • features/organizations/services/org.functions.ts - resolve organization, list organizations, create.
  • features/applications/... - launcher data.

Every function runs through the request runtime (runRequest/runRequestFolded), so each call authenticates, resolves the tenant, and carries permissions.

The app sidebar and header

  • AppSidebar (src/components/app-sidebar.tsx) - brand, organization switcher, navigation. Links point into the current tenant (/${organizationSlug}/...).
  • Header (src/components/header.tsx) - page chrome and actions.

Both are composed from @africaos/ui primitives and stay thin.

The composition rule

The web app is the composition root. It wires the request context, auth, organizations, the application registry, and the application mounts together. Application modules never import the web app - they are mounted by it.

Next steps

Last updated on August 18, 2026