# Umbrella UI Injection Architecture Guide

This guide defines how to build UI pages in an umbrella app when screen content depends on multiple domain apps.

Goal:

- keep ownership clear
- avoid cross-app coupling and circular dependencies
- allow dynamic component injection from other apps
- make implementation predictable for AI agents and developers

## 1. Design principles

### 1.1 Dependency direction (non-negotiable)

- `wallet_web` is the UI host and composition layer
- Domain apps (`wallet_transfers`, `wallet_loans`, `wallet_cards`, etc.) own business logic and data access
- Domain apps do not call UI internals directly unless implementing an explicit UI extension contract

Preferred flow:

- UI host -> domain APIs/queries
- Optional extension app -> UI contract -> rendered by UI host

Avoid:

- hidden coupling by direct template imports across apps
- circular dependencies between umbrella children

### 1.2 Open/Closed principle for pages

Each page should be:

- closed for modifying core shell repeatedly
- open for adding new panels/sections/tabs through registration

Use a stable extension contract and a registry/discovery mechanism.

### 1.3 Ports and adapters mindset

Treat extension contracts as ports:

- Host page defines behaviour and rendering lifecycle expectations
- Feature app implements adapter component and registers it

This gives runtime composability with strict boundaries.

## 2. Reference pattern: Page Host + Slot Registry + Injected Components

Use this pattern for any page likely to grow across teams.

### 2.1 Host page responsibilities (in wallet_web)

- route ownership
- page shell and layout
- extension discovery and ordering
- rendering active/allowed components
- cross-cutting concerns: flash, authorization gate, telemetry envelope

### 2.2 Extension component responsibilities (in feature app or wallet_web)

- implement behavior contract
- fetch and present feature-specific data
- own component-local events (`phx-target={@myself}`)
- send only typed parent messages (`{:ok, msg}`, `{:error, msg}`, `:reload_*`)

### 2.3 Registry responsibilities

- maintain list of core + extension components
- validate contract compliance
- sort by stable order
- fail safely if extension invalid

## 3. Standard contract for dynamic page injection

For each extensible page, define:

1. Behaviour module
2. Registry module
3. Parent page coordinator
4. Message contract (child -> parent)
5. Update contract (parent -> child)

Minimum behavior fields:

- unique `id` atom
- `label` for navigation
- `icon` (if nav uses icons)
- `order` for deterministic rendering

## 4. Implementation policy for AI agents and developers

When implementing or changing a page that spans multiple apps, follow this policy in order.

### 4.1 Decide if page must be extensible

Use injection if at least one is true:

- multiple domain teams will add sections over time
- release cadence differs per feature app
- feature can be enabled/disabled by environment or tenant

If false, keep static composition in `wallet_web`.

### 4.2 Define extension boundary first

Before writing component code:

- define behavior callbacks and message shapes
- define registry merge strategy (core + config + runtime)
- define fallback behavior when extension fails to load

### 4.3 Keep page shell stable

Do not duplicate host-page logic across apps.

- routing and shell remain in `wallet_web`
- extension components only render inside host-defined slots

### 4.4 Make update lifecycle robust

For LiveComponent extensions with custom `update/2`:

- always merge incoming assigns first:
  - `socket = assign(socket, assigns)`
- avoid unnecessary reloads; reload on entity change only
- keep local state reset on context switches

### 4.5 Keep event ownership local

- all extension events use `phx-target={@myself}`
- parent handles only global page events

### 4.6 Keep communication typed and minimal

Use only approved messages to parent:

- `{__MODULE__, {:ok, message}}`
- `{__MODULE__, {:error, message}}`
- `{__MODULE__, :reload_entity}`

### 4.7 Enforce authorization at both levels

- host page gates overall access
- extension component validates capability for sensitive actions

### 4.8 Make observability mandatory

At minimum:

- telemetry event for extension loaded
- telemetry event for extension errors
- optional metrics for render latency and event failures

## 5. Ownership matrix (what code goes where)

Put in `wallet_web`:

- route definitions
- page coordinator LiveView
- extension behaviour and registry
- shell layout, nav, shared UI components
- cross-cutting flash/error surface

Put in feature app (`wallet_transfers`, `wallet_loans`, etc.):

- domain queries and services
- optional extension component implementing host behaviour
- feature-specific validations and action logic

Do not put in feature app:

- host route orchestration
- direct assumptions about host internals beyond behaviour contract

## 6. Registration strategy

Support both:

- config-based registration for stable extensions
- runtime registration from app start for dynamic/conditional modules

Recommendation:

- use config-based for deterministic production startup
- use runtime only when feature flags or conditional loading are needed

## 7. Failure and resilience strategy

Design for partial failure:

- invalid extension module is skipped, not fatal
- host page still renders core components
- errors are logged with module name and reason

Prefer graceful degradation over full-page failure.

## 8. Testing strategy (required)

For each extensible page:

- unit tests for registry ordering and invalid extension handling
- integration tests for host rendering injected component
- event tests ensuring extension events do not leak to parent unexpectedly
- URL state tests for active section/tab
- smoke tests for missing registry process in test mode

For extension components:

- update lifecycle test (`update/2` keeps required assigns)
- event handling test with `phx-target={@myself}`
- parent message emission test

## 9. Decision checklist before merge

- clear owner for host and for extension module
- no new circular umbrella dependency
- behavior and registry documented
- dynamic injection path tested
- fallback behavior proven when extension unavailable
- observability hooks added

## 10. Anti-patterns to avoid

- adding domain-specific UI directly into host page repeatedly
- cross-app template coupling without a contract
- custom `update/2` that drops incoming assigns
- parent LiveView handling extension-local events
- untyped ad-hoc process messages

## 11. Apply this guide in current codebase

Use `UserDetailLive` as the working reference implementation and replicate the same pattern for other multi-domain admin pages.

See also:

- `docs/user-detail-tab-developer-guide.md`
- `docs/adr/0015-ui-composition-and-extension-architecture.md`
