Skip to content

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:

  1. Read current state.
  2. Perform action.
  3. Confirm state change (explicitly and visibly).

5. Required App Elements

Every app module should include:

  1. BFF contracts:
  2. Query for current state.
  3. Mutation for state-changing action.
  4. Query or payload confirmation for post-action state.

  5. Access control policy:

  6. Capability/role requirements.
  7. Route guard rules.

  8. Information rendering:

  9. Tables, lists, cards, charts, forms.
  10. Standard empty/error/loading states.

  11. Identity metadata:

  12. App icon.
  13. App display name.

  14. Discovery metadata:

  15. 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:

  1. e1 Data orientation
  2. Purpose: Identify primary data/system source.
  3. Initial literals:
  4. data:odoo
  5. data:teams
  6. data:abs
  7. data:iot

  8. e2 User orientation

  9. Purpose: Identify main user persona.
  10. Initial literals:
  11. user:end-user
  12. user:sales-rep
  13. user:service-attendant
  14. user:sales-manager
  15. user:operator
  16. user:admin

  17. e3 Workflow orientation

  18. Purpose: Identify business process focus.
  19. Initial literals:
  20. workflow:customer-acquisition
  21. workflow:credit-review
  22. workflow:quotation
  23. workflow:bundle-sales
  24. workflow: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

  1. User logs in through Keycloak SSO.
  2. Portal resolves:
  3. user profile defaults
  4. entitlement-based app availability
  5. saved My Apps preferences (filters, priority, pinned apps)
  6. User lands on My Apps with 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.