@manifesto-ai/projection-ui
v0.3.0
Published
UI Projection layer for Manifesto AI - converts domain policies to UI states
Maintainers
Readme
@manifesto-ai/projection-ui
Framework-agnostic UI state projection for Manifesto AI
Transform domain policies into UI states automatically. The domain decides what (relevant, editable, required), and your UI decides how (visible, enabled, styled).
Installation
pnpm add @manifesto-ai/projection-ui @manifesto-ai/coreCore Concept
Domain policies are projected to UI states through a simple mapping:
| Domain Policy | UI State | Description |
|--------------|----------|-------------|
| relevance | visible | Should this field be shown? |
| editability | enabled | Can the user interact with it? |
| requirement | required | Must the user provide a value? |
import { projectFieldPolicy } from '@manifesto-ai/projection-ui';
const uiState = projectFieldPolicy(policy, runtime);
// {
// visible: true,
// enabled: false,
// required: true,
// validation: { valid: true, issues: [] },
// meta: { path: 'data.email', label: 'Email' }
// }Quick Start
Single Field Projection
import { projectFieldPolicy } from '@manifesto-ai/projection-ui';
import { fieldPolicy, condition } from '@manifesto-ai/core';
const emailPolicy = fieldPolicy({
relevance: true,
editability: condition({ $not: { $get: 'state.isSubmitting' } }),
requirement: true,
});
const state = projectFieldPolicy(emailPolicy, runtime, 'data.email');
if (state.visible) {
renderField({
disabled: !state.enabled,
required: state.required,
error: state.validation.issues[0]?.message,
});
}Multiple Fields with ProjectionManager
import { createProjectionManager } from '@manifesto-ai/projection-ui';
const manager = createProjectionManager({
runtime,
domain,
fields: {
paths: ['data.name', 'data.email', 'data.phone'],
},
actions: {
actionIds: ['submit', 'save', 'cancel'],
},
});
// Get current state
const emailState = manager.getFieldState('data.email');
const submitState = manager.getActionState('submit');
// Subscribe to changes
const unsubscribe = manager.subscribeFields((states, changedPaths) => {
changedPaths.forEach(path => {
updateFieldUI(path, states.get(path));
});
});
// Cleanup
manager.dispose();API Reference
Field Projection
projectFieldPolicy(policy, runtime, path?)
Projects a single field policy to UI state.
const state = projectFieldPolicy(policy, runtime, 'data.email');projectFieldPolicies(policies, runtime)
Projects multiple field policies at once.
const states = projectFieldPolicies({
'data.name': namePolicy,
'data.email': emailPolicy,
}, runtime);
// Map<string, UIFieldState>Filter Utilities
import {
filterVisibleFields,
filterEnabledFields,
getRequiredFields,
} from '@manifesto-ai/projection-ui';
const visible = filterVisibleFields(states); // Only visible fields
const enabled = filterEnabledFields(states); // Only enabled fields
const required = getRequiredFields(states); // Only required fieldsAction Projection
projectActionState(actionId, runtime)
Projects an action's availability state.
const state = projectActionState('submit', runtime);
// {
// actionId: 'submit',
// available: true,
// unavailableReason: null,
// executing: false,
// preconditions: [
// { conditionId: 'hasItems', satisfied: true },
// { conditionId: 'isValid', satisfied: true },
// ]
// }Action Utilities
import {
getAvailableActions,
getUnavailableActions,
setExecuting,
} from '@manifesto-ai/projection-ui';
const available = getAvailableActions(states); // ['save', 'cancel']
const unavailable = getUnavailableActions(states); // [{ actionId: 'submit', reason: '...' }]
const executing = setExecuting(state, true); // Mark as executingEvent Projection
Create UI events (toasts, notifications) from domain events:
import {
createSuccessToast,
createErrorToast,
projectEvent,
} from '@manifesto-ai/projection-ui';
// Quick toast creation
const toast = createSuccessToast('Saved', 'Your changes have been saved');
// Or project from domain events
const uiEvent = projectEvent(domainEvent, {
transformer: (event) => ({
title: 'Order Created',
message: `Order #${event.payload.id} confirmed`,
severity: 'success',
}),
});
manager.emitEvent(toast);ProjectionManager
Full-featured manager for coordinating field, action, and event projections:
interface ProjectionManager {
// Fields
getFieldState(path: string): UIFieldState | undefined;
getAllFieldStates(): Map<string, UIFieldState>;
subscribeFields(listener: FieldStateListener): () => void;
// Actions
getActionState(actionId: string): UIActionState | undefined;
getAllActionStates(): Map<string, UIActionState>;
subscribeActions(listener: ActionStateListener): () => void;
setActionExecuting(actionId: string, executing: boolean): void;
// Events
emitEvent(event: UIEvent): void;
getPendingEvents(): UIEvent[];
dismissEvent(eventId: string): void;
subscribeEvents(listener: UIEventListener): () => void;
// Lifecycle
dispose(): void;
}Framework Integration
This package provides framework-agnostic projection logic. For framework-specific bindings, create thin wrappers in your application or use bridge packages:
React Example
function useFieldState(manager: ProjectionManager, path: string) {
const [state, setState] = useState(() => manager.getFieldState(path));
useEffect(() => {
return manager.subscribeFields((states, changed) => {
if (changed.includes(path)) {
setState(states.get(path));
}
});
}, [manager, path]);
return state;
}Vue Example
function useFieldState(manager: ProjectionManager, path: string) {
const state = ref(manager.getFieldState(path));
onMounted(() => {
const unsubscribe = manager.subscribeFields((states, changed) => {
if (changed.includes(path)) {
state.value = states.get(path);
}
});
onUnmounted(unsubscribe);
});
return state;
}Types
interface UIFieldState {
visible: boolean;
enabled: boolean;
required: boolean;
validation: {
valid: boolean;
issues: ValidationIssue[];
};
meta: {
path: string;
label?: string;
};
}
interface UIActionState {
actionId: string;
available: boolean;
unavailableReason: string | null;
executing: boolean;
preconditions: UIPreconditionState[];
}
interface UIEvent {
id: string;
title: string;
message?: string;
severity: 'success' | 'error' | 'warning' | 'info';
channel?: string;
timestamp: number;
dismissed: boolean;
}License
MIT © Manifesto AI
