---
name: shell-embed
load-when: authoring an embedded adia-ui surface — a `<embed-shell>` framing a primary app plus secondary panels a host page sizes/centers
load-size: ~1.5k tokens
required-for: [shell-selection — embed path]
---

# embed-shell — embedded multi-surface frame

Embedded chrome from `@adia-ai/web-modules` (shell cluster). Register: `import '@adia-ai/web-modules/shell'`. Peer of admin/editor/chat shells; light-DOM, content-agnostic — it orchestrates layout of whatever `[app]` + `[panel]` children it's given, never the content or data. (Claims re-verified against the kit's live source 2026-08-26; re-verify on a MINOR cut.)

## Cluster roster

`<embed-shell>` (host coordinator) — the only tag. Its children are consumer-supplied: one `[app]` primary surface + any number of `[panel="<name>"]` secondary surfaces. Extends `UIElement`, so `traits="resizable"` applies the real `resizable` trait (drag any edge + a `resize-end` event), not a CSS stand-in.

## Canonical skeleton

```html
<embed-shell>
  <patient-labs app></patient-labs>                 <!-- the primary surface (exactly one) -->
  <settings-panel panel="settings"></settings-panel><!-- a secondary surface (any number) -->
  <chat-panel panel="chat"></chat-panel>
  <!-- triggers anywhere inside, delegated: -->
  <button-ui opens="chat">Chat</button-ui>          <!-- toggles the named panel -->
  <button-ui close></button-ui>                      <!-- dismisses the open panel -->
</embed-shell>
```

## Contract · state · events

- **Children:** `[app]` = primary (one); `[panel="<name>"]` = secondaries. `[opens="<name>"]` toggles a panel; `[close]` dismisses the open one — both delegated off a click on any descendant.
- **State (reflected, ADR-0023):** `embed-shell[panel="chat"]` = which panel is open (`''`/absent = none); the open panel carries `[active]` (the CSS show/slide hook).
- **Events:** `embed:open` (in) `detail:{panel}` = request to open/toggle; `embed:change` (out) `detail:{panel}` = emitted after the open panel changes.
- **Methods:** `.open(name)` · `.close()` · `.toggle(name)`; getter `.panel` (open name or `''`). Escape closes the open panel.
- **Layout (embed-shell.css):** ≥760px the open panel is a 50/50 column beside the app; <760px it's a cover sheet (`translateY` — **identity at rest**, so popovers inside still anchor via CSS anchor-positioning; an offsetting `translate(-50%,-50%)` would break them).

## Authoring the surfaces inside it

The shell owns the frame; each `[app]`/`[panel]` surface is a self-booting light-DOM container you author to the embedded-surface pattern:

- **Self-booting** — `connected()` guarded by a `#booted` flag (it re-fires on DOM moves), fetches its data, renders its own subtree. No shadow DOM.
- **Size-agnostic** — the surface never hardcodes width/height; the shell (and the host page) own extent.
- **Data via projection** — `DataClient.read({ type, params })` returns typed projections from pure mappers (`app/shared/corpus/mappers/`); the surface never calls a backend directly. **Every `mutate` carries `action_source`** — the client throws without it. Depth: [`data-and-hydration.md`](data-and-hydration.md).
- **In-DOM tabs** — a content-less `<router-ui>` whose URL you manage with `history.replaceState()` to preserve the host's query params; do **not** set `router.routes` (that fetches + `innerHTML`-replaces).
- **Shared foundation** — multiple embedded surfaces live under a `shared-foundation` project shape (`app/shared/` for DataClient/mappers/tokens); see [`project-shapes.md`](project-shapes.md).

## SPA vs SSR

The shell mounts the full markup in SPA. For SSR, register it client-side like any component and keep any data keys server-side; it's a self-contained SPA island, not an SSR page (see the hybrid note in [`data-and-hydration.md`](data-and-hydration.md)).

## Gotchas

- **Piecemeal import** → `EmbedShell` unregistered; `.open()`/`.toggle()` undefined and panels collapse. Import the **shell barrel**.
- **A trigger `opens="x"` with no matching `[panel="x"]` child** → `.open()` no-ops silently.
- **Wrapping `[app]`/`[panel]` children in a layout `<div>`** → breaks the `:scope > [panel]` direct-child selectors the shell and CSS use.

Real usage: `apps/embedded-app/` — patient-labs and population-health surfaces built to the shared-foundation shape (`app/patient-labs/`, `app/population-health/`, shared `app/shared/`). Read it before authoring a new embedded surface.
