@judo/app-shell
v0.1.1
Published
> Admin dashboard application shell with multi-level navigation, multi-actor support, theme management, and badge notifications
Readme
@judo/app-shell
Admin dashboard application shell with multi-level navigation, multi-actor support, theme management, and badge notifications
Purpose
The Layer 5 (UI) entry point that orchestrates layout, routing, theming, navigation, user menu, actor switching, error boundaries, and the full runtime bootstrap. Supports both vertical (sidebar drawer) and horizontal (top bar) menu layouts, configurable via the Application.defaultMenuLayout model property or the menuLayout prop. Depends on nearly every other package in the monorepo.
Architecture Layer
Layer 5 (UI) — the top-level shell consumed by standalone and application-level code.
Dependencies
| Workspace | External (Peer) |
| -------------------- | -------------------------------------------------------------- |
| @judo/actions | @emotion/react, @emotion/styled |
| @judo/auth | @mui/icons-material, @mui/material |
| @judo/components | @mui/x-data-grid-pro (optional), @mui/x-license (optional) |
| @judo/core | react, react-router |
| @judo/expressions | |
| @judo/feedback | |
| @judo/guards | |
| @judo/i18n | |
| @judo/model-api | |
| @judo/model-loader | |
| @judo/test-ids | |
File Structure
src/
├── index.ts # Public API barrel
├── AppShell.tsx # Main shell wrapper (ThemeProvider + AppErrorBoundary + AppShellLayout)
├── actors/
│ └── ActorSelector.tsx # Multi-actor dropdown with realm-change confirmation
├── errors/
│ └── AppErrorBoundary.tsx # Root class-based error boundary
├── hooks/
│ ├── use-navigation-badge.ts # Badge hook (stub — always returns null)
│ └── use-theme.ts # Singleton ThemeStore + useTheme hook + initializeThemeStore
├── layout/
│ ├── AppBar.tsx # Top app bar (menu toggle, logo, actor selector, locale, theme, hero)
│ ├── AppShellLayout.tsx # Main layout (drawer + bar + content + footer + dialog host)
│ ├── Breadcrumbs.tsx # Breadcrumb trail from NavigationContext page stack
│ ├── Footer.tsx # Application footer (model name or custom footerText)
│ ├── PageDialogHost.tsx # Dialog stack renderer (all mounted, only topmost interactive)
│ └── layout-constants.ts # Drawer width constants (DRAWER_WIDTH_EXPANDED/COLLAPSED)
├── locale/
│ └── LocaleSwitcher.tsx # AppBar locale dropdown (globe icon, 2+ locales required)
├── navigation/
│ ├── NavigationDrawer.tsx # Persistent sidebar drawer (vertical mode)
│ ├── NavigationItemRenderer.tsx # Recursive vertical menu item with badge/icon/tooltip
│ ├── HorizontalNavigation.tsx # Top-bar nav container (horizontal mode)
│ ├── HorizontalNavigationItem.tsx # Recursive horizontal nav item + cascading sub-menus
│ ├── navigation-utils.ts # Route generation, filtering, localStorage persistence
│ └── dashboard-utils.ts # Dashboard route resolution from navigation tree
├── routes/
│ ├── generate-routes.tsx # Route tree generation from Application model
│ ├── DefaultGuestPage.tsx # Built-in guest access page (sign-in CTA)
│ ├── DefaultSettingsPage.tsx # Built-in settings placeholder
│ ├── PageRoute.tsx # Single page route wrapper with transfer ID generation
│ └── RedirectPage.tsx # /_redirect route — renders customizable RedirectHandler
├── runtime/
│ ├── types.ts # All config/props/state types
│ ├── JudoRuntime.tsx # Zero-boilerplate bootstrap (includes inlined AuthBridge, AuthTokenSync, NavigationProviderBridge, ApplicationRoutes)
│ ├── PrincipalBridge.tsx # Wires PrincipalProvider with API client
│ ├── DefaultLoadingScreen.tsx # Loading spinner screen
│ ├── DefaultErrorScreen.tsx # Error + retry screen
│ └── index.ts # Runtime barrel exports
├── theme/
│ ├── ThemeProvider.tsx # MUI ThemeProvider wrapper (mode, colors, density, muiThemeOptions)
│ ├── ThemeToggle.tsx # Light → dark → system toggle button
│ └── density.ts # Density level theme options (compact/comfortable/standard)
└── user/
├── DefaultHeroComponent.tsx # Built-in authenticated hero (avatar, profile, settings, logout)
└── UserMenu.tsx # Legacy user avatar dropdown (separate from DefaultHeroComponent)Exports Summary
Runtime Bootstrap
| Export | Kind | Description |
| ---------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| JudoRuntime | component | Zero-boilerplate bootstrap. Loads model via loader, creates registry, builds REST API client, wires full provider tree, and auto-generates routes. Shows loading/error screens during init. Supports retry. |
| JudoRuntimeConfig | interface | Aggregates: model, auth, api, theme, features, i18n, router, muiProLicenseKey, menuLayout. |
| JudoRuntimeProps | interface | Props for JudoRuntime — config, modelSource, logo, appBarExtra, loadingComponent, errorComponent, children, customizations. |
| JudoRuntimeState | interface | Internal state: phase (initializing/loading/ready/error) + error. |
| ModelConfig | interface | Model URL or async loader + which app to activate. |
| AuthConfig | interface | OIDC / skip-auth config. |
| ApiConfig | interface | REST API endpoint config (baseUrl, timeout, headers). |
| ThemeConfig | interface | Theme customization (defaultMode, colors, muiThemeOptions, density). |
| FeaturesConfig | interface | Feature flags (guards, forceShowTotalCountForLazyTables, headerFilters). |
| I18nConfig | interface | Locale config (locales, localeLoader, translationKeyMap, persistLocale). |
| RouterConfig | interface | Router type (browser/hash/memory) and basePath. |
| DefaultLoadingScreen | component | Full-viewport centered CircularProgress + "Loading application..." text. |
| DefaultErrorScreen | component | Full-viewport error display with optional retry button. |
| DefaultGuestPage | component | Built-in guest access page. Centered Paper with app title, instructional text, and "Sign In" button. Used as fallback when no custom guestComponent is set. Receives GuestPageProps. |
Application Shell
| Export | Kind | Description |
| ------------------ | --------- | --------------------------------------------------------------------------------------------------------- |
| AppShell | component | Wraps children in ThemeProvider + AppErrorBoundary + AppShellLayout. Requires ApplicationContext. |
| AppErrorBoundary | component | Root React error boundary. Catches unhandled errors, displays error + "Refresh Page" button. |
Layout
| Export | Kind | Description |
| ---------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AppShellLayout | component | Main dashboard layout. In vertical mode (default): drawer open/collapsed state + NavigationDrawer. In horizontal mode: no drawer, navigation embedded in AppBar. Includes Footer and PageDialogHost. Accepts optional menuLayout prop overriding model's Application.defaultMenuLayout. |
| AppBar | component | Fixed top bar with menu toggle (vertical only), logo/app name, ActorSelector, HorizontalNavigation (horizontal only), LocaleSwitcher, ThemeToggle, and DefaultHeroComponent (auth-required apps). Resolves profilePictureUrl and settings/profile navigation. |
| Breadcrumbs | component | Breadcrumb trail from NavigationContext page stack. Clickable entries navigate back. Uses useModelLabel and usePageTitle for translated labels. |
| PageDialogHost | component | Renders dialog stack from NavigationContext. All dialogs mounted simultaneously — non-topmost hidden via CSS. Each dialog is a SinglePageDialog with full provider tree (PageProvider, ValidationProvider, DispatchProvider, etc.). |
Navigation
| Export | Kind | Description |
| ------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| NavigationDrawer | component | Persistent MUI Drawer with collapse toggle, search filter, and recursive item list. Persists expanded state to localStorage. Applies menu customizer from customizations. Vertical mode only. |
| NavigationItemRenderer | component | Recursive vertical nav item with icon (via IconRenderer), badge, active-state highlighting, expand/collapse, tooltip when collapsed. Uses useModelLabel for translated labels. |
| HorizontalNavigation | component | Top-bar navigation container rendering items as a flex row of buttons. Applies menu customizer. No search. Horizontal mode only. |
| HorizontalNavigationItem | component | Recursive horizontal nav item. Top-level: toolbar Button with dropdown Menu for children. Nested: cascading MenuItem sub-menus via HorizontalNavigationSubItem. Supports N-deep nesting, badges, icons, active-state. |
| filterNavigationItems(items, query) | function | Recursively filters nav items by label/name matching (case-insensitive). |
| getRouteForPage(pageRef) | function | Generates a URL path from a PageDefinition (resolved or string reference). Uses container type for route suffix. |
| getDashboardRoute(application) | function | Finds the first isDashboard page referenced in navigation, returns its route. Falls back to "/". |
Routing
| Export | Kind | Description |
| ----------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| generateRoutes(props) | function | Generates a React Router route tree from Application. Adds dashboard route, maps each page to PageRoute, adds /_redirect, /settings, optional customRoutes, and 404 catch-all. Accepts GenerateRoutesProps. |
| PageRoute | component | Wraps a page in PageProvider + PageActionOverridesProvider + VisualPropertiesProvider. Generates stable transfer IDs for TABLE/VIEW pages. Extracts signedIdentifier from URL params. Renders PageRenderer. |
| RedirectPage | component | Rendered at /_redirect. Looks up redirectHandler from customizations and renders it; shows fallback message if none configured. |
| DefaultSettingsPage | component | Built-in placeholder settings page at /settings. Uses i18n for labels. Replaced when CustomizationsConfig.settingsPage is provided. |
Locale
| Export | Kind | Description |
| ---------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| LocaleSwitcher | component | Globe icon button + dropdown menu for switching locales. Only renders when 2+ locales configured. Uses useLocaleOptional() for safe fallback outside I18nProvider. Positioned before ThemeToggle in AppBar. |
Theme
| Export | Kind | Description |
| ---------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| ThemeProvider | component | Creates MUI theme from config (mode, colors, density, muiThemeOptions) and wraps children in MuiThemeProvider + CssBaseline. |
| ThemeToggle | component | IconButton cycling light → dark → system → light. Shows next-mode icon. |
| useTheme() | hook | Returns current theme config and setter. Backed by singleton LocalThemeStore via useSyncExternalStore. |
| initializeThemeStore(config, useSystemPreference?) | function | Called by JudoRuntime before rendering. Applies runtime config. Preserves user's saved localStorage preference. |
| hasUserThemePreference() | function | Returns true if user has saved a theme mode or system preference to localStorage. |
| createDensityThemeOptions(density) | function | Returns MUI ThemeOptions for the given DensityLevel (compact/comfortable/standard). Applied as base layer before muiThemeOptions. |
| DensityLevel | type | Union type: "compact" \| "comfortable" \| "standard". |
Actors & User
| Export | Kind | Description |
| ---------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ActorSelector | component | MUI Select dropdown for switching between applications/actors. Hidden when ≤1 app. Uses useActorSwitch for realm-change confirmation. Navigates to dashboard after switch. |
| DefaultHeroComponent | component | Built-in hero for authenticated actors. Avatar button + dropdown with user info, Profile (conditional on profilePage), Settings (always), and Log Out. Uses i18n for labels, supports profilePictureUrl. Rendered by AppBar when no custom heroComponent configured. |
| UserMenu | component | Standalone user avatar button + dropdown. Not used by AppBar (which uses DefaultHeroComponent instead). Kept for custom layouts. |
Hooks
| Hook | Description |
| -------------------------- | -------------------------------------------------------------------------------- |
| useTheme() | Theme config access via useSyncExternalStore over singleton LocalThemeStore. |
| useNavigationBadge(item) | Stub — always returns null. Reserved for future badge service. |
Key Patterns
- Dual menu layout:
AppShellLayoutreadsApplication.defaultMenuLayoutfrom the model (or acceptsmenuLayoutprop override).VERTICALrendersNavigationDrawersidebar with search/collapse/expand.HORIZONTALhides the drawer, hides the hamburger toggle, and embedsHorizontalNavigationinside theAppBartoolbar with cascading dropdown sub-menus. Both modes use the sameNavigationControllermodel and support N-deep menu trees. - Singleton external store for theme:
LocalThemeStoreclass withsubscribe/getSnapshotavoids React context for theme state; supports OS preference viamatchMediaand localStorage persistence - Provider tree composition:
JudoRuntimeassembles the entire provider stack in fixed order (Customizations → Theme → Feedback → API → RuntimeConfig → MuiPro → Application → Expressions → I18n → Auth → Data → Router → Navigation → AppShellLayout) - Model-driven routing:
generateRoutes+getRouteForPage+PageRoutederive URL paths entirely fromPageDefinitionobjects and their resolved containers (TABLE/FORM/VIEW) - Dialog-as-page:
PageDialogHostreads dialog state fromNavigationContextand renders a full page inside a MUI Dialog with its own PageProvider, ValidationProvider, DispatchProvider, and SelectorSelectionProvider - Stable transfer IDs: Both
PageRouteandPageDialogHostgenerate deterministic transfer IDs (page::,dialog::,transfer::) to key data store entries - Class-based error boundary:
AppErrorBoundaryuses React class component lifecycle since function components can't catch render errors - User preference preservation:
initializeThemeStorechecks localStorage before applying runtime config, ensuring user's chosen mode is never overwritten - Multi-actor support with realm safety:
ActorSelectorusesuseActorSwitchfor confirmation when switching between actors with different OIDC realms - Menu customizer: Both
NavigationDrawerandHorizontalNavigationapply themenuCustomizerfunction fromCustomizationsConfigto transform navigation items before rendering - Hero component pattern:
AppBarrendersDefaultHeroComponentfor authenticated actors by default; apps can replace it viaCustomizationsConfig.heroComponent. Both receiveHeroComponentProps. - Density theming:
createDensityThemeOptionsprovides three preset density levels that adjust spacing, font sizes, and component sizes across the entire UI - Footer:
AppShellLayoutincludes aFootercomponent at the bottom that displays either customfooterTextfromCustomizationsConfigor a default© {year} {modelName}string - Lazy code splitting: Container renderers are lazy-loaded via
React.lazyinPageRenderer
