OSS-first docs

These docs teach the open system first: contracts, generated surfaces, runtimes, governance, and incremental adoption. Studio shows up as the operating layer on top, not as the source of truth.

AI index

Mobile navigation

Use one capability-backed navigation contract to drive both web and native shells. ContractSpec should own navigation intent; Next.js and Expo Router should still own platform routing, transitions, deep links, and file layouts.

Recommended architecture

Model navigation as a shared surface, then project it into platform chrome. The same NavSurfaceItemSpec list can become a web sidebar, a responsive browser drawer, native tabs, and nested native stacks after role, policy, and preference resolution.

Shared contract layer

  • Capability-backed NavSurfaceItemSpec entries
  • Role-aware NavRegistry resolution
  • NavGraphSpec relationships for discovery and suggestions
  • AdaptiveShellSpec invariants for safe shell adaptation

Web shell

  • Sidebar, rail, topbar, breadcrumbs, and command palette
  • Responsive collapse rules for narrow browser widths
  • Route metadata and canonical URLs
  • Keyboard-first navigation and page outline support

Native shell

  • Tab roots from items without parentGroupKey
  • Stack children from items with parentGroupKey
  • Gesture hints such as swipe-back or none
  • Native icon mapping from tabBarIcon or iconKey fallback

Declare one navigation item

Start from the contract surface, not from a shell-specific menu. Web uses route.web, groupKey, and iconKey. Native can additionally read tabBarIcon, gestureHint, and parentGroupKey.

import { defineNavSurfaceItem } from "@lssm-tech/lib.contracts-spec/navigation";

export const inboxNav = defineNavSurfaceItem({
  meta: {
    key: "inbox",
    version: "1.0.0",
    description: "Inbox entry point",
    goal: "Give operators one canonical place to resume work.",
    context: "Rendered as a sidebar item on web and a tab root on mobile.",
    owners: ["team-platform"],
    tags: ["navigation", "mobile"],
  },
  capability: { key: "inbox.read" },
  route: { web: "/workspace/inbox", api: "/api/workspace/inbox" },
  personaVisibility: ["operations_manager", "frontline_staff"],
  groupKey: "inbox",
  iconKey: "icon.inbox",
  densityHint: "comfortable",
  tabBarIcon: "tab.inbox",
  gestureHint: "swipe-back",
});

Setup flow

1. Declare navigation once

Create NavSurfaceItemSpec entries next to the bundle or module that owns the capability. Treat route.web as canonical, then add mobile extension hints instead of duplicating native route lists.

2. Resolve by role before rendering

Use NavRegistry to filter by RoleMorph personas, hidden groups, policy, and capability availability before either shell sees the item list.

3. Let each shell adapt presentation

The web shell can render sidebars, rails, breadcrumbs, and command search. The native shell can project the same items into tabs, stacks, gestures, and compact labels.

4. Keep app routers authoritative

ContractSpec declares navigation intent. Next.js and Expo Router still own files, layouts, loaders, deep links, auth redirects, and platform-specific transition behavior.

Project web and native shells

A unified shell does not mean identical UI. It means both shells consume the same resolved navigation model and apply platform-appropriate presentation rules after policy and RoleMorph filtering.

const tabRoots = navRegistry
  .resolveForRoleMorph(roleMorph, preferences)
  .filter((item) => item.parentGroupKey === undefined);

const stackChildren = navRegistry
  .resolveForRoleMorph(roleMorph, preferences)
  .filter((item) => item.parentGroupKey !== undefined);

// Web shell: render all visible items by groupKey.
// Native shell: render tabRoots in the tab bar, then mount stackChildren
// below the tab whose groupKey matches parentGroupKey.

Web shell vs native shell

On web, prefer a route-aware sidebar or rail with breadcrumbs and command search. On native, prefer tab roots for high-frequency areas and stack children for detail screens. In both cases, use the app router as the final authority and keep ContractSpec as the source of navigation intent, capability binding, and role-aware visibility.

Consumer checklist

  • Every navigation item references a registered navigation capability.
  • Every mobile tab root has a stable groupKey and tabBarIcon or icon fallback.
  • Every stack child declares parentGroupKey instead of appearing in the tab bar.
  • Unsafe or audit-critical screens use gestureHint: "none" when accidental dismissals would lose context.
  • Both shells consume the same resolved registry output for the active role, workspace, and preferences.
  • Deep links are tested in the host router; ContractSpec does not replace Next.js or Expo Router ownership.

Boundary to keep clear

ContractSpec is the source of truth for navigation contracts and safe adaptation evidence. It should not hide platform router behavior. Keep web files in the Next.js app shell, native files in the Expo shell, and shared navigation decisions in the bundle or module that owns the capability.