# ADR 0015: UI Composition and Extension Architecture

- Status: Accepted
- Date: 2026-04-03
- Owners: Platform Team, Wallet Web Team, Domain Teams
- Related:
  - `docs/adr/0001-app-boundaries.md`
  - `docs/adr/0010-release-strategy-and-migration-rollback.md`
  - `docs/adr/0012-testing-strategy-and-quality-gates.md`
  - `docs/adr/0013-environment-and-infrastructure-topology.md`
  - `docs/umbrella-ui-injection-architecture-guide.md`
  - `docs/user-detail-tab-developer-guide.md`

## Context
The platform is in an early UI expansion stage where multiple domain apps contribute data and capabilities to shared pages in `wallet_web`.

Without a standard composition model, teams risk:
- page-level coupling across umbrella apps
- circular dependencies
- unstable ownership boundaries
- difficult migration when selected domains are later deployed to separate nodes/servers

A single policy is required for Phoenix UI composition, Elixir app boundaries, and umbrella-to-distributed evolution.

## Decision
Adopt a host-composition architecture for UI pages with explicit extension contracts.

Core decisions:
1. `wallet_web` is the UI host and composition root for all web pages.
2. Dynamic page sections/tabs/components are integrated via behaviour + registry contracts.
3. Domain apps own business logic and data access, not Phoenix page shell orchestration.
4. Extension components must follow typed message and update lifecycle contracts.
5. Architecture must preserve optional migration from single-release umbrella to multi-node/service deployment.

## Scope
This ADR applies to:
- admin and customer LiveView pages
- pages that aggregate capabilities from multiple domain apps
- all new extensible UI work and significant refactors of existing pages

## Architectural Model

### 1. UI Host (wallet_web)
Responsibilities:
- route ownership and navigation
- page shell and layout
- extension discovery and ordering
- cross-cutting concerns (authz, flash/error surface, telemetry envelope)

### 2. Extension Contract (behaviour)
Each extensible page defines a behaviour that includes:
- static metadata (`id`, `label`, `icon`, `order`)
- lifecycle expectations (`update/2` contract)
- supported parent-child message shapes

### 3. Registry
Each extensible page has a registry responsible for:
- loading core components
- adding extension components from config/runtime registration
- validating contract compliance
- deterministic ordering
- graceful degradation when invalid modules are provided

### 4. Injected Components
Injected components:
- render inside host-defined slots
- own component-local events via `phx-target={@myself}`
- send typed messages to host only for cross-cutting actions

## Dependency and Ownership Rules
1. Dependency direction must remain acyclic.
2. `wallet_web` may depend on domain apps.
3. Domain apps must not require host internals for business logic.
4. UI rendering concerns remain in `wallet_web` unless a dedicated extension boundary explicitly allows otherwise.
5. Cross-app integration should rely on contracts/ports, not direct template coupling.

## Phoenix/LiveView Rules
1. URL-driven state for selectable sections/tabs (`?tab=<id>` style).
2. `update/2` in custom LiveComponents must merge incoming assigns first.
3. Parent handles only global events; child handles feature-local events.
4. Child-to-parent communication is typed and minimal:
- `{Module, {:ok, message}}`
- `{Module, {:error, message}}`
- `{Module, :reload_entity}`
5. Modal IDs and DOM hooks must be component-safe and namespaced where needed.

## Elixir/OTP Rules
1. Domain behavior remains in domain apps (commands, queries, stores, events).
2. UI layer calls domain capabilities via stable APIs.
3. Supervision boundaries remain per app; registries are supervised in host app.
4. Telemetry and audit events emitted at integration boundaries.

## Deployment Evolution Rule (Umbrella -> Multi-Node)
To support future deployment of selected apps to separate nodes/servers:
1. Treat current umbrella boundaries as future service boundaries.
2. Keep host page composition local to UI runtime.
3. Replace direct in-process domain calls with adapters/ports when a domain moves remote.
4. Do not design runtime HTML/template injection from remote nodes.
5. Preserve contract compatibility so host page remains stable while transport changes.

## Registration Strategy
Preferred:
- Config registration for deterministic startup and predictable production behavior.

Allowed:
- Runtime registration for feature-flagged or environment-conditional modules.

Requirement:
- Invalid registration must not crash host page rendering.

## Testing and Quality Gates
For every extensible page:
1. Registry tests for ordering and invalid module handling.
2. LiveView integration tests for host + injected component rendering.
3. Event routing tests to ensure child events do not leak to host unexpectedly.
4. URL state tests for section/tab switching and deep linking.
5. Fallback tests when extensions are unavailable.

For each injected component:
1. `update/2` lifecycle test validating required assigns are preserved.
2. child event handling tests (`phx-target={@myself}`).
3. parent message emission tests for success/error/reload flows.

## Anti-Patterns (Prohibited)
1. Repeatedly editing host page shell for each new domain feature without extension seam.
2. Circular umbrella dependencies introduced for UI integration.
3. Custom `update/2` that does not merge incoming assigns.
4. Unstructured ad-hoc process messages between child and parent.
5. Cross-app direct imports of host templates/components without contract.

## Consequences
Positive:
- Clear ownership and maintainability for multi-team UI work.
- Consistent extension model for AI agents and developers.
- Lower risk when moving domains to separate nodes/services.
- Reduced regression risk through standardized lifecycle and tests.

Trade-offs:
- Additional upfront design effort for contracts and registries.
- Slightly more boilerplate for small pages.

## Acceptance Criteria
1. New multi-domain pages use host + contract + registry model.
2. No new circular dependency is introduced for UI extensibility.
3. Extension components follow typed messaging and lifecycle rules.
4. Required tests are present for host and extension behavior.
5. Deployment evolution path (local -> remote domain adapter) remains feasible without host page rewrite.
