@elevasis/sdk 1.4.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 (31) 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 +24 -0
  10. package/reference/deployment/provided-features.mdx +64 -25
  11. package/reference/framework/index.mdx +2 -2
  12. package/reference/framework/project-structure.mdx +10 -8
  13. package/reference/index.mdx +3 -3
  14. package/reference/packages/core/src/organization-model/README.md +19 -4
  15. package/reference/resources/patterns.mdx +54 -8
  16. package/reference/scaffold/core/organization-graph.mdx +262 -0
  17. package/reference/scaffold/core/organization-model.mdx +257 -0
  18. package/reference/scaffold/index.mdx +59 -0
  19. package/reference/scaffold/operations/workflow-recipes.md +419 -0
  20. package/reference/scaffold/recipes/add-a-feature.md +142 -0
  21. package/reference/scaffold/recipes/add-a-resource.md +163 -0
  22. package/reference/scaffold/recipes/gate-by-feature-or-admin.md +152 -0
  23. package/reference/scaffold/recipes/index.md +32 -0
  24. package/reference/scaffold/reference/contracts.md +1044 -0
  25. package/reference/scaffold/reference/feature-registry.md +30 -0
  26. package/reference/scaffold/reference/glossary.md +88 -0
  27. package/reference/scaffold/ui/composition-extensibility.mdx +216 -0
  28. package/reference/scaffold/ui/customization.md +239 -0
  29. package/reference/scaffold/ui/feature-flags-and-gating.md +265 -0
  30. package/reference/scaffold/ui/feature-shell.mdx +241 -0
  31. package/reference/scaffold/ui/recipes.md +418 -0
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: Feature Registry
3
+ description: Auto-generated catalog of registered feature manifests. Do not edit manually.
4
+ ---
5
+
6
+ > **Auto-generated** from source manifests. Do not edit manually. Run `node scripts/monorepo/generate-scaffold-feature-registry.js` to regenerate.
7
+
8
+ ## Registered Feature Manifests
9
+
10
+ | Feature Key | Access Key | Nav Label | Domains | Routes |
11
+ | --- | --- | --- | --- | --- |
12
+ | `crm` | `acquisition` | CRM | crm | /crm |
13
+ | `delivery` | `delivery` | Projects | delivery | /projects |
14
+ | `lead-gen` | `acquisition` | Lead Gen | lead-gen | /lead-gen |
15
+ | `monitoring` | `monitoring` | Monitoring | — | — |
16
+ | `operations` | `operations` | Operations | operations | /operations |
17
+ | `seo` | `seo` | — | — | /seo |
18
+ | `settings` | `settings` | Settings | — | — |
19
+
20
+ > **Note:** `auth` and `dashboard` are not in the registry — they are app-shell concerns, not gated features.
21
+
22
+ ## OrganizationModelFeatureKey Values
23
+
24
+ Values defined in `FeatureKeySchema` (`packages/core/src/organization-model/domains/features.ts`):
25
+
26
+ ```typescript
27
+ type OrganizationModelFeatureKey = 'acquisition' | 'delivery' | 'operations' | 'monitoring' | 'settings' | 'seo' | 'calibration'
28
+ ```
29
+
30
+ These are the valid values for `accessFeatureKey` in a `FeatureModule` manifest and for feature-flag gating in the organization model.
@@ -0,0 +1,88 @@
1
+ ---
2
+ title: Glossary
3
+ description: Terminology disambiguation for Organization OS concepts used in the template scaffold, foundations, and published packages.
4
+ ---
5
+
6
+ # Glossary
7
+
8
+ For canonical (internal) definitions, see the Organization OS glossary in the monorepo architecture docs.
9
+
10
+ This condensed version covers every ambiguity-prone term a template consumer or agent is likely to encounter. Alphabetical within each section.
11
+
12
+ ---
13
+
14
+ ## Terms
15
+
16
+ **accessFeatureKey** -- the `OrganizationModelFeatureKey` a `FeatureModule` declares so the provider knows which org-level gate to check. Required field on every `FeatureModule`. Distinct from the nav-item `featureKey` (see below). Shell keys like `crm` and `lead-gen` map to grouped org-model keys (`acquisition`) via `FEATURE_KEY_ALIASES` -- this aliasing is automatic; callers do not need to translate manually.
17
+
18
+ **AdminGuard** -- route-level admin wrapper from `@elevasis/ui/features/auth`. Wraps routes restricted to admin members. Must nest inside `ProtectedRoute`. Does not replace `requiresAdmin` on nav entries -- use both when both route access and nav visibility need admin enforcement.
19
+
20
+ **Contract** -- the publishable I/O boundary: Zod schemas in `foundations/types/index.ts` for workflow inputs/outputs, or the `FeatureModule` TypeScript shape for shell features. Distinct from "manifest": a contract is the structural definition; a manifest is a specific feature instance conforming to that shape.
21
+
22
+ **DeploymentSpec** -- the complete resource collection for one organization (`workflows`, `agents`, `triggers`, `integrations`, `relationships`, `externalResources`, `humanCheckpoints`). Defined in `@repo/core`. The `operations/src/index.ts` file in each external project exports one `DeploymentSpec`.
23
+
24
+ **Domain** -- a semantic business area in `OrganizationModel.domains`. Four defaults: `crm`, `lead-gen`, `delivery`, `operations`. Domains group entity IDs, surface IDs, resource IDs, and capability IDs into a coherent business area. Semantic, not navigational -- describes what business area a thing belongs to, not which access key gates it. Distinct from "feature": a domain describes business meaning; a feature key describes access and enablement.
25
+
26
+ **FEATURE_KEY_ALIASES** -- the alias map in `@elevasis/ui` that bridges shell feature-module keys to org-model grouped keys: `crm -> acquisition`, `lead-gen -> acquisition`, `projects -> delivery`. Used internally by `createFeatureAccessHook` so `isFeatureEnabled('crm')` resolves correctly against `MembershipFeatureConfig.features.acquisition`.
27
+
28
+ **FoundationLegacyFeatureKey** -- template-local union for feature keys defined in `foundations/config/organization-model.ts`. Distinct from the published `OrganizationModelFeatureKey` (seven-key union from `@elevasis/core`). Extension point for template-specific feature identity without modifying the published core union. `FoundationFeatureKey` combines both (`OrganizationModelFeatureKey | FoundationLegacyFeatureKey`). `FEATURE_KEY_ALIASES` maps between shell keys and org-model grouped keys at runtime.
29
+
30
+ **Feature** -- an overloaded term. Always qualify which layer is in scope:
31
+
32
+ - **Platform capability** -- a product area (Execution Engine, Workflows, Agents, etc.). Not one-to-one with shell features.
33
+ - **Shell FeatureModule** -- a manifest-backed UI feature registered with `ElevasisFeaturesProvider`. Seven manifest-backed: `lead-gen`, `crm`, `delivery`, `operations`, `monitoring`, `settings`, `seo`. Two utility (no manifest): `auth`, `dashboard`.
34
+ - **Organization-model feature key** (`OrganizationModelFeatureKey`) -- grouped access key. Seven values: `acquisition`, `delivery`, `operations`, `monitoring`, `settings`, `seo`, `calibration`. Controls org-level gating and membership overrides.
35
+
36
+ **featureKey** (nav-item) -- optional field on `FeatureNavEntry` and `FeatureNavLink` that gates a specific nav item's visibility independently of the module's `accessFeatureKey`. Resolved through `FEATURE_KEY_ALIASES`. May be more specific than the module's access key when a nested link needs a narrower gate.
37
+
38
+ **FeatureGuard** -- route-level feature gate from `@elevasis/ui/features/auth`. Blocks access to a route when the resolved org model has the relevant feature key disabled. Must nest inside `ProtectedRoute`. Distinct from `AdminGuard` (admin membership) and `ProtectedRoute` (authentication).
39
+
40
+ **FeatureModule** -- the manifest contract each shell feature provides to `ElevasisFeaturesProvider`. Defined in `@repo/ui`, NOT `@repo/core`. Fields: `key`, `accessFeatureKey` (required), `domainIds`, `capabilityIds`, `navEntry`, `sidebar`, `subshellRoutes`, `organizationGraph` (Operations-only). Template consumers build a local manifest array and pass it to `ElevasisFeaturesProvider` in `__root.tsx`.
41
+
42
+ **Foundations** -- the adapter layer in `foundations/` (two modules: `config/organization-model.ts` and `types/index.ts`). Source package with no build step. Depends only on `@elevasis/core` (npm) and `zod`. Never import `@repo/core` from foundations -- that would break standalone deployment.
43
+
44
+ **Manifest** -- a `FeatureModule` instance that declares what one shell feature contributes at runtime (nav, sidebar, subshell routes, access key, semantic references). The provider registers an array of manifests at startup and validates them against the resolved org model.
45
+
46
+ **MembershipFeatureConfig** -- per-member-per-org feature overrides stored in `org_memberships.config`. Six keys: `operations`, `monitoring`, `acquisition`, `delivery`, `calibration`, `seo`. Note: `settings` is absent -- see **Settings asymmetry** below.
47
+
48
+ **OrganizationModel** -- the top-level semantic contract for an organization. Published from `@elevasis/core/organization-model`. In the template, authored in `foundations/config/organization-model.ts` and exported as `canonicalOrganizationModel` (passed to `ElevasisFeaturesProvider`) and `organizationModel` (enriched shape for app-local use).
49
+
50
+ **OrganizationModelFeatureKey** -- seven values: `acquisition`, `delivery`, `operations`, `monitoring`, `settings`, `seo`, `calibration`. These appear in `OrganizationModel.features.enabled` and as `accessFeatureKey` on `FeatureModule` instances. `MembershipFeatureConfig` overrides six of them per member (no `settings` slot -- see **Settings asymmetry**).
51
+
52
+ **Provider / ElevasisFeaturesProvider** -- the runtime that registers manifests, resolves feature access against the org model, dispatches subshell routing via `FeatureShell`, and exposes resolved state through `useElevasisFeatures()`. Mounted in `__root.tsx`. Accepts `features`, optional `organizationModel`, and optional `appShellOverrides`.
53
+
54
+ **Resolved types (ResolvedFeatureModule, ResolvedShellNavItem)** -- provider output. `ResolvedFeatureModule` extends `FeatureModule` with `access` (enabled state) and `semantics` (merged domain, capability, and surface IDs). `ResolvedShellNavItem` extends `FeatureNavEntry` with `placement`, `source`, and `accessFeatureKey`. Both from `@elevasis/ui`.
55
+
56
+ **Resource** -- an entry in `OrganizationModel.resourceMappings` linking a deployable automation resource into the semantic model. At the registry layer, resources are `WorkflowDefinition` or `AgentDefinition` instances in a `DeploymentSpec`.
57
+
58
+ **Settings asymmetry** -- `settings` is a valid `OrganizationModelFeatureKey` (org-level, present in `OrganizationModel.features.enabled`). It is absent from `MembershipFeatureConfig` (six keys, no `settings`). The org can disable settings entirely, but per-member disabling is not supported. Settings visibility for individual members is controlled via `requiresAdmin` on the nav entry and `AdminGuard` on routes.
59
+
60
+ **Subshell / Sidebar** -- the feature-scoped UI region rendered when the current route matches a manifest's `subshellRoutes`. Each `FeatureModule.sidebar` is a `ComponentType`. Consumers customize by composing the feature's published sidebar primitives (`CrmSidebar`, `CrmSidebarMiddle`, etc.) and assigning their component to `manifest.sidebar`.
61
+
62
+ **Surface** -- a navigable view in `OrganizationModel.navigation.surfaces`. Identified by a dotted `id` (e.g., `crm.pipeline`). Has `path`, `surfaceType`, optional `featureKey` gate, and cross-reference arrays. Distinct from "page": a surface is the org-model declaration; a page is the React component rendered at the route.
63
+
64
+ **Topology** -- resource relationships (`triggers`, `uses`, `approval`) declared in `DeploymentSpec.relationships` and `HumanCheckpointDefinition.routesTo`. Used for Command View graph edges.
65
+
66
+ ---
67
+
68
+ ## Package-Boundary Cheat Sheet
69
+
70
+ **`@elevasis/core`** (published npm)
71
+
72
+ - `OrganizationModel`, `OrganizationModelFeatureKey`, `OrganizationModelSurface`, `OrganizationModelResourceMapping`
73
+ - `resolveOrganizationModel`, `defineOrganizationModel`, `DEFAULT_ORGANIZATION_MODEL`
74
+ - `MembershipFeatureConfig`
75
+
76
+ **`@elevasis/ui`** (published npm)
77
+
78
+ - `FeatureModule`, `FeatureNavEntry`, `FeatureNavLink`
79
+ - `ResolvedFeatureModule`, `ResolvedShellNavItem`
80
+ - `FeatureGuard`, `AdminGuard`, `ProtectedRoute`
81
+ - `ElevasisFeaturesProvider`, `ElevasisCoreProvider`, `useElevasisFeatures`
82
+ - `FEATURE_KEY_ALIASES`, `createFeatureAccessHook`
83
+
84
+ **`foundations/`** (local source package -- not published)
85
+
86
+ - `canonicalOrganizationModel` -- passed to `ElevasisFeaturesProvider`
87
+ - `organizationModel` -- enriched shape for app-local code
88
+ - Workflow I/O schemas (`types/index.ts`)
@@ -0,0 +1,216 @@
1
+ ---
2
+ title: Composition & Extensibility
3
+ description: Organization OS Toolkit layer guidance for customizing shared shell features without forking, including exported nav arrays, optional sidebar props, composable layout primitives, and the manifest.sidebar override pattern.
4
+ ---
5
+
6
+ ## Overview
7
+
8
+ Within Organization OS, this doc covers the **Toolkit** layer: the primitives consumers use to customize shared shell features by composition instead of fork. Consumers extend nav items, inject panels, or wrap pages by combining published primitives and assigning a custom component to `manifest.sidebar`. Copying source is physically possible but unsupported -- a fork owns upstream drift forever.
9
+
10
+ The core pattern: set `sidebar` on your manifest to a component that composes the feature's published pieces. If the published pieces can't express what you need, the missing export is a bug. File an issue instead of forking.
11
+
12
+ Shipped in `@elevasis/ui >= 2.8.1`.
13
+
14
+ ## The Override Pattern
15
+
16
+ Every `FeatureModule.sidebar` is a `ComponentType`. Consumers customize by:
17
+
18
+ 1. Importing the feature's published sidebar wrapper and middle component.
19
+ 2. Wrapping or replacing the middle with custom content.
20
+ 3. Spreading the stock manifest and setting `sidebar` to their component.
21
+ 4. Passing the customized manifest to `ElevasisFeaturesProvider`.
22
+
23
+ No new manifest fields. No discriminated unions. No runtime machinery.
24
+
25
+ ## Decision Tree
26
+
27
+ - **Adding or changing nav items?** -> Extend the exported item array and pass to `<*SidebarMiddle items={...}>`.
28
+ - **Structural changes (injecting panels, wrapping sections)?** -> Compose `SidebarTop`, `SidebarMiddle`, and exported panels directly in your own component.
29
+ - **Wrapping pages?** -> Import the feature's page component and wrap with custom chrome in a route file.
30
+ - **None of these fits?** -> Missing export is the bug. File an issue.
31
+
32
+ ## Exported Primitives
33
+
34
+ ### Nav item arrays
35
+
36
+ Re-exported from each feature's sidebar barrel:
37
+
38
+ - `CRM_ITEMS` from `@elevasis/ui/features/crm`
39
+ - `LEAD_GEN_ITEMS` from `@elevasis/ui/features/lead-gen`
40
+ - `DELIVERY_PROJECT_ITEMS`, `DELIVERY_WORK_ITEMS`, `DELIVERY_COMMUNICATION_ITEMS` from `@elevasis/ui/features/delivery`
41
+
42
+ Shape is the shared `NavItem` type exported from `@elevasis/ui/layout`.
43
+
44
+ ### Sidebar wrappers
45
+
46
+ Thin Top+Middle composers. Each accepts optional `children?: ReactNode` that replaces the default middle:
47
+
48
+ - `CrmSidebar`, `LeadGenSidebar`, `ProjectsSidebar`
49
+
50
+ ### Middle components with optional item props
51
+
52
+ - `CrmSidebarMiddle` -- `items?: NavItem[]` (defaults to `CRM_ITEMS`)
53
+ - `LeadGenSidebarMiddle` -- `items?: NavItem[]` (defaults to `LEAD_GEN_ITEMS`)
54
+ - `ProjectsSidebarMiddle` -- `projectItems?`, `workItems?`, `communicationItems?` (each defaults to its respective exported array)
55
+
56
+ Delivery's three sections are preserved intentionally -- a real UX pattern consumers may want to extend with additional sections.
57
+
58
+ ### Composable layout primitives
59
+
60
+ From `@elevasis/ui/layout`:
61
+
62
+ - `SubshellNavItem` -- individual nav link
63
+ - `SubshellNavList` -- router-context-aware list renderer (accepts `items: NavItem[]`, derives active state via `useRouterContext()`)
64
+ - `SubshellSidebarSection` -- section header with icon, label, optional top border
65
+ - `NavItem` -- shared item shape type
66
+ - `SubshellContainer` -- full-width, full-height flex wrapper that composes sidebar and content side by side
67
+ - `SubshellRightSideContainer` -- content area column (`flex: 1`, `minWidth: 0`) that prevents sidebar from squeezing content
68
+ - `SubshellContentContainer` -- scrollable content area with standard `md` padding and automatic topbar offset applied via `paddingTop`
69
+ - `SubshellLoader` -- loading placeholder centered in the subshell viewport; reads the active loader from `AppearanceContext`
70
+
71
+ `FeatureShell` wires these together at `width={250}` as the default sidebar width. Consumers composing their own shell layout can pass a different `width` prop to `SubshellSidebar`.
72
+
73
+ ### SubshellSidebar features
74
+
75
+ `SubshellSidebar` is more than a thin wrapper. Key behaviors verified in source:
76
+
77
+ - **Collapsible** -- controlled by `collapsible` prop (defaults to `true`) and `defaultOpen` prop (defaults to `true`). When collapsed, the sidebar animates to zero width and a small toggle button remains visible at the bottom-right edge.
78
+ - **Glass morphism** -- applies `color-mix(in srgb, var(--glass-background) 80%, transparent)` as background and `var(--glass-blur)` as `backdropFilter`, consistent with the theme token system.
79
+ - **Animated transitions** -- width and opacity animate on collapse/expand using `sidebarTransitionDuration` from the layout constants.
80
+ - **Width** -- no built-in default; the caller supplies `width`. `FeatureShell` passes `250` and this is the canonical default for stock feature sidebars.
81
+
82
+ From `@elevasis/ui/router/context`:
83
+
84
+ - `useRouterContext` -- access route state for custom active-state logic
85
+
86
+ ### RouterAdapter interface
87
+
88
+ `RouterAdapter` is the interface that bridges any router implementation to the shared layout primitives. Defined in `packages/ui/src/router/context.ts`:
89
+
90
+ - `currentPath: string` -- current pathname, used by `SubshellNavList` to derive active state
91
+ - `currentSearch?: string` -- current search string (optional)
92
+ - `navigate: (to: string) => void` -- imperative navigation
93
+ - `Link: ElementType<LinkProps>` -- router-aware anchor component; `LinkProps` extends `AnchorHTMLAttributes\<HTMLAnchorElement>` with a required `to: string` field
94
+
95
+ Consumers calling `useRouterContext()` get back a `RouterAdapter`. Any component that needs to read `currentPath` or call `navigate` can use the hook directly rather than prop-drilling from a parent.
96
+
97
+ ### TanStackRouterBridge
98
+
99
+ `TanStackRouterBridge` is the concrete `RouterAdapter` implementation for TanStack Router. Exported from `packages/ui/src/router/providers/TanStackRouterBridge.tsx` and available via `@elevasis/ui/router`.
100
+
101
+ It wraps `RouterProvider` and supplies the adapter by reading `useLocation` and `useRouter` from `@tanstack/react-router`. The adapter value is stabilized with `useMemo` to prevent unnecessary re-renders in downstream consumers. Place it inside TanStack's `RouterProvider`:
102
+
103
+ ```tsx
104
+ <TanStackRouterBridge>
105
+ <ElevasisFeaturesProvider ...>
106
+ <FeatureShell>...</FeatureShell>
107
+ </ElevasisFeaturesProvider>
108
+ </TanStackRouterBridge>
109
+ ```
110
+
111
+ Consumers using a different router can implement `RouterAdapter` directly and pass it to `RouterProvider` instead of using this bridge.
112
+
113
+ ### Feature panels (CRM)
114
+
115
+ - `MyTasksPanel`, `QuickCreateActions` -- exported from `@elevasis/ui/features/crm`, included by default in `CrmSidebarMiddle`; consumers composing their own middle can re-include them explicitly.
116
+
117
+ ### Feature pages
118
+
119
+ Every page each feature renders is exported from its feature barrel, so consumers can wrap with custom chrome without forking:
120
+
121
+ - CRM: `DealsListPage`, `DealDetailPage`
122
+ - Lead-gen: `LeadGenOverviewPage`, `LeadGenDeliverabilityPage`, `LeadGenListsPage`, `LeadGenListDetailPage`, `LeadGenCompaniesPage`, `LeadGenContactsPage`
123
+ - Delivery: `AllTasksPage`, `UpcomingMilestonesPage`, `ProjectsListPage`, `ProjectDetailPage`
124
+ - Operations, monitoring, settings -- full page inventories exported from their feature barrels
125
+
126
+ ## Consumer Patterns
127
+
128
+ ### 1. Extend nav items (shortest path)
129
+
130
+ ```tsx
131
+ import { CrmSidebar, CrmSidebarMiddle, CRM_ITEMS } from '@elevasis/ui/features/crm'
132
+ import { IconFileText } from '@tabler/icons-react'
133
+
134
+ const customItems = [
135
+ ...CRM_ITEMS,
136
+ { label: 'Reports', to: '/crm/reports', icon: IconFileText, exact: false },
137
+ ]
138
+
139
+ const MyCrmSidebar = () => (
140
+ <CrmSidebar>
141
+ <CrmSidebarMiddle items={customItems} />
142
+ </CrmSidebar>
143
+ )
144
+
145
+ const customCrmManifest = { ...crmManifest, sidebar: MyCrmSidebar }
146
+ ```
147
+
148
+ ### 2. Inject a new delivery section (compose from primitives)
149
+
150
+ ```tsx
151
+ import {
152
+ ProjectsSidebar,
153
+ DELIVERY_PROJECT_ITEMS,
154
+ DELIVERY_WORK_ITEMS,
155
+ DELIVERY_COMMUNICATION_ITEMS,
156
+ } from '@elevasis/ui/features/delivery'
157
+ import { SubshellSidebarSection, SubshellNavList } from '@elevasis/ui/layout'
158
+ import { Stack } from '@mantine/core'
159
+ import { IconChartBar, IconListCheck, IconMessageCircle } from '@tabler/icons-react'
160
+
161
+ const ANALYTICS_ITEMS = [
162
+ { label: 'Dashboard', to: '/projects/analytics', icon: IconChartBar, exact: true },
163
+ ]
164
+
165
+ const MyDeliveryMiddle = () => (
166
+ <Stack gap={0} style={{ flex: 1, overflowY: 'auto' }}>
167
+ <Stack gap={0} p="sm"><SubshellNavList items={DELIVERY_PROJECT_ITEMS} /></Stack>
168
+ <SubshellSidebarSection icon={IconListCheck} label="Work" withTopBorder />
169
+ <Stack gap={0} p="sm"><SubshellNavList items={DELIVERY_WORK_ITEMS} /></Stack>
170
+ <SubshellSidebarSection icon={IconMessageCircle} label="Communication" withTopBorder />
171
+ <Stack gap={0} p="sm"><SubshellNavList items={DELIVERY_COMMUNICATION_ITEMS} /></Stack>
172
+ <SubshellSidebarSection icon={IconChartBar} label="Analytics" withTopBorder />
173
+ <Stack gap={0} p="sm"><SubshellNavList items={ANALYTICS_ITEMS} /></Stack>
174
+ </Stack>
175
+ )
176
+
177
+ const MyDeliverySidebar = () => (
178
+ <ProjectsSidebar>
179
+ <MyDeliveryMiddle />
180
+ </ProjectsSidebar>
181
+ )
182
+ ```
183
+
184
+ ### 3. Wrap a page with custom chrome
185
+
186
+ ```tsx
187
+ // in a route file
188
+ import { DealsListPage } from '@elevasis/ui/features/crm'
189
+
190
+ export default function CustomDealsRoute() {
191
+ return (
192
+ <MyChromeWrapper>
193
+ <DealsListPage />
194
+ </MyChromeWrapper>
195
+ )
196
+ }
197
+ ```
198
+
199
+ ## Operations & Unmodified Features
200
+
201
+ - **Operations** -- uses `sidebar?: ComponentType` with route-aware dispatch; the items-prop pattern does not apply.
202
+ - **Monitoring, Settings** -- no subshell sidebar. No customization surface needed.
203
+ - **SEO** -- has sidebar components (`SEOSidebar`, `SEOSidebarTop`, `SEOSidebarMiddle`) but no page inventory in `@elevasis/ui`.
204
+
205
+ ## Publish-Surface Discipline
206
+
207
+ All primitives above flow through published subpaths (`@elevasis/ui/features/<feature>`, `@elevasis/ui/layout`, `@elevasis/ui/router/context`). Any new primitive must sync four files per `.claude/rules/ui-package.md`: `package.json` `exports`, `publishConfig.exports`, `rollup.dts.config.mjs`, and `tsup.config.ts`.
208
+
209
+ The published barrel (`packages/ui/src/provider/published.ts`) remains headless -- no Mantine-dependent visual pieces leak into the external contract.
210
+
211
+ ## What Not to Do
212
+
213
+ - **Don't fork sidebar files into your app tree.** You own upstream drift forever.
214
+ - **Don't redefine `NavItem` locally.** Import it from `@elevasis/ui/layout` so future shape changes stay coherent.
215
+ - **Don't monkey-patch `FEATURE_MANIFESTS`.** It's a published convenience constant; consumers build their own manifest array and pass it to the provider.
216
+ - **Don't add discriminated-union sidebar configuration to manifests.** The single pattern (assign a component to `manifest.sidebar`) is intentional.
@@ -0,0 +1,239 @@
1
+ ---
2
+ title: Customizing Features
3
+ description: One pattern for customizing feature sidebars and pages -- set manifest.sidebar to a component composing the feature's published pieces. Decision tree + three worked examples.
4
+ ---
5
+
6
+ # Customizing Features
7
+
8
+ **Status:** 🟢 Stable
9
+
10
+ > **Requires `@elevasis/ui` >= 2.8.1.**
11
+
12
+ There is one pattern for customizing a feature's sidebar or pages: set `sidebar` on your manifest override to a component that composes the feature's published pieces. Pass custom `items` to swap the nav array, or compose `SidebarTop` + `SidebarMiddle` + exported panels directly for structural changes. Never fork a source file as a first resort -- if you copy source you own upstream drift forever.
13
+
14
+ ## Decision Tree
15
+
16
+ **Adding or changing nav items?** Extend the exported item array and pass to `<*SidebarMiddle items={...}>`. This covers the vast majority of sidebar customization needs.
17
+
18
+ **Structural changes (injecting panels, reordering sections, wrapping sections)?** Compose `SidebarTop`, `SidebarMiddle`, and exported panels (`MyTasksPanel`, `QuickCreateActions`) directly into your own component and assign it to the manifest's `sidebar` field.
19
+
20
+ **Wrapping pages?** Import the feature's page component and wrap with custom chrome in a route file.
21
+
22
+ **Neither fits?** The missing export is a bug in `@elevasis/ui` -- file an issue. Copying source is physically possible but unsupported. You own upstream drift forever.
23
+
24
+ ## Worked Example 1: Sidebar Nav Extension
25
+
26
+ Extend CRM's nav by spreading `CRM_ITEMS` and appending a Reports entry. Pass the extended array to `CrmSidebarMiddle`.
27
+
28
+ Wire the override in `ui/src/routes/__root.tsx`, where feature manifests are assembled before being passed to `ElevasisFeaturesProvider`.
29
+
30
+ ```tsx
31
+ // ui/src/routes/__root.tsx (excerpt -- add after existing imports)
32
+
33
+ import { crmManifest, CrmSidebar, CrmSidebarMiddle, CRM_ITEMS } from '@elevasis/ui/features/crm'
34
+ import { IconFileText } from '@tabler/icons-react'
35
+ import type { NavItem } from '@elevasis/ui/layout'
36
+
37
+ // Extend the published CRM nav array with a project-specific Reports entry.
38
+ const customCrmItems: NavItem[] = [
39
+ ...CRM_ITEMS,
40
+ { label: 'Reports', to: '/crm/reports', icon: IconFileText, exact: false }
41
+ ]
42
+
43
+ // Compose CrmSidebar (Top + Middle) with the extended item list.
44
+ const MyCrmSidebar = () => (
45
+ <CrmSidebar>
46
+ <CrmSidebarMiddle items={customCrmItems} />
47
+ </CrmSidebar>
48
+ )
49
+
50
+ // Override the manifest's sidebar, keeping every other field unchanged.
51
+ const customCrmManifest = { ...crmManifest, sidebar: MyCrmSidebar }
52
+ ```
53
+
54
+ Then swap `crmManifest` for `customCrmManifest` in the `FEATURE_MANIFESTS` array:
55
+
56
+ ```tsx
57
+ // In the FEATURE_MANIFESTS array (same file)
58
+ const FEATURE_MANIFESTS: FeatureModule[] = [
59
+ leadGenManifest,
60
+ customCrmManifest, // <-- replaces crmManifest
61
+ deliveryManifest,
62
+ operationsManifest,
63
+ monitoringManifest,
64
+ settingsManifest
65
+ ]
66
+ ```
67
+
68
+ Your new `/crm/reports` route file still needs to be created in `ui/src/routes/crm/` -- the nav item just controls the link.
69
+
70
+ ## Worked Example 2: Sidebar with Injected Panel
71
+
72
+ Inject a custom panel between the nav list and the My Tasks section. Compose the parts explicitly rather than relying on `CrmSidebarMiddle`'s default layout.
73
+
74
+ ```tsx
75
+ // ui/src/routes/__root.tsx (excerpt)
76
+
77
+ import {
78
+ crmManifest,
79
+ CrmSidebar,
80
+ CrmSidebarMiddle,
81
+ CrmSidebarTop,
82
+ MyTasksPanel,
83
+ QuickCreateActions,
84
+ CRM_ITEMS
85
+ } from '@elevasis/ui/features/crm'
86
+ import { SubshellNavList, SubshellSidebarSection } from '@elevasis/ui/layout'
87
+ import { Stack } from '@mantine/core'
88
+ import { IconChartBar } from '@tabler/icons-react'
89
+
90
+ // Custom middle that inserts a Pipeline Health panel between nav and My Tasks.
91
+ const MyCrmMiddle = () => (
92
+ <Stack gap={0} style={{ flex: 1, overflowY: 'auto' }}>
93
+ <Stack gap={0} p="sm">
94
+ <SubshellNavList items={CRM_ITEMS} />
95
+ </Stack>
96
+ {/* Inject custom panel here */}
97
+ <SubshellSidebarSection icon={IconChartBar} label="Pipeline Health" withTopBorder />
98
+ <Stack gap={0} p="sm">
99
+ <PipelineHealthWidget />
100
+ </Stack>
101
+ {/* Preserve the My Tasks section from the default middle */}
102
+ <MyTasksPanel footer={<QuickCreateActions showSectionLabel={false} />} showSectionLabel />
103
+ </Stack>
104
+ )
105
+
106
+ // Full sidebar: Top stays unchanged, Middle is replaced.
107
+ const MyCrmSidebar = () => (
108
+ <CrmSidebar>
109
+ <MyCrmMiddle />
110
+ </CrmSidebar>
111
+ )
112
+
113
+ const customCrmManifest = { ...crmManifest, sidebar: MyCrmSidebar }
114
+ ```
115
+
116
+ `PipelineHealthWidget` is a component you define in `ui/src/features/crm/` or `ui/src/shared/components/`. Keep route files and `__root.tsx` thin -- push component logic into feature modules.
117
+
118
+ ## Worked Example 3: Page Wrapping
119
+
120
+ Import a feature's page component and wrap it with custom chrome in a route file. This keeps the feature's business logic intact while adding project-specific framing (banners, secondary nav, analytics context, etc.).
121
+
122
+ The exported CRM page components are `DealsListPage` and `DealDetailPage`:
123
+
124
+ ```tsx
125
+ // ui/src/routes/crm/deals.index.tsx
126
+
127
+ import { createFileRoute } from '@tanstack/react-router'
128
+ import { DealsListPage } from '@elevasis/ui/features/crm'
129
+ import { ProtectedRoute } from '@/features/auth'
130
+ import { ProjectAnnouncementBanner } from '@/shared/components/ProjectAnnouncementBanner'
131
+ import { Stack } from '@mantine/core'
132
+
133
+ export const Route = createFileRoute('/crm/deals/')({
134
+ component: DealsListPageGuarded
135
+ })
136
+
137
+ function DealsListPageGuarded() {
138
+ return (
139
+ <ProtectedRoute>
140
+ <Stack gap={0}>
141
+ <ProjectAnnouncementBanner context="crm-deals" />
142
+ <DealsListPage />
143
+ </Stack>
144
+ </ProtectedRoute>
145
+ )
146
+ }
147
+ ```
148
+
149
+ The pattern is identical for `DealDetailPage`. Import the published component, wrap it in your chrome, export as the route component. No forking, no copying source.
150
+
151
+ ## Delivery: Three-Section Sidebar
152
+
153
+ Delivery keeps three named sections (Projects, Work, Communication). It exports three separate arrays and `ProjectsSidebarMiddle` accepts three matching optional props -- one per section. There is no single `items` prop.
154
+
155
+ ```tsx
156
+ // Adding items to an existing delivery section
157
+ import {
158
+ deliveryManifest,
159
+ ProjectsSidebar,
160
+ ProjectsSidebarMiddle,
161
+ DELIVERY_PROJECT_ITEMS,
162
+ DELIVERY_WORK_ITEMS,
163
+ DELIVERY_COMMUNICATION_ITEMS
164
+ } from '@elevasis/ui/features/delivery'
165
+ import type { NavItem } from '@elevasis/ui/layout'
166
+ import { IconChartBar } from '@tabler/icons-react'
167
+
168
+ const customProjectItems: NavItem[] = [
169
+ ...DELIVERY_PROJECT_ITEMS,
170
+ { label: 'Analytics', to: '/projects/analytics', icon: IconChartBar, exact: false }
171
+ ]
172
+
173
+ const MyDeliverySidebar = () => (
174
+ <ProjectsSidebar>
175
+ <ProjectsSidebarMiddle
176
+ projectItems={customProjectItems}
177
+ workItems={DELIVERY_WORK_ITEMS}
178
+ communicationItems={DELIVERY_COMMUNICATION_ITEMS}
179
+ />
180
+ </ProjectsSidebar>
181
+ )
182
+
183
+ const customDeliveryManifest = { ...deliveryManifest, sidebar: MyDeliverySidebar }
184
+ ```
185
+
186
+ **Adding a new section** (not extending an existing one) requires composing from primitives. Use `SubshellSidebarSection` as the section header and `SubshellNavList` to render items inside it:
187
+
188
+ ```tsx
189
+ import {
190
+ ProjectsSidebar,
191
+ DELIVERY_PROJECT_ITEMS,
192
+ DELIVERY_WORK_ITEMS,
193
+ DELIVERY_COMMUNICATION_ITEMS
194
+ } from '@elevasis/ui/features/delivery'
195
+ import { SubshellSidebarSection, SubshellNavList } from '@elevasis/ui/layout'
196
+ import { Stack } from '@mantine/core'
197
+ import { IconChartBar, IconListCheck, IconMessageCircle, IconBriefcase } from '@tabler/icons-react'
198
+ import type { NavItem } from '@elevasis/ui/layout'
199
+
200
+ const ANALYTICS_ITEMS: NavItem[] = [
201
+ { label: 'Dashboard', to: '/projects/analytics', icon: IconChartBar, exact: true }
202
+ ]
203
+
204
+ const MyDeliveryMiddle = () => (
205
+ <Stack gap={0} style={{ flex: 1, overflowY: 'auto' }}>
206
+ <Stack gap={0} p="sm">
207
+ <SubshellNavList items={DELIVERY_PROJECT_ITEMS} />
208
+ </Stack>
209
+ <SubshellSidebarSection icon={IconListCheck} label="Work" withTopBorder />
210
+ <Stack gap={0} p="sm">
211
+ <SubshellNavList items={DELIVERY_WORK_ITEMS} />
212
+ </Stack>
213
+ <SubshellSidebarSection icon={IconMessageCircle} label="Communication" withTopBorder />
214
+ <Stack gap={0} p="sm">
215
+ <SubshellNavList items={DELIVERY_COMMUNICATION_ITEMS} />
216
+ </Stack>
217
+ <SubshellSidebarSection icon={IconChartBar} label="Analytics" withTopBorder />
218
+ <Stack gap={0} p="sm">
219
+ <SubshellNavList items={ANALYTICS_ITEMS} />
220
+ </Stack>
221
+ </Stack>
222
+ )
223
+
224
+ const MyDeliverySidebar = () => <ProjectsSidebar><MyDeliveryMiddle /></ProjectsSidebar>
225
+ ```
226
+
227
+ This is the same compose-from-primitives path CRM consumers use for structural changes. No special API -- just `SubshellSidebarSection` + `SubshellNavList` from `@elevasis/ui/layout`.
228
+
229
+ ## Imports Cheat Sheet
230
+
231
+ | What you need | Import from |
232
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
233
+ | `CRM_ITEMS`, `CrmSidebar`, `CrmSidebarTop`, `CrmSidebarMiddle`, `MyTasksPanel`, `QuickCreateActions`, `DealsListPage`, `DealDetailPage`, `crmManifest` | `@elevasis/ui/features/crm` |
234
+ | `LEAD_GEN_ITEMS`, `LeadGenSidebar`, `LeadGenSidebarTop`, `LeadGenSidebarMiddle`, `leadGenManifest` | `@elevasis/ui/features/lead-gen` |
235
+ | `DELIVERY_PROJECT_ITEMS`, `DELIVERY_WORK_ITEMS`, `DELIVERY_COMMUNICATION_ITEMS`, `ProjectsSidebar`, `ProjectsSidebarTop`, `ProjectsSidebarMiddle`, `deliveryManifest` | `@elevasis/ui/features/delivery` |
236
+ | `SubshellNavList`, `SubshellNavItem`, `SubshellSidebarSection`, `NavItem` | `@elevasis/ui/layout` |
237
+ | `FeatureModule` | `@elevasis/ui/provider` |
238
+
239
+ Operations, monitoring, and settings have no customizable sidebar items. Operations sidebar is route-aware dispatch and not extended via the `items` pattern.