CRUD - ovApp Portal Architecture
This document extends existing UXI documentation for CRUD-style applications and ovApp behavior.
It is an evolution of:
- docs/vision/architecture-vision.md
- docs/stack/stack.md
- docs/style-system/overview.md
It does not replace those principles. It makes them implementation-ready for portal/super-app delivery.
1. ovApp Core Model¶
At high level:
app portal = { user profile, app collection }
User question:
"Here I am. What apps can I use?"
Portal answer:
My Apps based on identity, access policy, and profile preferences.
2. User Profile Model¶
user profile = { name, login credentials/SSO, settings/preferences, customization }
Minimum profile shape:
export interface UserProfile {
userId: string;
displayName: string;
authProvider: 'keycloak' | 'sso';
roles: string[];
preferences: {
appFilters?: string[];
appPriority?: string[];
compactView?: boolean;
};
customization?: {
pinnedApps?: string[];
hiddenApps?: string[];
};
}
3. App Collection Model¶
The app collection is an assortment of apps/utilities that supports: - Search by name/tags. - Filter by dimensions/tags. - Sorting/prioritization by user preference. - Optional admin-driven curation/ordering.
Both user and backend admin can influence what appears on My Apps.
4. Canonical App Definition¶
An app in ovApp must: - Access data in a current state. - Allow one or more actions. - Confirm and render resulting state change.
Canonical structure:
App = { data current state, action, data state-change confirmation }
This is the required user-flow pattern for CRUD apps:
- Read current state.
- Perform action.
- Confirm state change (explicitly and visibly).
5. Required App Elements¶
Every app module should include:
- BFF contracts:
- Query for current state.
- Mutation for state-changing action.
-
Query or payload confirmation for post-action state.
-
Access control policy:
- Capability/role requirements.
-
Route guard rules.
-
Information rendering:
- Tables, lists, cards, charts, forms.
-
Standard empty/error/loading states.
-
Identity metadata:
- App icon.
-
App display name.
-
Discovery metadata:
- Tags for search and grouping.
6. Dimension-Tag Framework (Controlled Vocabulary)¶
To support scale and avoid tagging drift, apps use a controlled dimension-tag system.
Each app is positioned in a tag vector space:
appTagVector = { e1, e2, e3 }
Where:
e1Data orientation- Purpose: Identify primary data/system source.
- Initial literals:
data:odoodata:teamsdata:abs-
data:iot -
e2User orientation - Purpose: Identify main user persona.
- Initial literals:
user:end-useruser:sales-repuser:service-attendantuser:sales-manageruser:operator-
user:admin -
e3Workflow orientation - Purpose: Identify business process focus.
- Initial literals:
workflow:customer-acquisitionworkflow:credit-reviewworkflow:quotationworkflow:bundle-salesworkflow:service
Rules: - Only approved literal strings are allowed. - New literals require governance review. - One app can map to multiple values per dimension if needed.
Example:
export interface AppTags {
e1: Array<'data:odoo' | 'data:teams' | 'data:abs' | 'data:iot'>;
e2: Array<
| 'user:end-user'
| 'user:sales-rep'
| 'user:service-attendant'
| 'user:sales-manager'
| 'user:operator'
| 'user:admin'
>;
e3: Array<
| 'workflow:customer-acquisition'
| 'workflow:credit-review'
| 'workflow:quotation'
| 'workflow:bundle-sales'
| 'workflow:service'
>;
}
7. Access Model and Module Contract¶
Core objects:
- Capability: fine-grained permission (example fleet.vehicle.read).
- Entitlement: granted capabilities resolved from Keycloak roles/groups/claims.
- OvAppModule: app metadata + required capabilities + tag vectors.
export type CapabilityKey = string;
export interface OvAppModule {
key: string;
name: string;
icon?: string;
routeBase: string;
requiredCapabilities: CapabilityKey[];
tags: AppTags;
}
export interface UserEntitlements {
userId: string;
roles: string[];
capabilities: CapabilityKey[];
}
Enablement rule:
module is enabled iff all requiredCapabilities exist in UserEntitlements.capabilities.
8. Portal UX Flow¶
After login, user lands on My Apps:
- App icon grid/list.
- Search and tag-based filtering.
- Ordering by profile priority.
- Optional user customization (pin/hide/reorder).
When user opens an app: - App-specific menu and workflow appear. - Interaction follows state-act-change-confirm pattern.
Portal-level settings: - Preferences/profile management. - App selection filters (via tag dimensions).
9. Platform Wrapper Contract (Parity Across Web/Mobile/Desktop)¶
To keep behavior consistent across Android/iOS/Desktop/Web, use a shared JS interface with platform-specific adapters.
export interface PlatformServices {
camera: { captureImage(): Promise<string> };
ble: { scan(timeoutMs: number): Promise<Array<{ id: string; name?: string }>> };
files: { pickFile(): Promise<{ name: string; mimeType: string; uri: string }> };
notifications: { sendLocal(title: string, body: string): Promise<void> };
}
Rules:
- Apps call only PlatformServices, never native APIs directly.
- Wrapper implementations vary by runtime (WebView/WebView2/browser), but UX behavior remains aligned.
- Unsupported capabilities must return standardized responses and fallback UI.
10. Delivery Strategy (Evolution, Not Reset)¶
Phase 1 (current)¶
- Static module registry in repo.
- Controlled tag literals (
e1/e2/e3) in code/config. - Entitlement-aware nav + route guards.
- My Apps landing and preference filters.
Phase 2¶
- Module registry served via ovAPI/BFF.
- Admin-curated collections by role/group/tenant.
- Saved user personalization in backend profile store.
Phase 3¶
- Remote signed module manifests.
- Fine-grained policy evaluation and rollout controls.
- Usage analytics to optimize app discoverability.
11. Relationship to ISR and CRUD Streams¶
- ISR apps remain content delivery surfaces.
- CRUD apps are transactional/state-change surfaces under ovApp shell patterns.
- Both continue to share UXI design tokens, identity model, and governance.
The difference is runtime behavior and interaction depth, not a separate UX language.
12. Example: Sales Rep App Set¶
Example user profile:
const salesRepProfile: UserProfile = {
userId: 'u-10021',
displayName: 'Amina K.',
authProvider: 'keycloak',
roles: ['sales-rep'],
preferences: {
appFilters: ['user:sales-rep'],
appPriority: ['customers', 'deals', 'sales', 'emob-bundle-sale', 'ebat-swap-service'],
},
customization: {
pinnedApps: ['customers', 'deals', 'sales'],
},
};
Example module entries available to this user:
const salesRepModules: OvAppModule[] = [
{
key: 'customers',
name: 'Customers',
routeBase: '/app/customers',
requiredCapabilities: ['crm.customer.read'],
tags: {
e1: ['data:odoo'],
e2: ['user:sales-rep'],
e3: ['workflow:customer-acquisition'],
},
},
{
key: 'deals',
name: 'Deals',
routeBase: '/app/deals',
requiredCapabilities: ['crm.deal.read', 'crm.deal.update'],
tags: {
e1: ['data:odoo'],
e2: ['user:sales-rep'],
e3: ['workflow:quotation'],
},
},
{
key: 'sales',
name: 'Sales',
routeBase: '/app/sales',
requiredCapabilities: ['sales.order.read', 'sales.order.create'],
tags: {
e1: ['data:odoo'],
e2: ['user:sales-rep'],
e3: ['workflow:bundle-sales'],
},
},
{
key: 'assets',
name: 'Assets',
routeBase: '/app/assets',
requiredCapabilities: ['asset.read'],
tags: {
e1: ['data:abs'],
e2: ['user:sales-rep'],
e3: ['workflow:service'],
},
},
{
key: 'contracts',
name: 'Contracts',
routeBase: '/app/contracts',
requiredCapabilities: ['contract.read', 'contract.update'],
tags: {
e1: ['data:abs', 'data:odoo'],
e2: ['user:sales-rep'],
e3: ['workflow:bundle-sales'],
},
},
{
key: 'emob-bundle-sale',
name: 'eMob Bundle Sale',
routeBase: '/app/emob-bundle-sale',
requiredCapabilities: ['bundle.emob.quote', 'bundle.emob.create'],
tags: {
e1: ['data:odoo', 'data:abs'],
e2: ['user:sales-rep'],
e3: ['workflow:bundle-sales'],
},
},
{
key: 'ebat-swap-service',
name: 'eBat Swap Service',
routeBase: '/app/ebat-swap-service',
requiredCapabilities: ['swap.order.read', 'swap.order.create'],
tags: {
e1: ['data:abs', 'data:iot'],
e2: ['user:sales-rep', 'user:service-attendant'],
e3: ['workflow:service'],
},
},
{
key: 'my-shop',
name: 'My Shop',
routeBase: '/app/settings/my-shop',
requiredCapabilities: ['shop.profile.read', 'shop.profile.update'],
tags: {
e1: ['data:odoo'],
e2: ['user:sales-rep'],
e3: ['workflow:service'],
},
},
{
key: 'my-team',
name: 'My Team',
routeBase: '/app/settings/my-team',
requiredCapabilities: ['team.read'],
tags: {
e1: ['data:teams'],
e2: ['user:sales-rep', 'user:sales-manager'],
e3: ['workflow:service'],
},
},
{
key: 'my-apps',
name: 'My Apps',
routeBase: '/app/settings/my-apps',
requiredCapabilities: ['portal.preferences.read', 'portal.preferences.update'],
tags: {
e1: ['data:odoo'],
e2: ['user:sales-rep'],
e3: ['workflow:service'],
},
},
];
Expected My Apps landing for this profile:
- Basic apps: Customers, Deals, Sales, Assets, Contracts
- Workflow apps: eMob Bundle Sale, eBat Swap Service
- Settings apps: My Shop, My Team, My Apps
13. Developer-Oriented Portal UX Vision (Typical Flow)¶
13.1 Login and Initial Portal State¶
- User logs in through Keycloak SSO.
- Portal resolves:
- user profile defaults
- entitlement-based app availability
- saved
My Appspreferences (filters, priority, pinned apps) - User lands on
My Appswith an initial app icon group.
Initial icon group for sales rep persona typically includes:
- Customers
- Deals
- Sales
- Assets
- Contracts
- plus role-eligible workflow and settings apps
13.2 Customers App: List and State View¶
Clicking Customers opens a paginated customer listing with:
- aggregate cards:
- Total Customers
- New Customers with time selector (last 7/30/90 days, etc.)
- sort controls (name, created date, status, owner)
- search/filter controls (optional by phase)
13.3 Row-Level Actions (State -> Action -> Confirmation)¶
Each customer row exposes actions such as:
- Update Details:
- opens customer form
- workflow uses Save / Cancel
- on Save, show explicit success confirmation and updated row state
- Follow Up:
- opens action options Call / Email / SMS
- selected action creates follow-up record and shows confirmation
Page-level action:
- Add New button opens create-customer form with standard Save / Cancel.
13.4 Admin Extension: Allocation Workflow¶
For sales manager role, Customers also enables:
- Allocate action to assign selected customers to a specific sales rep.
Expected post-action behavior:
- allocation mutation succeeds
- audit entry is recorded
- assigned rep receives notification, for example:
- "25 customers allocated to your lead pool."
This preserves the same canonical interaction model:
current state -> action -> state-change confirmation.