@elevasis/sdk 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/cli.cjs +75 -8
  2. package/dist/index.d.ts +1464 -749
  3. package/dist/index.js +74 -7
  4. package/dist/types/worker/adapters/crm.d.ts +20 -0
  5. package/dist/types/worker/adapters/index.d.ts +2 -0
  6. package/dist/types/worker/adapters/projects.d.ts +20 -0
  7. package/dist/worker/index.js +41 -1
  8. package/package.json +2 -2
  9. package/reference/_navigation.md +103 -5
  10. package/reference/_reference-manifest.json +72 -0
  11. package/reference/deployment/provided-features.mdx +64 -25
  12. package/reference/framework/index.mdx +2 -2
  13. package/reference/framework/project-structure.mdx +10 -8
  14. package/reference/index.mdx +3 -3
  15. package/reference/packages/core/src/README.md +34 -0
  16. package/reference/packages/core/src/organization-model/README.md +94 -0
  17. package/reference/packages/ui/src/api/README.md +18 -0
  18. package/reference/packages/ui/src/auth/README.md +18 -0
  19. package/reference/packages/ui/src/components/README.md +24 -0
  20. package/reference/packages/ui/src/execution/README.md +16 -0
  21. package/reference/packages/ui/src/features/README.md +28 -0
  22. package/reference/packages/ui/src/graph/README.md +16 -0
  23. package/reference/packages/ui/src/hooks/README.md +24 -0
  24. package/reference/packages/ui/src/initialization/README.md +19 -0
  25. package/reference/packages/ui/src/organization/README.md +18 -0
  26. package/reference/packages/ui/src/profile/README.md +19 -0
  27. package/reference/packages/ui/src/provider/README.md +31 -0
  28. package/reference/packages/ui/src/router/README.md +18 -0
  29. package/reference/packages/ui/src/sse/README.md +13 -0
  30. package/reference/packages/ui/src/theme/README.md +23 -0
  31. package/reference/packages/ui/src/types/README.md +16 -0
  32. package/reference/packages/ui/src/utils/README.md +18 -0
  33. package/reference/packages/ui/src/zustand/README.md +18 -0
  34. package/reference/resources/patterns.mdx +54 -8
  35. package/reference/scaffold/core/organization-graph.mdx +262 -0
  36. package/reference/scaffold/core/organization-model.mdx +257 -0
  37. package/reference/scaffold/index.mdx +59 -0
  38. package/reference/scaffold/operations/workflow-recipes.md +419 -0
  39. package/reference/scaffold/recipes/add-a-feature.md +142 -0
  40. package/reference/scaffold/recipes/add-a-resource.md +163 -0
  41. package/reference/scaffold/recipes/gate-by-feature-or-admin.md +152 -0
  42. package/reference/scaffold/recipes/index.md +32 -0
  43. package/reference/scaffold/reference/contracts.md +1044 -0
  44. package/reference/scaffold/reference/feature-registry.md +30 -0
  45. package/reference/scaffold/reference/glossary.md +88 -0
  46. package/reference/scaffold/ui/composition-extensibility.mdx +216 -0
  47. package/reference/scaffold/ui/customization.md +239 -0
  48. package/reference/scaffold/ui/feature-flags-and-gating.md +265 -0
  49. package/reference/scaffold/ui/feature-shell.mdx +241 -0
  50. package/reference/scaffold/ui/recipes.md +418 -0
