PrimeBrickPrimeBrick
  • Docs
  • Contact
  • MIT License
  • Documentation
  • MCP Server
  • API Catalog
  • Services
  • Libraries
PrimeBrickPrimeBrick

© 2026 PrimeBrick. MIT License. v3.11.2

github
Backend
Frontend
    Developer Guide
      OverviewGetting startedUI stackApp shell & sidebarAuthentication & sessionsSystem settingsUI componentsUI patternsIcon ConventionsInput AnatomyField Hints & TooltipsButton Style StandardsEntity metadatai18n — Translations on the FrontendThe AI Tool Loop (caller-driven)Error HandlingComposables (use* hooks)
      Components
      API Reference
Microservices
powered by Zudoku
Developer Guide

App shell & sidebar

The application shell is the single layout that wraps every authenticated route. It is mounted once in src/routes/(app)/+layout.svelte, which fetches the user profile, restores the last visited route, and renders <AppShell>.

Structure

AppShell.svelte is responsible for bootstrapping the runtime: probing backend health, loading shell navigation, starting services polling, and registering global error handlers (unhandledrejection, window error). When the backend is offline or the DB/IDP is degraded, health is re-probed every 5 seconds; on recovery the modules list is reloaded.

Topbar

AppTopbar.svelte is a 3-column grid:

  • Left: sidebar trigger (collapse/expand)
  • Center: CommandPalette
  • Right: detected IANA timezone, LangSelect, errors button (badge with impact-colored count, opens the errors sheet), notifications, ThemeToggle

Module navigation

Module loading and selection is centralized in src/lib/shell/modules-shell.svelte.ts (exported as shellNav).

FunctionPurpose
loadShellNav()Fetches the access-filtered GET /api/v1/modules payload — every record embeds nav, routes and per-root home
selectModule(id)Selects a module — navigation comes from the already-loaded ModuleInfo, no second request
reloadModuleNav()Rebuilds the current module's navigation from the loaded payload
syncModuleFromRoute(pathname)Auto-selects the module that owns the current route via route_prefixes
getLastRoute() / saveLastRoute(pathname)localStorage persistence of the last visited route

When the user navigates to a route, syncModuleFromRoute matches the pathname against each module's route_prefixes and switches module automatically, loading that module's navigation structure.

SidebarAppLauncher renders the Finder-style launcher that replaces the old root/module switchers: one trigger ({app} / {module}) opens a three-column panel Applications | Modules | Pages.

Application launcher (Finder cascade)

The launcher is always visible, even when a single app level exists. Modules carry a part_of field (/api/v1/modules) — the root level they are part of — distinct from the module code:

  • part_of: "system" → module lives under /system/<code> (settings, job, emailsender, webhook, ai…)
  • part_of === code → the module IS its own root level and the namespace collapses to /<code> (crm → /crm)

A module may also declare extra root memberships — route_prefixes entries of the form /{other-root}/{code} (e.g. crm owns /crm/* plus /system/crm/pipeline). Every prefix's first segment becomes an app level the module lists under; part_of alone decides where is_default applies.

The payload is already access-filtered server-side — a user only ever sees the modules/pages their access.{app}.{module}.{page} grants cover (see the BE dev-guide for the grammar). The FE never re-filters.

Columns, left to right — click/tap descends into the next column (Finder-style), hover only highlights, and every row carries an icon-only goto CTA (Enter/Space) that navigates without descending:

  1. Applications — every NAVIGABLE root level: a root appears only when at least one module has home[root] (≥1 reachable page under it post access-filter). Click = descend; goto CTA = the app's declared is_default module's first page under that app (home[root]).
  2. Modules — modules NAVIGABLE under the selected app (home[root] present — declared membership without a reachable page is routing-domain only and never lists). Click = descend; goto CTA = home[root]. Labels resolve from the embedded nav/label_key in the same payload — no per-module meta fetch.
  3. Pages — the selected module's nav links scoped to the selected app (a multi-root module shows only that app's slice). Click = direct navigation.

The current route marks its ancestors in all three columns; the shown "current app" is the ROUTE's root (shellNav.rootForRoute), so a crm page at /system/crm/pipeline marks System, not CRM.

Module labels come from ONE source: service_registry.label_key (ModuleInfo.label_key), resolved by resolveModuleLabel(module, t) against the module's own dictionary with the raw name as the only fallback. The launcher trigger, the Modules column, moduleBreadcrumbSegments (breadcrumb) and the service-admin pages all share it — "Impostazioni" everywhere, never a registry-name drift. Row goto CTAs and the keyboard footer use app.nav.goTo ("Vai a"/"Go to"); app.nav.open stays on the sidebar open-trigger where "open navigation" is the literal action.

The root segment is also the first breadcrumb segment (auto-prepended by AppPageBreadcrumb), giving {app} / {module} / {page}. The sidebar nav links are likewise root-scoped — under /system/crm/* only the crm pages declared below /system/crm render.

Organization switcher

SidebarOrgSwitcher fetches active organizations from /api/v1/system/organizations/active and lets the user pick one. The selected org is tracked in local state.

Code
type ActiveOrg = { uuid: string; idp_code: string; idp_name: string; display_name: string; avatar: string | null; };

Profile menu

SidebarProfileMenu shows the user avatar (with avatar_color / avatar_initials from the profile), display name, and email. The dropdown contains a Settings action (switches to the settings module and navigates to /system/settings/profile) and a Sign out action that calls the onLogout callback (clears cookies, redirects to /login).

Health badge

SidebarHealthBadge uses the useHealthChip() composable and reads backendState.healthChip. The chip state drives the icon and color:

StateIconColor
backend_offlineCloudOffred
db_offlineDatabasered
idp_offlineShieldAlertorange
okCloudgreen
loading—gray

Clicking the badge opens the shell.versions sheet, which shows detailed version and health information.

Version badge

SidebarVersionBadge displays APP_VERSION from $lib/version in a monospace v{version} badge. It is hidden when the sidebar is collapsed on desktop but always visible on mobile. Clicking it also opens the shell.versions sheet.

Services store

src/lib/services-store.svelte.ts polls microservice status every 30s (startServicesPolling(30000)), started by AppShell on mount. It exposes probeServices(), aggregateStatus(instances), and groupByCode(services), which the Settings → Modules page uses to render grouped service cards with per-instance status badges (online, going_live, offline, unknown).

Global side sheets

The right-hand sheet is not route-owned. A single SheetHost is mounted in AppShell and picks the panel component from a registry by sheetState.panelId. Open a panel with openSheet('<id>', props, { contentClass, side }) from a click handler — never from an $effect tied to a bindable open flag (that causes re-open loops while the sheet is closing).

Built-in shell panels live in src/lib/shell/sheets/panels/ (errors, versions). Entity-list panels (columns, filters, search-in, preview, version history) live in src/lib/entity-list/sheets/panels/.

Next steps

  • Authentication & sessions
  • System settings
  • API reference
Last modified on October 28, 2026
UI stackAuthentication & sessions
On this page
  • Structure
  • Topbar
  • Module navigation
  • Application launcher (Finder cascade)
  • Organization switcher
  • Profile menu
  • Health badge
  • Version badge
  • Services store
  • Global side sheets
  • Next steps
TypeScript