Config-Driven UI
Use config-driven UI when an app needs tenant-specific layout, copy, feature flags, store state, or service wiring without forking page code per tenant.
Source Files
| File | Purpose |
|---|---|
src/eai.config/default.ts | Default tenant config, store slices, API paths, storage keys, and layout slots. |
src/eai.config/index.ts | Maps tenant keys to config objects. |
src/eai.blocks.tsx | Registers app-local or package components that config can reference by name. |
src/app/providers.tsx | Provides auth/session context and the EAI config runtime. |
src/hooks/useResources.ts | ResourceAPI-backed business data access. |
src/hooks/useDocuments.ts | Document upload, classification, and RAG indexing access. |
src/hooks/useChat.ts | Streaming and non-streaming chat access. |
Construction Pattern
- Put tenant-specific data in config: branding, feature flags, API endpoints, storage keys, initial store state, and layout slots.
- Register renderable components in
src/eai.blocks.tsx. - Reference components from config by their registered string name.
- Bind store paths into component props with
storeBindings. - Hide or show components with JSON-safe
showWhenconditions. - Keep callbacks, React nodes, and live browser behavior in code-level overrides, not config.
- Keep browser service calls behind template hooks and BFF routes.
Component Config Shape
{
component: 'TaskSummary',
priority: 10,
props: {
title: 'Open tasks',
},
storeBindings: [
{ prop: 'tasks', storePath: 'tasks.items' },
{ prop: 'isLoading', storePath: 'tasks.isLoading' },
],
showWhen: { path: 'user.isAuthenticated', equals: true },
}
Rules:
componentmust match a registered component name.- Lower
priorityrenders first. propsare static, tenant-configurable values.storeBindingsmap store paths to component prop paths. Dot notation is supported on both sides.showWhensupportsequals,notEquals,exists, and compoundandoror.
Slot Pattern
Use the template slot shape:
layout: {
header: {
className: 'bg-white border-b',
components: [{ component: 'AppHeader', priority: 1 }],
},
middlePane: {
className: 'flex-1 p-6',
components: [
{ component: 'TaskSummary', priority: 1 },
{ component: 'TaskList', priority: 2 },
],
},
rightPane: {
className: 'hidden w-80 border-l p-4 lg:block',
components: [{ component: 'AssistantPanel', priority: 1 }],
},
}
Do not generate stale array-only slots such as
middlePane: [{ component: 'Dashboard' }].
Store Slice Pattern
store: {
tasks: {
initialState: {
items: [],
selectedId: null,
isLoading: false,
},
persist: true,
},
ui: {
initialState: { showWelcome: true },
persist: false,
},
}
Persist only tenant-safe UI or workflow state. Never place access tokens, platform credentials, database connection details, blob credentials, search credentials, or model provider credentials in config or store state.
Runtime Overrides
Config is data-only. Use wrapper components for runtime behavior:
const componentOverrides = {
TaskList: {
onSelectTask: (taskId: string) => setSelectedTaskId(taskId),
},
AssistantPanel: {
onSendMessage: sendMessage,
},
};
Use overrides for event handlers, auth actions, router callbacks, analytics callbacks, render props, React nodes, and any other non-serializable value.