@@ -0,0 +1,265 @@
1
+ ---
2
+ title: Feature Flags & Gating
3
+ description: End-to-end recipe for the three gating concepts -- featureKey nav visibility, FeatureGuard route gate, AdminGuard / requiresAdmin. One doc resolves confusion that scored 2/5 in scaffold eval.
4
+ ---
5
+
6
+ # Feature Flags & Gating
7
+
8
+ **Status: 🟢 Stable**
9
+
10
+ This doc resolves the three-concept confusion that trips up most agents building new features. Read
11
+ the overview table first, then follow the end-to-end chain for the template's actual grouped-key
12
+ plus alias flow.
13
+
14
+ ---
15
+
16
+ ## Overview
17
+
18
+ Three distinct mechanisms control access in the template. They operate at different layers and are not interchangeable.
19
+
20
+ | Concept | What it gates | Where it applies | How it works |
21
+ | ------------------------------ | -------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------- |
22
+ | `featureKey` on a nav entry | Whether a nav item is visible in the sidebar | `navEntry` in a feature manifest or `nav-items.ts` | The shell reads the organization model and hides items whose `featureKey` is disabled |
23
+ | `FeatureGuard` component | Whether a route subtree is accessible | Route layout files (`ui/src/routes/*.tsx`) | Reads org + membership access and redirects with a notification if the feature is off |
24
+ | `AdminGuard` / `requiresAdmin` | Whether an admin-only surface is accessible | Route layout files and manifest nav entries | Reads `profile.is_platform_admin`; redirects non-admins to `/` |
25
+
26
+ The three concepts work together but are independent. Hiding a nav item does not protect a route -- you must also wrap the route in `FeatureGuard`. Showing a nav item does not grant access.
27
+
28
+ ---
29
+
30
+ ## End-to-End Chain
31
+
32
+ This section walks through the template's existing acquisition flow. In this scaffold, `crm` and
33
+ `lead-gen` are route-level aliases that map to the grouped org-model key `acquisition`.
34
+
35
+ If you need a brand-new shell feature key such as `analytics`, that is not a template-only change.
36
+ It requires updating the published `@elevasis/core` organization-model contract and any shared
37
+ `@elevasis/ui` feature manifests that should consume it.
38
+
39
+ ### Step 1: Declare the grouped org-model gate in `organization-model.ts`
40
+
41
+ File: `foundations/config/organization-model.ts`
42
+
43
+ ```ts
44
+ const foundationOrganizationModelOverride = defineOrganizationModel({
45
+ features: {
46
+ enabled: {
47
+ acquisition: true
48
+ },
49
+ labels: {
50
+ acquisition: 'Acquisition'
51
+ }
52
+ },
53
+ // ... rest of model
54
+ })
55
+ ```
56
+
57
+ The `features.enabled` record is the source of truth for the published org-model gates. In the
58
+ current template, those keys are the grouped platform keys:
59
+ `acquisition`, `delivery`, `operations`, `monitoring`, `settings`, `seo`, and `calibration`.
60
+
61
+ Further down in the same file, the template adapts that canonical model into shell-friendly aliases:
62
+
63
+ ```ts
64
+ const featuresEnabled: Record<FoundationFeatureKey, boolean> = {
65
+ acquisition: resolvedOrganizationModel.features.enabled.acquisition,
66
+ crm: resolvedOrganizationModel.features.enabled.acquisition,
67
+ 'lead-gen': resolvedOrganizationModel.features.enabled.acquisition
68
+ }
69
+ ```
70
+
71
+ That alias layer is why route guards can ask for `crm` or `lead-gen` even though the published
72
+ organization-model contract only knows about `acquisition`.
73
+
74
+ ### Step 2: Wire `featureKey` on a nav item
75
+
76
+ Nav items come from one of two places: the manifest `navEntry` (for features registered through `ElevasisFeaturesProvider`) or the local `nav-items.ts` config (for app-local nav).
77
+
78
+ **Manifest nav entry** (preferred for platform features):
79
+
80
+ ```ts
81
+ // Published shared manifest
82
+ import type { FeatureModule } from '@elevasis/ui/provider'
83
+ import { crmManifest } from '@elevasis/ui/features/crm'
84
+
85
+ const manifest: FeatureModule = crmManifest
86
+ ```
87
+
88
+ `accessFeatureKey` on the `FeatureModule` is what drives whether the entire shared feature
89
+ (including its nav entry) is visible. In the published manifests, `crm` and `lead-gen` both use
90
+ `accessFeatureKey: 'acquisition'`; `delivery` uses `accessFeatureKey: 'delivery'`. The shell hides
91
+ the nav entry when org or membership access disables that grouped key.
92
+
93
+ **Local nav-items.ts** (for one-off items not part of a registered feature):
94
+
95
+ ```ts
96
+ // ui/src/config/nav-items.ts
97
+ export const navItems: ExtendedLinksGroupProps[] = [
98
+ { label: 'Dashboard', icon: IconDashboard, link: '/' },
99
+ { label: 'CRM', icon: IconBriefcase, link: '/crm', featureKey: 'crm' }
100
+ ]
101
+ ```
102
+
103
+ When `featureKey` is set on a nav item in `nav-items.ts`, the shell hides that item if
104
+ `isFeatureEnabled(featureKey)` returns false. Template-local aliases like `crm`, `lead-gen`, and
105
+ `projects` work here because the adapted `organizationModel` exposed by foundations includes them.
106
+
107
+ ### Step 3: Wrap the route in `FeatureGuard`
108
+
109
+ File: `ui/src/routes/crm.tsx` (TanStack Router layout file for the `/crm` subtree)
110
+
111
+ ```tsx
112
+ import { ProtectedRoute } from '@/features/auth'
113
+ import { FeatureGuard } from '@/features/auth/guards/FeatureGuard'
114
+ import { createFileRoute, Outlet } from '@tanstack/react-router'
115
+
116
+ export const Route = createFileRoute('/crm')({
117
+ component: RouteComponent
118
+ })
119
+
120
+ function RouteComponent() {
121
+ return (
122
+ <ProtectedRoute>
123
+ <FeatureGuard featureKey="crm">
124
+ <Outlet />
125
+ </FeatureGuard>
126
+ </ProtectedRoute>
127
+ )
128
+ }
129
+ ```
130
+
131
+ `FeatureGuard` must always be nested inside `ProtectedRoute`. It reads from `useFeatureAccess()`
132
+ (the template's wrapper around `createFeatureAccessHook`), checks the effective org-plus-membership
133
+ access, and redirects to `/` with a Mantine notification if the feature is off. The layout pattern
134
+ means every child route under `/crm` is automatically protected without repeating the guard.
135
+
136
+ All existing feature routes in the template follow this exact pattern: `operations.tsx`,
137
+ `projects.tsx`, `lead-gen.tsx`, `crm.tsx`, and `monitoring.tsx`.
138
+
139
+ ### Step 4: Check in a component via `useFeatureAccess`
140
+
141
+ For conditional UI within a component (showing or hiding a button, panel, or section based on a feature flag), use the `useFeatureAccess` hook directly.
142
+
143
+ ```tsx
144
+ import { useFeatureAccess } from '@/shared/hooks/useFeatureAccess'
145
+
146
+ function Dashboard() {
147
+ const { hasFeature } = useFeatureAccess()
148
+
149
+ return (
150
+ <div>
151
+ <CoreMetrics />
152
+ {hasFeature('crm') && <CrmSummaryPanel />}
153
+ </div>
154
+ )
155
+ }
156
+ ```
157
+
158
+ `hasFeature` returns a boolean. Use it for render-conditional logic only. Do not use it as a substitute for `FeatureGuard` on a route -- hiding a component does not prevent direct URL access.
159
+
160
+ The hook is defined in `ui/src/shared/hooks/useFeatureAccess.ts`. It wraps
161
+ `createFeatureAccessHook` from `@elevasis/ui/hooks` and extends it with template-specific key
162
+ aliases (`crm` and `lead-gen` alias to `acquisition`; `projects` aliases to `delivery`).
163
+
164
+ ---
165
+
166
+ ## Admin Gating
167
+
168
+ Admin gating is separate from feature gating. It restricts surfaces to users with `profile.is_platform_admin === true`.
169
+
170
+ ### `requiresAdmin` on a manifest nav entry
171
+
172
+ ```ts
173
+ // In a FeatureNavEntry
174
+ navEntry: {
175
+ label: 'Admin',
176
+ icon: IconShield,
177
+ link: '/admin',
178
+ requiresAdmin: true
179
+ }
180
+ ```
181
+
182
+ Setting `requiresAdmin: true` on a `FeatureNavEntry` causes the shell to hide that nav item for non-admin users. This is purely a visibility concern -- the route is still accessible by URL. Always combine with a route-level `AdminGuard`.
183
+
184
+ ### `AdminGuard` in a route
185
+
186
+ ```tsx
187
+ // ui/src/routes/admin.tsx
188
+ import { ProtectedRoute } from '@/features/auth'
189
+ import { AdminGuard } from '@elevasis/ui/auth'
190
+ import { createFileRoute, Outlet } from '@tanstack/react-router'
191
+
192
+ export const Route = createFileRoute('/admin')({
193
+ component: RouteComponent
194
+ })
195
+
196
+ function RouteComponent() {
197
+ return (
198
+ <ProtectedRoute>
199
+ <AdminGuard fallback={<AppShellLoader />}>
200
+ <Outlet />
201
+ </AdminGuard>
202
+ </ProtectedRoute>
203
+ )
204
+ }
205
+ ```
206
+
207
+ `AdminGuard` reads `profile.is_platform_admin` from the initialization context. Non-admins are redirected to `redirectTo` (default `/`). Always nest inside `ProtectedRoute` so that the user profile is loaded before the guard runs.
208
+
209
+ ### `AdminGuard` in a component
210
+
211
+ ```tsx
212
+ import { AdminGuard } from '@elevasis/ui/auth'
213
+
214
+ function SettingsPage() {
215
+ return (
216
+ <>
217
+ <GeneralSettings />
218
+ <AdminGuard>
219
+ <DangerZone />
220
+ </AdminGuard>
221
+ </>
222
+ )
223
+ }
224
+ ```
225
+
226
+ Use this pattern when only a section of a page is admin-only, not the entire route. The rest of the page renders normally for all authenticated users.
227
+
228
+ ### When to use each
229
+
230
+ - `requiresAdmin` on `navEntry`: hide the nav item for non-admins (cosmetic only, always pair with route guard)
231
+ - `AdminGuard` wrapping a route's `Outlet`: protect the entire route subtree
232
+ - `AdminGuard` wrapping a component section: protect part of a page
233
+
234
+ ---
235
+
236
+ ## Import Paths
237
+
238
+ | Symbol | Import path | Notes |
239
+ | ------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------- |
240
+ | `FeatureGuard` | `@/features/auth/guards/FeatureGuard` | Template-local wrapper; uses `useFeatureAccess` from `@/shared/hooks/useFeatureAccess` |
241
+ | `AdminGuard` | `@elevasis/ui/auth` | Published from `@elevasis/ui` |
242
+ | `useFeatureAccess` | `@/shared/hooks/useFeatureAccess` | Template-local wrapper around `createFeatureAccessHook` from `@elevasis/ui/hooks` |
243
+ | `createFeatureAccessHook` | `@elevasis/ui/hooks` | Factory for building a feature-access hook; already consumed by the template wrapper |
244
+ | `ProtectedRoute` | `@elevasis/ui/auth` or `@/features/auth` | Ensures user is authenticated before any guard runs |
245
+
246
+ The template's `FeatureGuard` (`@/features/auth/guards/FeatureGuard`) is not the same as the published `FeatureGuard` from `@elevasis/ui/features`. The template version closes over the local `useFeatureAccess` hook and the local organization model labels, so it knows your feature key aliases. Use the template-local version.
247
+
248
+ ---
249
+
250
+ ## Common Mistakes
251
+
252
+ - **Inventing a new org-model key in the template only.** The published
253
+ `OrganizationModelFeatureKey` union is fixed in `@elevasis/core`. Template-local aliases like
254
+ `crm`, `lead-gen`, and `projects` work because foundations maps them onto grouped keys; a brand
255
+ new shell key such as `analytics` requires a core/package contract change, not just a doc tweak.
256
+
257
+ - **Using `FeatureGuard` on a nav item instead of a route.** `FeatureGuard` is a React component that redirects on mount. Placing it inside a nav item or link will fire a redirect whenever the sidebar renders. Gate nav visibility with `featureKey` on the nav entry or `accessFeatureKey` on the manifest; gate route access with `FeatureGuard` in the route layout.
258
+
259
+ - **Confusing `requiresAdmin` with `AdminGuard`.** `requiresAdmin` on a `FeatureNavEntry` is a display hint read by the shell nav renderer -- it hides the nav item but does not block the route. `AdminGuard` is a React component that actively redirects. You need both if you want the nav hidden and the route blocked.
260
+
261
+ - **Placing `FeatureGuard` or `AdminGuard` outside `ProtectedRoute`.** Both guards depend on the user profile being loaded. Unauthenticated users will see a redirect loop or a blank screen if the profile is not yet available. Always nest guards inside `ProtectedRoute`.
262
+
263
+ - **Using `hasFeature` from `useFeatureAccess` as a route guard.** `hasFeature` is for conditional
264
+ rendering inside components. A user can still navigate directly to a URL and reach the route
265
+ component. Use `FeatureGuard` in the route file for actual access control.
@@ -0,0 +1,241 @@
1
+ ---
2
+ title: Feature Shell & Provider Runtime
3
+ description: Organization OS UI Shell Runtime and Features layer architecture covering FeatureModule manifests, ElevasisFeaturesProvider, route-to-sidebar dispatch, and organization-model-aware runtime resolution.
4
+ ---
5
+
6
+ ## Overview
7
+
8
+ Within Organization OS, the feature shell spans two layers: the **UI Shell Runtime** that resolves shared nav/sidebar behavior, and the **Features** layer that declares what each feature contributes. It lets command-center and external template consumers compose the same nav, sidebars, and subshell routes from a small set of manifests. It is intentionally a thin layer: manifests describe what a feature contributes, the provider resolves that into a runtime shell model, and consumers still own their own routes, branding, and app-local nav.
9
+
10
+ Three layers participate, and the word "feature" means something different in each:
11
+
12
+ - **Platform capabilities** -- product-facing areas documented under `technical/features/` (Execution Engine, Workflows, Agents, Operations, etc.). These are not one-to-one with shell features.
13
+ - **Shell features** -- features in `packages/ui/src/features/*`. Seven are manifest-backed (`lead-gen`, `crm`, `delivery`, `operations`, `monitoring`, `settings`, `seo`); two are utility features without manifests (`auth`, `dashboard`).
14
+ - **Organization-model features** -- grouped semantic/access keys in `@repo/core/organization-model` (`acquisition`, `delivery`, `operations`, `monitoring`, `settings`, `seo`, `calibration`). See [Organization Model](../core/organization-model.mdx).
15
+
16
+ One shell feature can contain several platform capabilities; shell and organization-model keys are different concepts.
17
+
18
+ ## Source of Truth
19
+
20
+ - `packages/ui/src/features/registry/types.ts` -- `FeatureModule` contract
21
+ - `packages/ui/src/features/registry/manifests.ts` -- published `FEATURE_MANIFESTS` convenience map
22
+ - `packages/ui/src/provider/ElevasisFeaturesProvider.tsx` -- runtime composition
23
+ - `packages/ui/src/provider/FeatureShell.tsx` -- route-to-sidebar dispatch
24
+ - `packages/ui/src/provider/resolvers/{RouteResolver,NavResolver}.ts` -- pure path/nav helpers
25
+ - `packages/ui/src/provider/validateManifests.ts` -- startup-time manifest validation
26
+ - `packages/ui/src/provider/published.ts` -- headless published barrel
27
+ - `apps/command-center/src/routes/__root.tsx` -- reference composition
28
+
29
+ ## The FeatureModule Contract
30
+
31
+ A `FeatureModule` describes one shell feature's contribution. Fields:
32
+
33
+ - `key` -- unique stable identifier (e.g., `'crm'`, `'delivery'`)
34
+ - `accessFeatureKey` -- **required**; the key `useFeatureAccess()` checks for gating
35
+ - `domainIds`, `capabilityIds` -- semantic references into the organization model
36
+ - `navEntry` -- top-level nav contribution (label, icon, optional `link`, optional nested `links`, optional `requiresAdmin`, optional onboarding-tour ID, optional `featureKey` override when nav identity must diverge from access identity)
37
+ - `sidebar` -- optional `ComponentType` for the feature's subshell sidebar
38
+ - `subshellRoutes` -- routes the feature owns under its subshell
39
+ - `organizationGraph` -- **Operations-only**; bridges a manifest to an organization-model surface (ignored by other features)
40
+
41
+ `FeatureModule.label` has been removed; nav labels resolve from `navEntry.label` or from organization-model feature labels.
42
+
43
+ Manifests are validated at provider registration via `validateManifests()`. Unknown `accessFeatureKey`, `domainIds`, or `capabilityIds` (measured against the resolved organization model) throw with all violations collected into one error.
44
+
45
+ ### ResolvedFeatureModule
46
+
47
+ `ResolvedFeatureModule` extends `FeatureModule` with two additional fields added during provider resolution:
48
+
49
+ - `access: ResolvedFeatureAccess` -- `{ featureKey: string, enabled: boolean }` -- the resolved access state for this feature
50
+ - `semantics: ResolvedFeatureSemantics` -- `{ domainIds, capabilityIds, surfaceIds, surfaces }` -- merged semantic identifiers derived from both manifest declarations and organization-model surface data
51
+
52
+ `ResolvedFeatureSemantics.surfaces` carries the full `OrganizationModelSurface[]` objects (not just IDs), so consumers can inspect surface metadata without a separate lookup.
53
+
54
+ ## Provider Architecture
55
+
56
+ The provider layer is composed of several focused context providers. `ElevasisCoreProvider` and `ElevasisFeaturesProvider` are the two consumer-facing entry points; the others are internal building blocks also published for advanced use.
57
+
58
+ ### ElevasisCoreProvider
59
+
60
+ `ElevasisCoreProvider` is the headless root provider for Elevasis-powered applications. It is pure auth + API with no style side-effects -- no CSS variables, no `data-elevasis-scheme`, no font loading. When `apiUrl` is provided it composes the full provider stack:
61
+
62
+ `QueryClientProvider` -> `AuthProvider` -> `ApiClientProvider` -> `ProfileProvider` -> `OrganizationProvider` -> `ElevasisServiceProvider` -> `NotificationProvider` -> `InitializationProvider`
63
+
64
+ Consumers that need Mantine theming use `ElevasisUIProvider` (internal) or `ElevasisProvider` instead. `ElevasisCoreProvider` is exported from `packages/ui/src/provider/published.ts` for headless SDK consumers.
65
+
66
+ ### ElevasisServiceProvider
67
+
68
+ `ElevasisServiceProvider` (from `ElevasisServiceContext.tsx`) is the standalone service context. It accepts three props directly: `apiRequest`, `organizationId`, and `isReady`. `ElevasisCoreProvider` composes this internally after org resolution; advanced consumers can mount it standalone for testing or embedding.
69
+
70
+ `useElevasisServices()` reads this context and throws if used outside a provider. Consumed throughout the shared component library wherever API requests are needed.
71
+
72
+ ### AppearanceProvider
73
+
74
+ `AppearanceProvider` (from `AppearanceContext.tsx`) supplies an `AppearanceConfig` to the tree: `{ background?: ReactNode, loader?: ReactNode }`. The `background` field controls layers rendered behind app content; `loader` controls loading-state elements. Defaults are set in the provider rather than at context creation to avoid importing heavy visual components at module scope.
75
+
76
+ `useAppearance()` reads this context and throws if used outside a provider.
77
+
78
+ ### NotificationProvider
79
+
80
+ `NotificationProvider` (from `NotificationContext.tsx`) accepts a pluggable `NotificationAdapter` for routing notifications to any library. The adapter interface is `{ success, error, info, warning, apiError }`. `ElevasisUIProvider` wires the Mantine adapter automatically.
81
+
82
+ When no provider is present in the tree, `useNotificationAdapter()` falls back to a console-based adapter (not a no-op), so template consumers work without Mantine. The hook never throws.
83
+
84
+ ### Memoization Strategy
85
+
86
+ All computed values inside `ElevasisFeaturesProvider` are wrapped in `useMemo()` with precise dependency tracking. Resolved features, nav items, shell model, organization graph, and the context value object are each memoized separately so downstream consumers only re-render when their specific inputs change.
87
+
88
+ ## Provider Responsibilities
89
+
90
+ `ElevasisFeaturesProvider` owns:
91
+
92
+ - registration of shared feature manifests
93
+ - feature-flag-aware nav contribution
94
+ - route-to-sidebar subshell dispatch (via `shellRuntime.resolveRoute`)
95
+ - provider-scoped shared runtime context
96
+ - resolved shell-model composition and shell-native route matching
97
+ - organization-model-aware nav label and path resolution
98
+ - resolved feature output (no legacy `shellModule` wrapper)
99
+
100
+ It does **not** own:
101
+
102
+ - TanStack file-based route registration
103
+ - app branding, topbar behavior, admin entries, assistant/context glue
104
+
105
+ Consumers keep thin local route wrappers and app-local nav where needed.
106
+
107
+ ## Runtime Flow
108
+
109
+ 1. The app defines a manifest list (often by spreading `FEATURE_MANIFESTS` and overriding entries).
110
+ 2. The app optionally resolves an organization model and passes it to the provider.
111
+ 3. The provider combines `useFeatureAccess()` with organization-model feature state to compute enabled features.
112
+ 4. Nav labels and nav paths resolve from `organizationModel.features.labels` and `organizationModel.navigation.surfaces` when present.
113
+ 5. The provider exposes `shellModel`, `shellRuntime`, resolved feature access, `organizationGraph`, and shared runtime inputs via `useElevasisFeatures()`.
114
+ 6. `FeatureShell` calls `shellRuntime.resolveRoute(currentPath)`:
115
+ - `matched` -- render the feature's sidebar subshell
116
+ - `hidden` -- render `FeatureUnavailableState`
117
+ - `unmatched` -- render plain children (consumer owns the route)
118
+
119
+ A route resolves as `hidden` when the matched nav link meets either of these conditions: its `featureKey` fails the `isFeatureEnabled()` check, or its path matches an entry in `disabledSubsectionPaths`. Both checks are applied recursively through nested nav links.
120
+
121
+ Consumer nav derivation runs locally from `shellModel.navItems`; the provider no longer exposes `visibleNavItems`.
122
+
123
+ ## Provider-Scoped Runtime Context
124
+
125
+ `useElevasisFeatures()` exposes:
126
+
127
+ - `shellModel` -- `{ navItems: ResolvedShellNavItem[] }` -- the full resolved nav list
128
+ - `shellRuntime` -- `{ resolveRoute: (path) => ResolvedShellRouteMatch }` -- route dispatch
129
+ - `resolvedFeatures` -- all `ResolvedFeatureModule[]` regardless of enabled state
130
+ - `enabledResolvedFeatures` -- filtered to `access.enabled === true`
131
+ - `timeRange`
132
+ - `operationsApiUrl`, `operationsSSEManager`
133
+ - `organizationModel`
134
+ - `organizationGraph` (resolved from `operations.organization-graph` surface; see [Organization Graph](../core/organization-graph.mdx))
135
+ - `disabledSubsectionPaths` -- used by consumers like `_template` to hide specific subsections (e.g., `/settings/appearance`)
136
+ - `isFeatureEnabled(key: string): boolean` -- checks both membership flag and organization-model feature state
137
+ - `getResolvedFeature(key: string): ResolvedFeatureModule | undefined` -- looks up a resolved feature by its `key` field (module key, not access key)
138
+
139
+ ## Nav Resolution
140
+
141
+ ### ResolvedShellNavItem
142
+
143
+ `ResolvedShellNavItem` extends `FeatureNavEntry` with two additional fields:
144
+
145
+ - `placement: 'primary' | 'bottom'` -- where the item appears in the shell nav; feature manifests always produce `'primary'`; `appShellOverrides.bottomNavItems` produce `'bottom'`
146
+ - `source: 'app' | 'feature'` -- whether the item came from a manifest (`'feature'`) or from `appShellOverrides` (`'app'`)
147
+ - `accessFeatureKey?: string` -- the feature key associated with this nav item for gating checks
148
+
149
+ `shellModel.navItems` contains the merged and filtered list of all `ResolvedShellNavItem`s from both manifest features and `appShellOverrides`.
150
+
151
+ ### filterNavLinks
152
+
153
+ `filterNavLinks()` in `ElevasisFeaturesProvider.tsx` recursively removes gated and disabled-subsection links from a `FeatureNavLink[]` array. A link is removed if its `featureKey` fails `isFeatureEnabled()` or if its path matches any entry in `disabledSubsectionPaths`. The function recurses into nested `links` arrays before deciding whether a parent with now-empty children should be retained or dropped.
154
+
155
+ ## Access Resolution & Key Aliasing
156
+
157
+ Shell feature-module keys (`crm`, `lead-gen`, `delivery`) differ from the organization-model grouped keys used by published membership config (`acquisition`, `delivery`). The published `@elevasis/ui` line preserves grouped-key compatibility:
158
+
159
+ - `createFeatureAccessHook` is the factory that produces `useFeatureAccess`. (Old name `createUseFeatureAccess` is re-exported as `@deprecated`.)
160
+ - `FEATURE_KEY_ALIASES` in `packages/ui/src/hooks/feature-access/aliases.ts` is the single authoritative alias map. `crm -> acquisition`, `lead-gen -> acquisition`, `projects -> delivery`.
161
+ - Shared manifests declare grouped `accessFeatureKey` (e.g., `crmManifest.accessFeatureKey = 'acquisition'`) so the published contract gates against `MembershipFeatureConfig` as written.
162
+ - The provider fallback for missing feature access identity is retired -- explicit `accessFeatureKey` is required.
163
+
164
+ ## Published Surface
165
+
166
+ `packages/ui/src/provider/published.ts` is intentionally headless: it exports the provider, types, and hooks, but no Mantine-dependent visual provider pieces. External consumers can adopt the feature/provider contract without pulling the full internal visual surface.
167
+
168
+ All nine features are published as individual subpath exports from `@elevasis/ui`:
169
+
170
+ - `@elevasis/ui/features/auth` -- `ProtectedRoute`, `AdminGuard`, `FeatureGuard`, `useUserProfile` (utility feature; no manifest)
171
+ - `@elevasis/ui/features/dashboard` -- `Dashboard`, `ResourceOverview`, `RecentExecutionsByResource`, `UnresolvedErrorsTeaser` (utility feature; no manifest)
172
+ - `@elevasis/ui/features/crm`
173
+ - `@elevasis/ui/features/delivery`
174
+ - `@elevasis/ui/features/lead-gen`
175
+ - `@elevasis/ui/features/monitoring`
176
+ - `@elevasis/ui/features/operations`
177
+ - `@elevasis/ui/features/seo`
178
+ - `@elevasis/ui/features/settings`
179
+
180
+ The `auth` and `dashboard` features are not in `FEATURE_MANIFESTS` and are not registered with `ElevasisFeaturesProvider`. They are standalone published feature modules consumed directly by host apps.
181
+
182
+ ## Composition Patterns
183
+
184
+ ### Command-center composition
185
+
186
+ `apps/command-center/src/routes/__root.tsx`:
187
+
188
+ 1. imports the mounted manifest list
189
+ 2. resolves `COMMAND_CENTER_ORGANIZATION_MODEL`
190
+ 3. passes manifests, organization model, time range, operations API URL, and SSE manager to `ElevasisFeaturesProvider`
191
+ 4. keeps app-local nav entries for dashboard, admin, archive (passed into `appShellOverrides`)
192
+ 5. derives filtered nav locally from `shellModel.navItems`
193
+ 6. lets `FeatureShell` dispatch subshell sidebars for matched routes
194
+
195
+ ### appShellOverrides
196
+
197
+ `appShellOverrides` is an `AppShellOverrides` prop on `ElevasisFeaturesProvider` that lets the host app inject nav items outside the manifest registry:
198
+
199
+ - `primaryNavItems?: FeatureNavEntry[]` -- prepended before feature nav items in the primary nav position; organization-model label and path resolution is applied to these entries
200
+ - `bottomNavItems?: FeatureNavEntry[]` -- rendered at the bottom of the shell nav; also subject to organization-model resolution and `filterNavLinks` filtering
201
+
202
+ Both arrays go through the same `isFeatureEnabled()` and `disabledSubsectionPaths` filtering as manifest-derived items. Items whose `featureKey` is disabled, or that have only disabled nested links and no direct `link`, are dropped entirely.
203
+
204
+ Nine feature modules are published (see **Published Surface** above). Six are currently mounted in command-center: `leadGenManifest`, `crmManifest`, `deliveryManifest`, `operationsManifest`, `monitoringManifest`, `settingsManifest`. The remaining three (`seoManifest`, `auth`, `dashboard`) are published but not mounted through `ElevasisFeaturesProvider` in command-center -- `auth` and `dashboard` are utility features consumed directly, while `seoManifest` is available for composition but not wired into the current command-center shell.
205
+
206
+ ### External-consumer composition
207
+
208
+ External shells consume the published `@elevasis/ui` provider surface. `_template/ui` imports individual manifests, composes a local `FEATURE_MANIFESTS: FeatureModule[]` array, and passes `canonicalOrganizationModel` (from `@foundation/config/organization-model`) into the provider. Host-local nav (home/dashboard) remains app-owned -- the provider covers shared shell features, not total shell ownership.
209
+
210
+ See [Composition & Extensibility](./composition-extensibility.mdx) for the sidebar/page override patterns consumers use to customize shared features without forking.
211
+
212
+ ## Feature-Specific Sidebar Behavior
213
+
214
+ ### CRM Sidebar
215
+
216
+ The CRM sidebar (`packages/ui/src/features/crm/sidebar/`) exports `CrmSidebar`, `CrmSidebarTop`, and `CrmSidebarMiddle`. `SavedViewsPanel` lives in `packages/ui/src/features/crm/workbench/SavedViewsPanel.tsx` and is exported from the CRM workbench barrel; it provides the saved contact views panel rendered within the CRM workbench surface.
217
+
218
+ ### Operations Sidebar
219
+
220
+ `OperationsSidebarTop` in `packages/ui/src/features/operations/sidebar/OperationsSidebarTop.tsx` is context-aware by route:
221
+
222
+ - Returns `null` for sessions (`/operations/sessions`) and command-view (`/operations/command-view`) routes -- these sections own their own top-area UI
223
+ - Shows a "Resource Overview" navigation button for the resources section (`/operations/resources`)
224
+ - Shows a "Calibration Projects" navigation button for the calibration section (`/operations/calibration`)
225
+ - Returns `null` for all other paths (e.g., the operations index)
226
+
227
+ This pattern avoids hardcoding sidebar top content in the parent and lets each operations sub-section declare its own top panel entry point.
228
+
229
+ ## Testing
230
+
231
+ - `createTestFeaturesProvider` fixture at `packages/ui/src/provider/createTestFeaturesProvider.tsx` (internal; exported from `provider/index.ts`) gives tests a pre-wired provider with configurable organization model and feature access.
232
+ - Resolver modules have focused unit tests (`RouteResolver.test.ts`, `NavResolver.test.ts`).
233
+ - `validateManifests.test.ts` covers manifest validation against the organization model.
234
+ - `ElevasisFeaturesProvider.test.tsx`, `FeatureShell.test.tsx`, and `feature-contract.test.ts` cover runtime composition and the published surface.
235
+
236
+ ## Key Conceptual Distinctions
237
+
238
+ - **Platform capability vs. shell feature** -- capabilities are the product map; shell features are manifest-backed UI surfaces.
239
+ - **Shell feature key vs. organization-model feature key** -- `crm` (shell) vs. `acquisition` (published access identity).
240
+ - **Semantic domain vs. feature key** -- `domain = crm` describes the business area; `feature key = acquisition` gates access.
241
+ - **Provider-owned vs. consumer-owned** -- provider owns shared manifests, nav contribution, sidebar dispatch, shared runtime context; consumers own route files, branding, topbar behavior, admin entries.