@elevasis/sdk 1.10.0 → 1.12.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.
- package/dist/cli.cjs +52 -149
- package/dist/index.d.ts +468 -198
- package/dist/index.js +225 -147
- package/dist/test-utils/index.d.ts +272 -99
- package/dist/test-utils/index.js +4756 -125
- package/dist/types/worker/adapters/llm.d.ts +1 -1
- package/dist/worker/index.js +14 -6
- package/package.json +2 -2
- package/reference/claude-config/rules/agent-start-here.md +14 -14
- package/reference/claude-config/skills/configure/SKILL.md +3 -3
- package/reference/claude-config/skills/setup/SKILL.md +6 -6
- package/reference/claude-config/sync-notes/2026-04-25-auth-role-system-and-settings-roles.md +55 -0
- package/reference/claude-config/sync-notes/2026-04-27-crm-hitl-action-layer-cutover.md +101 -0
- package/reference/cli.mdx +57 -0
- package/reference/deployment/provided-features.mdx +40 -267
- package/reference/examples/organization-model.ts +99 -564
- package/reference/packages/core/src/organization-model/README.md +102 -97
- package/reference/resources/types.mdx +72 -163
- package/reference/scaffold/core/organization-graph.mdx +92 -272
- package/reference/scaffold/core/organization-model.mdx +155 -320
- package/reference/scaffold/index.mdx +3 -0
- package/reference/scaffold/operations/propagation-pipeline.md +4 -1
- package/reference/scaffold/operations/scaffold-maintenance.md +3 -0
- package/reference/scaffold/operations/workflow-recipes.md +13 -10
- package/reference/scaffold/recipes/add-a-feature.md +105 -158
- package/reference/scaffold/recipes/add-a-resource.md +88 -158
- package/reference/scaffold/recipes/customize-organization-model.md +144 -400
- package/reference/scaffold/recipes/extend-a-base-entity.md +11 -8
- package/reference/scaffold/recipes/gate-by-feature-or-admin.md +117 -158
- package/reference/scaffold/recipes/index.md +3 -0
- package/reference/scaffold/reference/contracts.md +107 -435
- package/reference/scaffold/reference/feature-registry.md +11 -8
- package/reference/scaffold/reference/glossary.md +74 -105
- package/reference/scaffold/ui/composition-extensibility.mdx +3 -0
- package/reference/scaffold/ui/customization.md +3 -0
- package/reference/scaffold/ui/feature-flags-and-gating.md +29 -231
- package/reference/scaffold/ui/feature-shell.mdx +53 -219
- package/reference/scaffold/ui/recipes.md +65 -397
- package/reference/claude-config/logs/pre-edit-vibe-gate.log +0 -40
- package/reference/claude-config/logs/scaffold-registry-reminder.log +0 -38
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: UI Recipes
|
|
3
|
-
description:
|
|
3
|
+
description: Current recipes for adding pages, feature-gated routes, feature components, theme tokens, Organization Model sidebar entries, and executable resources.
|
|
4
4
|
---
|
|
5
|
+
<!-- @generated by packages/sdk/scripts/copy-reference-docs.mjs -- DO NOT EDIT -->
|
|
6
|
+
<!-- Regenerate: pnpm scaffold:sync -->
|
|
5
7
|
|
|
6
|
-
# UI Recipes
|
|
7
|
-
|
|
8
|
-
**Status:** 🟢 Stable
|
|
9
|
-
|
|
10
|
-
Copy-paste starting points for the five most common UI tasks. Each recipe shows the complete file contents or a tight diff first, then explains what each part does. For deeper background on auth, feature flags, or customization, follow the links at the bottom of each section.
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
# UI Recipes
|
|
13
10
|
|
|
14
11
|
## 1. Add a Top-Level Page
|
|
15
12
|
|
|
16
|
-
### Code
|
|
17
|
-
|
|
18
13
|
Create `ui/src/routes/my-feature.index.tsx`:
|
|
19
14
|
|
|
20
15
|
```tsx
|
|
@@ -42,36 +37,26 @@ function MyFeatureRouteComponent() {
|
|
|
42
37
|
}
|
|
43
38
|
```
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
Add the sidebar entry as a feature node in `core/config/organization-model.ts`:
|
|
46
41
|
|
|
47
42
|
```ts
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
features: [
|
|
44
|
+
{
|
|
45
|
+
id: 'my-feature',
|
|
46
|
+
label: 'My Feature',
|
|
47
|
+
enabled: true,
|
|
48
|
+
path: '/my-feature',
|
|
49
|
+
icon: 'star',
|
|
50
|
+
uiPosition: 'sidebar-primary'
|
|
51
|
+
}
|
|
53
52
|
]
|
|
54
53
|
```
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
TanStack Router derives the path from the filename. The sidebar is derived from `OrganizationModel.features`.
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
- **`ProtectedRoute`:** redirects unauthenticated users to `/login?returnTo=<current-path>` and shows a full-screen loader while auth is initializing. Always wrap top-level pages with it.
|
|
60
|
-
- **Layout components:** `AppTopbarAdjusterWrapper` adds the correct top padding to clear the topbar. `AppShellContentContainer` constrains the content to the main column. `PageContainer` adds consistent horizontal padding.
|
|
61
|
-
- **`routeTree.gen.ts`:** TanStack Router regenerates this file on `pnpm dev`. Never edit it by hand.
|
|
62
|
-
- **Nav item:** Icons come from `@tabler/icons-react`. No other icon library.
|
|
57
|
+
## 2. Add a Feature-Gated Page
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## 1a. Add a Feature-Gated Top-Level Page
|
|
69
|
-
|
|
70
|
-
If the page should only be visible and accessible when a feature is enabled, add `featureKey` to the nav item and wrap the route component with `FeatureGuard`.
|
|
71
|
-
|
|
72
|
-
### Code
|
|
73
|
-
|
|
74
|
-
`ui/src/routes/my-feature.index.tsx` (change from recipe 1):
|
|
59
|
+
Wrap the route with `FeatureGuard` and use the same feature ID as the org model node.
|
|
75
60
|
|
|
76
61
|
```tsx
|
|
77
62
|
import { FeatureGuard } from '@/features/auth/guards/FeatureGuard'
|
|
@@ -93,29 +78,11 @@ function MyFeatureRouteComponent() {
|
|
|
93
78
|
}
|
|
94
79
|
```
|
|
95
80
|
|
|
96
|
-
`
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
{ label: 'My Feature', icon: IconStar, link: '/my-feature', featureKey: 'my-feature' }
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Explanation
|
|
103
|
-
|
|
104
|
-
- **`featureKey` on nav item:** hides the nav entry when the feature is disabled for the organization or the current membership.
|
|
105
|
-
- **`FeatureGuard` on the route:** hard-stops at the route boundary when someone navigates directly to the URL. Shows a notification and redirects to `/` when access is denied.
|
|
106
|
-
- **Feature keys** are declared in `foundations/config/organization-model.ts` under `features.enabled`. Add a new key there before using it in a guard.
|
|
107
|
-
|
|
108
|
-
For the full three-concept model (`featureKey` / `FeatureGuard` / `AdminGuard`), see `./feature-flags-and-gating.md`.
|
|
109
|
-
|
|
110
|
-
---
|
|
81
|
+
Set `enabled: false` in the feature node to hide it by default. Use `requiresAdmin: true` on the feature node plus `AdminGuard` in the route for admin-only pages.
|
|
111
82
|
|
|
112
|
-
##
|
|
83
|
+
## 3. Add a Nested Page
|
|
113
84
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
### Code
|
|
117
|
-
|
|
118
|
-
First, ensure the layout file exists. For a new section called `reporting`, create `ui/src/routes/reporting.tsx`:
|
|
85
|
+
Create a layout file for the section and child route files under the same directory.
|
|
119
86
|
|
|
120
87
|
```tsx
|
|
121
88
|
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
|
@@ -134,400 +101,101 @@ function ReportingLayoutComponent() {
|
|
|
134
101
|
}
|
|
135
102
|
```
|
|
136
103
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
```tsx
|
|
140
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
141
|
-
import { AppShellContentContainer, AppTopbarAdjusterWrapper, PageContainer } from '@elevasis/ui/layout'
|
|
142
|
-
import { ReportingOverviewPage } from '@/features/reporting/components/ReportingOverviewPage'
|
|
143
|
-
|
|
144
|
-
export const Route = createFileRoute('/reporting/overview/')({
|
|
145
|
-
component: ReportingOverviewRouteComponent
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
function ReportingOverviewRouteComponent() {
|
|
149
|
-
return (
|
|
150
|
-
<AppTopbarAdjusterWrapper>
|
|
151
|
-
<AppShellContentContainer>
|
|
152
|
-
<PageContainer>
|
|
153
|
-
<ReportingOverviewPage />
|
|
154
|
-
</PageContainer>
|
|
155
|
-
</AppShellContentContainer>
|
|
156
|
-
</AppTopbarAdjusterWrapper>
|
|
157
|
-
)
|
|
158
|
-
}
|
|
159
|
-
```
|
|
104
|
+
Add dotted feature nodes for the sidebar hierarchy:
|
|
160
105
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
import { SubshellContentContainer, PageContainer } from '@elevasis/ui/layout'
|
|
166
|
-
import { ReportDetailPage } from '@/features/reporting/components/ReportDetailPage'
|
|
167
|
-
|
|
168
|
-
export const Route = createFileRoute('/reporting/reports/$reportId/')({
|
|
169
|
-
component: ReportDetailRouteComponent
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
function ReportDetailRouteComponent() {
|
|
173
|
-
const { reportId } = Route.useParams()
|
|
174
|
-
|
|
175
|
-
return (
|
|
176
|
-
<SubshellContentContainer>
|
|
177
|
-
<PageContainer>
|
|
178
|
-
<ReportDetailPage reportId={reportId} />
|
|
179
|
-
</PageContainer>
|
|
180
|
-
</SubshellContentContainer>
|
|
181
|
-
)
|
|
182
|
-
}
|
|
106
|
+
```ts
|
|
107
|
+
{ id: 'reporting', label: 'Reporting', enabled: true, uiPosition: 'sidebar-primary' },
|
|
108
|
+
{ id: 'reporting.overview', label: 'Overview', enabled: true, path: '/reporting/overview' },
|
|
109
|
+
{ id: 'reporting.reports', label: 'Reports', enabled: true, path: '/reporting/reports' }
|
|
183
110
|
```
|
|
184
111
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- **Layout file:** `reporting.tsx` matches the `/reporting` prefix. TanStack Router renders child routes into its `<Outlet />`. Guards placed here apply to every child automatically -- no need to repeat `ProtectedRoute` in every child.
|
|
188
|
-
- **File naming convention:** `reporting/overview.index.tsx` maps to `/reporting/overview/`. The directory matches the layout prefix; the file maps to the path segment.
|
|
189
|
-
- **Dynamic params:** `$reportId` in the filename becomes a typed param. `Route.useParams()` returns `{ reportId: string }` with full type safety -- no casting needed.
|
|
190
|
-
- **`SubshellContentContainer` vs `AppShellContentContainer`:** use `SubshellContentContainer` inside sections that already have a shared subshell (like operations). Use `AppShellContentContainer` for top-level full-width pages.
|
|
191
|
-
- **Guards in children:** if the layout already wraps `ProtectedRoute`, children do not need to repeat it. Add `FeatureGuard` in the layout if the entire section is gated.
|
|
192
|
-
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
## 3. Add a Feature-Scoped Component
|
|
196
|
-
|
|
197
|
-
### When to use `ui/src/features/<name>/`
|
|
198
|
-
|
|
199
|
-
Create a feature directory when:
|
|
200
|
-
|
|
201
|
-
- Logic is reused across two or more route files, OR
|
|
202
|
-
- The feature owns its own context, custom hooks, or local state that does not belong to a single route.
|
|
112
|
+
Containers omit `path`; leaves provide `path`.
|
|
203
113
|
|
|
204
|
-
|
|
114
|
+
## 4. Add a Feature-Scoped Component
|
|
205
115
|
|
|
206
|
-
|
|
116
|
+
Use `ui/src/features/<name>/` when logic is shared across routes or owns local state.
|
|
207
117
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
Standard feature directory layout:
|
|
211
|
-
|
|
212
|
-
```
|
|
118
|
+
```text
|
|
213
119
|
ui/src/features/reporting/
|
|
214
120
|
components/
|
|
215
|
-
ReportingOverviewPage.tsx
|
|
216
|
-
ReportCard.tsx
|
|
121
|
+
ReportingOverviewPage.tsx
|
|
122
|
+
ReportCard.tsx
|
|
217
123
|
hooks/
|
|
218
|
-
useReports.ts
|
|
124
|
+
useReports.ts
|
|
219
125
|
utils/
|
|
220
|
-
formatReportDate.ts
|
|
221
|
-
types.ts
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
`ui/src/features/reporting/hooks/useReports.ts`:
|
|
225
|
-
|
|
226
|
-
```ts
|
|
227
|
-
import { useQuery } from '@tanstack/react-query'
|
|
228
|
-
import { useApiClient } from '@/lib/hooks/useApiClient'
|
|
229
|
-
|
|
230
|
-
export function useReports() {
|
|
231
|
-
const { apiRequest, isOrganizationReady } = useApiClient()
|
|
232
|
-
|
|
233
|
-
return useQuery({
|
|
234
|
-
queryKey: ['reports'],
|
|
235
|
-
queryFn: () => apiRequest('/reports', { method: 'GET' }),
|
|
236
|
-
enabled: isOrganizationReady
|
|
237
|
-
})
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
`ui/src/features/reporting/components/ReportingOverviewPage.tsx`:
|
|
242
|
-
|
|
243
|
-
```tsx
|
|
244
|
-
import { Stack, Text } from '@mantine/core'
|
|
245
|
-
import { useReports } from '../hooks/useReports'
|
|
246
|
-
import { ReportCard } from './ReportCard'
|
|
247
|
-
|
|
248
|
-
export function ReportingOverviewPage() {
|
|
249
|
-
const { data: reports, isLoading } = useReports()
|
|
250
|
-
|
|
251
|
-
if (isLoading) return <Text>Loading...</Text>
|
|
252
|
-
|
|
253
|
-
return (
|
|
254
|
-
<Stack>
|
|
255
|
-
{reports?.map((report) => (
|
|
256
|
-
<ReportCard key={report.id} report={report} />
|
|
257
|
-
))}
|
|
258
|
-
</Stack>
|
|
259
|
-
)
|
|
260
|
-
}
|
|
126
|
+
formatReportDate.ts
|
|
127
|
+
types.ts
|
|
261
128
|
```
|
|
262
129
|
|
|
263
|
-
|
|
130
|
+
Use `@/*` imports for app-local cross-feature imports and keep route layout concerns in route files.
|
|
264
131
|
|
|
265
|
-
|
|
266
|
-
- **Server state in Query, client state in Zustand:** if data comes from an API, it belongs in a `useQuery` hook, not a Zustand slice. Zustand is for client-only UI state (open/close, selection, optimistic updates).
|
|
267
|
-
- **`useApiClient`:** from `@/lib/hooks/useApiClient`. Returns `apiRequest` (an authenticated fetch wrapper) and `isOrganizationReady` (use this as `enabled` to avoid firing requests before the org is loaded).
|
|
268
|
-
- **`SubshellContentContainer` / `PageContainer`:** added in the route file, not in the feature component. Feature components should be layout-agnostic -- the route decides the shell treatment.
|
|
269
|
-
- **No React, no Node imports in `foundations/`:** types shared between frontend and `operations/` live in `foundations/`. Feature components live only in `ui/src/features/`.
|
|
270
|
-
|
|
271
|
-
---
|
|
132
|
+
## 5. Change Theme Tokens
|
|
272
133
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
### Code
|
|
276
|
-
|
|
277
|
-
Edit `ui/src/config/theme.ts`. The most common task is changing the primary color and brand feel for the `default` preset:
|
|
134
|
+
Edit `ui/src/config/theme.ts`.
|
|
278
135
|
|
|
279
136
|
```ts
|
|
280
|
-
// ui/src/config/theme.ts (diff -- change the default preset's dark token block)
|
|
281
|
-
|
|
282
137
|
default: {
|
|
283
138
|
label: 'Default',
|
|
284
|
-
description: '
|
|
285
|
-
colors: ['#
|
|
139
|
+
description: 'Project brand theme',
|
|
140
|
+
colors: ['#2563eb', '#080808', '#f2f2f5'],
|
|
286
141
|
dark: {
|
|
287
|
-
primary: '#
|
|
288
|
-
primaryContrast: '#ffffff',
|
|
289
|
-
background: '#050507',
|
|
290
|
-
surface: '#0f0f14',
|
|
291
|
-
surfaceHover: '#1a1a22',
|
|
142
|
+
primary: '#2563eb',
|
|
143
|
+
primaryContrast: '#ffffff',
|
|
144
|
+
background: '#050507',
|
|
145
|
+
surface: '#0f0f14',
|
|
146
|
+
surfaceHover: '#1a1a22',
|
|
292
147
|
text: '#ffffff',
|
|
293
148
|
textDimmed: '#b0b0c0',
|
|
294
149
|
textSubtle: '#888898',
|
|
295
|
-
border: 'rgba(255, 255, 255, 0.07)'
|
|
296
|
-
// ... keep remaining tokens
|
|
297
|
-
},
|
|
298
|
-
// light: { ... },
|
|
299
|
-
framework: {
|
|
300
|
-
defaultRadius: 'md', // Global border radius ('xs' | 'sm' | 'md' | 'lg' | 'xl')
|
|
301
|
-
fontFamily: '"Inter", sans-serif',
|
|
302
|
-
headings: { fontFamily: '"Inter", sans-serif', fontWeight: '600' }
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
To set the preset that loads by default for new users, change `defaultPreset` at the bottom of the file:
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
export const defaultPreset = 'default' // was 'tactical'
|
|
311
|
-
export const defaultColorScheme = 'dark' as const
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
To add a completely new named preset (selectable from appearance settings), append it to `themePresets`:
|
|
315
|
-
|
|
316
|
-
```ts
|
|
317
|
-
export const themePresets: Record<string, ThemePreset> = {
|
|
318
|
-
...presets,
|
|
319
|
-
default: { /* ... */ },
|
|
320
|
-
'acme-brand': {
|
|
321
|
-
label: 'Acme Brand',
|
|
322
|
-
description: 'Acme Corp official brand',
|
|
323
|
-
colors: ['#2563eb', '#0a0a0a', '#f5f5f5'],
|
|
324
|
-
dark: {
|
|
325
|
-
primary: '#2563eb',
|
|
326
|
-
primaryContrast: '#fff',
|
|
327
|
-
background: '#0a0a0a',
|
|
328
|
-
surface: '#111827',
|
|
329
|
-
surfaceHover: '#1f2937',
|
|
330
|
-
text: '#f9fafb',
|
|
331
|
-
textDimmed: '#9ca3af',
|
|
332
|
-
textSubtle: '#6b7280',
|
|
333
|
-
border: 'rgba(255,255,255,0.08)',
|
|
334
|
-
error: '#ef4444',
|
|
335
|
-
warning: '#f59e0b',
|
|
336
|
-
success: '#10b981',
|
|
337
|
-
glassBackground: 'rgba(10, 10, 10, 0.5)',
|
|
338
|
-
glassBlur: 'blur(20px) saturate(160%)',
|
|
339
|
-
shadow: '0 1px 3px rgba(0,0,0,0.4), 0 8px 24px -8px rgba(0,0,0,0.5)',
|
|
340
|
-
cardShadow: 'inset 0 1px 0 rgba(255,255,255,0.06), 0 2px 8px rgba(0,0,0,0.3)',
|
|
341
|
-
durationFast: '150ms',
|
|
342
|
-
durationNormal: '250ms',
|
|
343
|
-
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
344
|
-
destructiveFg: '#ffffff',
|
|
345
|
-
fontHeading: '"Inter", sans-serif',
|
|
346
|
-
fontSans: '"Inter", sans-serif'
|
|
347
|
-
},
|
|
348
|
-
light: { /* ... mirror pattern with light-mode values */ },
|
|
349
|
-
framework: { defaultRadius: 'md', fontFamily: '"Inter", sans-serif' },
|
|
350
|
-
fontImports: ['https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap']
|
|
150
|
+
border: 'rgba(255, 255, 255, 0.07)'
|
|
351
151
|
}
|
|
352
152
|
}
|
|
353
153
|
```
|
|
354
154
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
- **All tokens flow into CSS variables automatically.** `primary` becomes `--color-primary`, `background` becomes `--color-background`, `glassBackground` becomes `--glass-background`, and so on. Every component that references those CSS variables (the sidebar, topbar, cards, modals) picks up the change immediately without touching component code.
|
|
358
|
-
- **`framework` field:** maps directly to Mantine theme overrides. `defaultRadius` and `fontFamily` are the most commonly changed values. Component-level overrides can go here too (see the JSDoc in `theme.ts` for the pattern).
|
|
359
|
-
- **`colors` array:** the three-element shorthand is `[primary, darkBackground, lightSurface]`. It is used by the appearance settings preview swatch only -- the actual runtime values come from `dark.*` and `light.*`.
|
|
360
|
-
- **Appearance settings:** users can switch presets from the settings UI. The `defaultPreset` and `defaultColorScheme` exports set what loads for a user who has never changed their preference.
|
|
361
|
-
- **`ui/src/config/README.md`:** contains the deeper guide for `background.tsx` (full-page background treatment) and `loader.tsx` (global spinner customization), which are separate from the theme preset system.
|
|
362
|
-
|
|
363
|
-
---
|
|
364
|
-
|
|
365
|
-
## 5. Add a Nav Item
|
|
366
|
-
|
|
367
|
-
Nav items live in `ui/src/config/nav-items.ts`. The current (pre-foundations-refactor) pattern is to add entries directly to the `navItems` array.
|
|
368
|
-
|
|
369
|
-
### Code
|
|
370
|
-
|
|
371
|
-
`ui/src/config/nav-items.ts`:
|
|
372
|
-
|
|
373
|
-
```ts
|
|
374
|
-
import { IconDashboard, IconChartBar, IconUsers, IconSettings } from '@tabler/icons-react'
|
|
375
|
-
import { LinksGroupProps } from '@elevasis/ui/layout'
|
|
376
|
-
import { organizationModel } from '@foundation/config/organization-model'
|
|
377
|
-
|
|
378
|
-
export const navItems: ExtendedLinksGroupProps[] = [
|
|
379
|
-
// Top-level link (no submenu)
|
|
380
|
-
{ label: organizationModel.navigation.homeLabel, icon: IconDashboard, link: '/' },
|
|
381
|
-
|
|
382
|
-
// Top-level link gated by feature flag
|
|
383
|
-
{ label: 'Reporting', icon: IconChartBar, link: '/reporting', featureKey: 'reporting' },
|
|
384
|
-
|
|
385
|
-
// Group with sub-links
|
|
386
|
-
{
|
|
387
|
-
label: 'Team',
|
|
388
|
-
icon: IconUsers,
|
|
389
|
-
featureKey: 'operations', // hides entire group when feature is off
|
|
390
|
-
links: [
|
|
391
|
-
{ label: 'Members', link: '/team/members' },
|
|
392
|
-
{ label: 'Roles', link: '/team/roles', featureKey: 'settings' }, // sub-link can have its own key
|
|
393
|
-
]
|
|
394
|
-
},
|
|
395
|
-
|
|
396
|
-
// Admin-only top-level link
|
|
397
|
-
{ label: 'Settings', icon: IconSettings, link: '/settings/account', requiresAdmin: true }
|
|
398
|
-
]
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
### Explanation
|
|
402
|
-
|
|
403
|
-
- **`featureKey`:** when set on a group, the entire group (including its sub-links) is hidden when the feature is disabled. When set on a sub-link, only that link is hidden.
|
|
404
|
-
- **`requiresAdmin`:** hides the entry for non-admin users. Combine with `ProtectedRoute` + `AdminGuard` on the actual route to enforce the restriction at the route level too.
|
|
405
|
-
- **Icon library:** `@tabler/icons-react` only. Never Lucide, Heroicons, or others.
|
|
406
|
-
- **Dashboard entry:** `organizationModel.navigation.homeLabel` resolves to `'Dashboard'` from `foundations/config/organization-model.ts`. Use it instead of a hard-coded string so the label stays consistent with the rest of the shell.
|
|
407
|
-
|
|
408
|
-
**Upcoming change (foundations-refactor Step 8):** the nav customization pattern will become cleaner. Published feature manifests will export a `CRM_ITEMS` array (and equivalents for other features), and the sidebar will accept an `items` prop to swap or extend those arrays without editing `nav-items.ts`. When that ships, `./customization.md` will show the updated pattern. For now, `nav-items.ts` is the canonical place for host-local nav additions.
|
|
409
|
-
|
|
410
|
-
---
|
|
411
|
-
|
|
412
|
-
## Cross-References
|
|
413
|
-
|
|
414
|
-
- `./index.md` -- shell architecture, auth flow, route structure overview
|
|
415
|
-
- `./feature-flags-and-gating.md` -- full three-concept gating model (featureKey / FeatureGuard / AdminGuard)
|
|
416
|
-
- `./customization.md` -- customizing feature sidebars and pages via manifest composition
|
|
417
|
-
- `ui/src/config/README.md` -- deeper guide for theme, background, and loader config files
|
|
418
|
-
- `external/_template/.claude/rules/frontend.md` -- concise agent-oriented frontend conventions
|
|
419
|
-
|
|
420
|
-
---
|
|
155
|
+
All tokens flow into CSS variables. Change `defaultPreset` to alter the initial preset for new users.
|
|
421
156
|
|
|
422
157
|
## 6. Execute a Resource from a Surface
|
|
423
158
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
### The Three Foundations Layers
|
|
427
|
-
|
|
428
|
-
- **Org model** (`foundations/config/organization-model.ts`): semantic metadata -- which surfaces exist, what resources map to them, labels and colors.
|
|
429
|
-
- **Entity contracts** (`foundations/types/entities.ts`): typed shapes for Project, Deal, etc. -- extend `BaseProject`, `BaseDeal` from `@elevasis/core/entities`.
|
|
430
|
-
- **Resource I/O** (`foundations/types/index.ts`): Zod schemas for workflow inputs/outputs.
|
|
431
|
-
|
|
432
|
-
### Code
|
|
433
|
-
|
|
434
|
-
#### 1. Declare the resource on a surface
|
|
435
|
-
|
|
436
|
-
`foundations/config/organization-model.ts`:
|
|
437
|
-
|
|
438
|
-
```ts
|
|
439
|
-
import { defineOrganizationModel, CRM_PIPELINE_SURFACE_ID } from '@elevasis/core/organization-model'
|
|
440
|
-
|
|
441
|
-
export const organizationOverride = defineOrganizationModel({
|
|
442
|
-
branding: { /* ... */ },
|
|
443
|
-
resourceMappings: [
|
|
444
|
-
{
|
|
445
|
-
id: 'crm.pipeline.write-note',
|
|
446
|
-
surfaceId: CRM_PIPELINE_SURFACE_ID,
|
|
447
|
-
resourceId: 'my-project-write-note-workflow',
|
|
448
|
-
resourceType: 'workflow',
|
|
449
|
-
label: 'Write Note',
|
|
450
|
-
color: 'blue',
|
|
451
|
-
featureIds: ['crm'],
|
|
452
|
-
entityIds: ['crm.deal'],
|
|
453
|
-
capabilityIds: []
|
|
454
|
-
}
|
|
455
|
-
]
|
|
456
|
-
})
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
#### 2. Share the input schema
|
|
460
|
-
|
|
461
|
-
`foundations/types/index.ts`:
|
|
159
|
+
Declare the resource relationship in resource metadata:
|
|
462
160
|
|
|
463
161
|
```ts
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
162
|
+
config: {
|
|
163
|
+
resourceId: 'write-note',
|
|
164
|
+
name: 'Write Note',
|
|
165
|
+
type: 'workflow',
|
|
166
|
+
version: '1.0.0',
|
|
167
|
+
status: 'prod',
|
|
168
|
+
links: [{ nodeId: 'feature:sales.crm', kind: 'operates-on' }],
|
|
169
|
+
category: 'production'
|
|
170
|
+
}
|
|
472
171
|
```
|
|
473
172
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
`ui/src/features/crm/components/DealPanel.tsx`:
|
|
173
|
+
Render it with `RunResourceButton`:
|
|
477
174
|
|
|
478
175
|
```tsx
|
|
479
176
|
import { RunResourceButton } from '@elevasis/ui/components'
|
|
480
|
-
import { organizationModel } from '@foundation/config/organization-model'
|
|
481
177
|
import { writeNoteInputSchema } from '@foundation/types'
|
|
482
178
|
|
|
483
179
|
export function DealPanel({ dealId }: { dealId: string }) {
|
|
484
180
|
return (
|
|
485
181
|
<RunResourceButton
|
|
486
|
-
resourceId="
|
|
182
|
+
resourceId="write-note"
|
|
487
183
|
resourceType="workflow"
|
|
488
|
-
|
|
184
|
+
label="Write Note"
|
|
489
185
|
getInput={() => ({
|
|
490
186
|
schema: writeNoteInputSchema,
|
|
491
187
|
defaults: { dealId }
|
|
492
188
|
})}
|
|
493
|
-
onSuccess={(result) => console.log('Started:', result.executionId)}
|
|
494
189
|
/>
|
|
495
190
|
)
|
|
496
191
|
}
|
|
497
192
|
```
|
|
498
193
|
|
|
499
|
-
|
|
194
|
+
`links[].nodeId` binds the resource into the Organization Model graph. `category` drives operational filtering for production, diagnostic, internal, and testing resources.
|
|
500
195
|
|
|
501
|
-
|
|
502
|
-
// Plain object (no form) -- executes immediately on click
|
|
503
|
-
getInput={() => ({ dealId, note: 'Auto-generated' })}
|
|
504
|
-
|
|
505
|
-
// Mixed (form for unfixed fields) -- modal opens with dealId pre-filled
|
|
506
|
-
getInput={() => ({ schema: writeNoteInputSchema, defaults: { dealId } })}
|
|
507
|
-
|
|
508
|
-
// Full form -- modal opens with all fields blank
|
|
509
|
-
getInput={() => ({ schema: writeNoteInputSchema })}
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
#### 4. Optionally listen for live execution logs
|
|
513
|
-
|
|
514
|
-
```tsx
|
|
515
|
-
import { useExecutionLogSSE, useMergedExecution } from '@elevasis/ui/hooks'
|
|
516
|
-
// Pass the SSE manager from your app's sse.ts wrapper and apiUrl from config.
|
|
517
|
-
// For the full live-logs pattern see:
|
|
518
|
-
// apps/command-center/src/features/operations/executions/
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
### How It Works
|
|
522
|
-
|
|
523
|
-
- `RunResourceButton` inspects what `getInput()` returns: a plain object triggers execution immediately; `{ schema, defaults? }` opens a `ZodFormRenderer` modal so the user can fill in missing fields before submitting.
|
|
524
|
-
- Label and color resolve in order: explicit props > `resourceMappings` entry matching `resourceId` > default `'Run'`. The `icon` prop is prop-only and is never inferred from the mapping.
|
|
525
|
-
- `ZodFormRenderer` introspects the Zod schema at runtime and renders Mantine fields for each top-level key. Unsupported Zod types (nested objects, arrays, unions) fall back to a JSON textarea so the form always renders regardless of schema complexity.
|
|
526
|
-
- Typed feature and surface constants (e.g. `CRM_PIPELINE_SURFACE_ID`) come from `@elevasis/core/organization-model`. See Issue 4 constants for the full list.
|
|
527
|
-
- Entity contracts (Deal, Project) live in `foundations/types/entities.ts` and extend base types from `@elevasis/core/entities` -- see the entity recipe for the full shape.
|
|
528
|
-
|
|
529
|
-
### Cross-References
|
|
196
|
+
## Cross-References
|
|
530
197
|
|
|
531
|
-
- `./feature-flags-and-gating.md` --
|
|
532
|
-
-
|
|
533
|
-
-
|
|
198
|
+
- `./feature-flags-and-gating.md` -- feature and admin access model
|
|
199
|
+
- `./feature-shell.mdx` -- provider and sidebar derivation
|
|
200
|
+
- `./customization.md` -- feature sidebar customization
|
|
201
|
+
- `ui/src/config/README.md` -- theme, background, and loader config
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
[2026-04-20T09:29:53.658Z] tool=Write path=../../foundations/config/organization-model.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
2
|
-
[2026-04-20T09:29:53.702Z] tool=Edit path=../../foundations/config/extensions/deal-ecom.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
3
|
-
[2026-04-20T09:29:53.747Z] tool=MultiEdit path=../../foundations/config/organization-model.override.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
4
|
-
[2026-04-20T09:29:53.793Z] tool=Write path=../../foundations/config/organization-model.ts protected=false VIBE_APPROVED=true decision=ALLOW
|
|
5
|
-
[2026-04-20T09:29:53.837Z] tool=Edit path=../../foundations/config/extensions/deal-ecom.ts protected=false VIBE_APPROVED=true decision=ALLOW
|
|
6
|
-
[2026-04-20T09:29:53.890Z] tool=Write path=../../ui/src/routes/__root.tsx protected=false VIBE_APPROVED=false decision=ALLOW
|
|
7
|
-
[2026-04-20T09:29:53.934Z] tool=Edit path=../../operations/src/index.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
8
|
-
[2026-04-20T09:32:06.844Z] tool=Write path=../../foundations/config/organization-model.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
9
|
-
[2026-04-20T09:32:06.890Z] tool=Edit path=../../foundations/config/extensions/deal-ecom.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
10
|
-
[2026-04-20T09:32:06.934Z] tool=MultiEdit path=../../foundations/config/organization-model.override.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
11
|
-
[2026-04-20T09:32:06.978Z] tool=Write path=../../foundations/config/organization-model.ts protected=false VIBE_APPROVED=true decision=ALLOW
|
|
12
|
-
[2026-04-20T09:32:07.021Z] tool=Edit path=../../foundations/config/extensions/deal-ecom.ts protected=false VIBE_APPROVED=true decision=ALLOW
|
|
13
|
-
[2026-04-20T09:32:07.065Z] tool=Write path=../../ui/src/routes/__root.tsx protected=false VIBE_APPROVED=false decision=ALLOW
|
|
14
|
-
[2026-04-20T09:32:07.109Z] tool=Edit path=../../operations/src/index.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
15
|
-
[2026-04-20T09:32:30.340Z] tool=Write path=../../foundations/config/organization-model.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
16
|
-
[2026-04-20T09:38:14.624Z] tool=Write path=../../foundations/config/organization-model.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
17
|
-
[2026-04-20T09:40:20.586Z] tool=Write path=foundations/config/organization-model.ts protected=true VIBE_APPROVED=false decision=BLOCK
|
|
18
|
-
[2026-04-20T09:40:20.631Z] tool=Edit path=foundations/config/extensions/deal-ecom.ts protected=true VIBE_APPROVED=false decision=BLOCK
|
|
19
|
-
[2026-04-20T09:40:20.674Z] tool=MultiEdit path=foundations/config/organization-model.override.ts protected=true VIBE_APPROVED=false decision=BLOCK
|
|
20
|
-
[2026-04-20T09:40:20.718Z] tool=Write path=foundations/config/organization-model.ts protected=true VIBE_APPROVED=true decision=ALLOW
|
|
21
|
-
[2026-04-20T09:40:20.762Z] tool=Edit path=foundations/config/extensions/deal-ecom.ts protected=true VIBE_APPROVED=true decision=ALLOW
|
|
22
|
-
[2026-04-20T09:40:20.807Z] tool=Write path=ui/src/routes/__root.tsx protected=false VIBE_APPROVED=false decision=ALLOW
|
|
23
|
-
[2026-04-20T09:40:20.905Z] tool=Edit path=operations/src/index.ts protected=false VIBE_APPROVED=false decision=ALLOW
|
|
24
|
-
[2026-04-21T02:33:54.792Z] tool=Write path=../../apps/docs/content/docs/in-progress/active-development/elevasis-sdk-cli-audit/index.mdx protected=false VIBE_APPROVED=false decision=ALLOW
|
|
25
|
-
[2026-04-21T02:34:53.555Z] tool=Edit path=CLAUDE.md protected=false VIBE_APPROVED=false decision=ALLOW
|
|
26
|
-
[2026-04-21T02:34:54.867Z] tool=Edit path=.claude/skills/deploy/SKILL.md protected=false VIBE_APPROVED=false decision=ALLOW
|
|
27
|
-
[2026-04-21T02:35:01.437Z] tool=Edit path=.claude/skills/project/SKILL.md protected=false VIBE_APPROVED=false decision=ALLOW
|
|
28
|
-
[2026-04-21T02:35:12.254Z] tool=Edit path=.claude/skills/deploy/SKILL.md protected=false VIBE_APPROVED=false decision=ALLOW
|
|
29
|
-
[2026-04-21T02:35:14.878Z] tool=Edit path=.claude/skills/status/SKILL.md protected=false VIBE_APPROVED=false decision=ALLOW
|
|
30
|
-
[2026-04-21T02:35:16.589Z] tool=Edit path=.claude/rules/observability.md protected=false VIBE_APPROVED=false decision=ALLOW
|
|
31
|
-
[2026-04-21T02:35:22.331Z] tool=Edit path=.claude/skills/save/SKILL.md protected=false VIBE_APPROVED=false decision=ALLOW
|
|
32
|
-
[2026-04-21T02:35:23.493Z] tool=Edit path=.claude/skills/save/SKILL.md protected=false VIBE_APPROVED=false decision=ALLOW
|
|
33
|
-
[2026-04-21T02:35:24.652Z] tool=Edit path=.claude/skills/save/SKILL.md protected=false VIBE_APPROVED=false decision=ALLOW
|
|
34
|
-
[2026-04-21T02:35:40.826Z] tool=Edit path=.claude/skills/submit-request/SKILL.md protected=false VIBE_APPROVED=false decision=ALLOW
|
|
35
|
-
[2026-04-21T02:35:46.042Z] tool=Edit path=../../apps/docs/content/docs/in-progress/active-development/elevasis-sdk-cli-audit/index.mdx protected=false VIBE_APPROVED=false decision=ALLOW
|
|
36
|
-
[2026-04-21T02:35:52.609Z] tool=Edit path=../../apps/docs/content/docs/in-progress/active-development/elevasis-sdk-cli-audit/index.mdx protected=false VIBE_APPROVED=false decision=ALLOW
|
|
37
|
-
[2026-04-21T02:38:11.027Z] tool=Edit path=../../apps/docs/content/docs/in-progress/active-development/elevasis-sdk-cli-audit/index.mdx protected=false VIBE_APPROVED=false decision=ALLOW
|
|
38
|
-
[2026-04-21T02:40:59.364Z] tool=Edit path=../../apps/docs/content/docs/in-progress/active-development/elevasis-sdk-cli-audit/index.mdx protected=false VIBE_APPROVED=false decision=ALLOW
|
|
39
|
-
[2026-04-21T02:46:47.829Z] tool=Edit path=../../apps/docs/content/docs/in-progress/active-development/elevasis-sdk-cli-audit/index.mdx protected=false VIBE_APPROVED=false decision=ALLOW
|
|
40
|
-
[2026-04-21T02:46:54.250Z] tool=Edit path=../../apps/docs/content/docs/in-progress/active-development/elevasis-sdk-cli-audit/index.mdx protected=false VIBE_APPROVED=false decision=ALLOW
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
[2026-04-21T02:33:54.962Z] SKIP — registry not found (pre-SDK-delivery)
|
|
2
|
-
[2026-04-21T02:34:53.682Z] SKIP — registry not found (pre-SDK-delivery)
|
|
3
|
-
[2026-04-21T02:35:00.720Z] SKIP — registry not found (pre-SDK-delivery)
|
|
4
|
-
[2026-04-21T02:35:01.648Z] SKIP — registry not found (pre-SDK-delivery)
|
|
5
|
-
[2026-04-21T02:35:12.366Z] SKIP — registry not found (pre-SDK-delivery)
|
|
6
|
-
[2026-04-21T02:35:15.057Z] SKIP — registry not found (pre-SDK-delivery)
|
|
7
|
-
[2026-04-21T02:35:16.874Z] SKIP — registry not found (pre-SDK-delivery)
|
|
8
|
-
[2026-04-21T02:35:22.469Z] SKIP — registry not found (pre-SDK-delivery)
|
|
9
|
-
[2026-04-21T02:35:23.677Z] SKIP — registry not found (pre-SDK-delivery)
|
|
10
|
-
[2026-04-21T02:35:24.831Z] SKIP — registry not found (pre-SDK-delivery)
|
|
11
|
-
[2026-04-21T02:35:41.027Z] SKIP — registry not found (pre-SDK-delivery)
|
|
12
|
-
[2026-04-21T02:35:46.206Z] SKIP — registry not found (pre-SDK-delivery)
|
|
13
|
-
[2026-04-21T02:35:52.776Z] SKIP — registry not found (pre-SDK-delivery)
|
|
14
|
-
[2026-04-21T02:38:11.201Z] SKIP — registry not found (pre-SDK-delivery)
|
|
15
|
-
[2026-04-21T02:40:59.696Z] SKIP — registry not found (pre-SDK-delivery)
|
|
16
|
-
[2026-04-21T02:46:47.986Z] SKIP — registry not found (pre-SDK-delivery)
|
|
17
|
-
[2026-04-21T02:46:54.532Z] SKIP — registry not found (pre-SDK-delivery)
|
|
18
|
-
[2026-04-21T07:44:32.528Z] SKIP — registry not found (pre-SDK-delivery)
|
|
19
|
-
[2026-04-21T07:52:53.483Z] SKIP — registry not found (pre-SDK-delivery)
|
|
20
|
-
[2026-04-21T07:54:29.656Z] SKIP — registry not found (pre-SDK-delivery)
|
|
21
|
-
[2026-04-21T07:54:33.615Z] SKIP — registry not found (pre-SDK-delivery)
|
|
22
|
-
[2026-04-21T07:57:28.696Z] SKIP — registry not found (pre-SDK-delivery)
|
|
23
|
-
[2026-04-21T08:00:48.300Z] SKIP — registry not found (pre-SDK-delivery)
|
|
24
|
-
[2026-04-21T08:00:49.082Z] SKIP — registry not found (pre-SDK-delivery)
|
|
25
|
-
[2026-04-21T08:00:50.494Z] SKIP — registry not found (pre-SDK-delivery)
|
|
26
|
-
[2026-04-21T08:00:56.735Z] SKIP — registry not found (pre-SDK-delivery)
|
|
27
|
-
[2026-04-21T08:00:58.798Z] SKIP — registry not found (pre-SDK-delivery)
|
|
28
|
-
[2026-04-21T08:01:07.662Z] SKIP — registry not found (pre-SDK-delivery)
|
|
29
|
-
[2026-04-21T08:01:19.062Z] SKIP — registry not found (pre-SDK-delivery)
|
|
30
|
-
[2026-04-21T08:01:24.353Z] SKIP — registry not found (pre-SDK-delivery)
|
|
31
|
-
[2026-04-21T08:01:42.662Z] SKIP — registry not found (pre-SDK-delivery)
|
|
32
|
-
[2026-04-21T08:12:30.121Z] SKIP — registry not found (pre-SDK-delivery)
|
|
33
|
-
[2026-04-21T08:14:55.475Z] SKIP — registry not found (pre-SDK-delivery)
|
|
34
|
-
[2026-04-21T08:17:01.426Z] SKIP — registry not found (pre-SDK-delivery)
|
|
35
|
-
[2026-04-21T08:36:02.820Z] SKIP — registry not found (pre-SDK-delivery)
|
|
36
|
-
[2026-04-21T22:16:29.986Z] SKIP — registry not found (pre-SDK-delivery)
|
|
37
|
-
[2026-04-21T22:16:55.680Z] SKIP — registry not found (pre-SDK-delivery)
|
|
38
|
-
[2026-04-21T22:17:54.009Z] SKIP — registry not found (pre-SDK-delivery)
|