==> read the decisions
ADR-0004: Project UIs are custom elements loaded into one app
Status: Accepted. Amended by ADR-0017 (2026-10-14): the shadow root carries the shared Tailwind theme and components. Amended by ADR-0023 (2026-10-15): the UI runs in a sandboxed frame, not in the app's page.
Context
Section titled “Context”The shell must be consistent across every project, and a project must be shippable without a platform deploy.
Decision
Section titled “Decision”- One Next app is the shell.
/lists that user's projects, rendered by the platform from the registry. /p/<slug>resolves the project for (session user, slug) and loads its module, which definescs-project-<slug>. The app renders that element.- Single origin, no iframe.
- Theming crosses the boundary by CSS custom properties; auth by the same-origin session.
Consequences
Section titled “Consequences”- React is not shared across the boundary at all, so there is no version skew and no runtime router contract.
- Shipping a project is: build, publish an immutable artifact, update the registry row. No platform deploy, no platform code change.
/p/<slug>authorizes on OWNERSHIP, not on being logged in, and answers 404 rather than 403 so it does not confirm other users' slugs.- A project shares its own user's page, so it is NOT isolated from that user's shell. Per-user artifact paths are what keep one user's module out of another's page.
- If a project ever has to be untrusted relative to its own user, an iframe becomes necessary; that is the only case that needs one.
Alternatives rejected
Section titled “Alternatives rejected”- Building the UIs into the app: simplest, but a project change becomes a platform deploy, which defeats the independence.
- A per-user origin: the wildcard is cheap, but it does not separate the module from the session, so it buys nothing that per-user artifact paths do not already give.