ADR 0032 — Six-position shell slots + split/tabs page templates
This ADR closes the remaining app-shell parity gap flagged by ADR 0031 ("the
six-position SlotShell itself is still not adopted") and adopts EE's page
templates (split_page, tabs_page) as a DSL choice. Where Mosaic's existing
surfaces already do more, they are kept and extended (the switchable shell
layout, projection charts on the split page) instead of replaced.
1. The shell is a six-position slot system
Context
EE's SlotShell (blocks/ui/ee-theme) is a frame with named injectable
positions — header_left, header_right, sidebar_top, header_tools,
tenant_selector, sidebar_scenes, footer, events_panel, help_panel —
and the app frame composes them. Mosaic's shell had a fixed content model
(brand, nav, actions, footer) with no declarative way to inject content into
positions.
Decision
The generated web shell is now a six-position slot system, with the switchable
layout (topbar/sidebar/bottom/floating, ADR 0022-era data-layout) kept on
top of it — the layout moves the nav between positions, the slots fill them:
header-left— the brand (declaredapp.ui.brandor the app name).header-center— afilter navigationsearch box (client-side JS filters the scene links and hides empty scene groups) plus any declaredapp.ui.slotsentries for this position.header-right— the nav actions (SSO sign-in/out, the P31 realm bar, theme toggle, layout toggle).menu-top/menu-bottom— declaredapp.ui.slotslinks placed at the top/bottom of the nav.footer— the P31-era footer (copyright + links).events/timeline slot — when the app has aggregates, a live ticker over the CQRS event log (SSR paints from the store;spawn_clientrefreshes viaGET /api/events/list) with atimelinelink into the/cqrsreplay scrubber — the full timeline-toolbar parity (EE's sticky scrubber island is that page itself).
New DSL: app.ui.slots —
app:
ui:
slots:
- position: header-center # header-center | menu-top | menu-bottom
label: knowledge
path: /knowledge # internal path or external URL (new tab)
Positions are validated at plan time (bad-app-ui-slot). EE's
tenant_selector maps to the P31 realm bar, sidebar_scenes to the scene
nav groups, and help_panel to /docs + the nav search — no separate
positions are emitted for them (their content already lives in a slot).
2. split and tabs page templates
Context
EE ships page templates in blocks/ui/ee-ui/src/components/pages/:
list_page (toolbar + list), detail_page (back nav + optional sidebar),
dashboard_page (KPI grid), split_page (master-detail: 360px master column
- detail pane),
tabs_page(header + tab panels). Mosaic already generates the list/detail/dashboard shapes fromui:hints; the split and tabs shapes were missing.
Decision
ui.template on a projection selects the list page shape:
projections:
- name: Tickets
fields: [ … ]
on: [ … ]
ui:
template: split # split | tabs — absent → the classic list page
split— master-detail: the rows card (filters + sortable headers + reactive table, unchanged) becomes the left master column (380pxgrid track,1frdetail track; stacks below768px); each row is clickable (row-selectable) and selecting one renders the row inline in the right pane as a label/value card (title = the row key), so the detail view exists even withoutui.detail_page. Charts stay above the split — EE'ssplit_pagehas no charts; Mosaic keeps them.tabs—rows/chartsas tab panels (a tab bar only when charts exist; each panel is aShowover atabsignal). This is EE'stabs_pageshape with Mosaic's reactive table and SVG charts as panels.
template is validated at plan time (split | tabs); both compile in every
hydration mode (verified for wasm32-unknown-unknown and native SSR).
Incidental fix
A projection page whose list is off (ui.list_page.enabled: false) with no
charts and no admin commands rendered an empty <Layout> — a
required-children component, so the generated app failed to compile. Such
pages are now suppressed, and declared charts render even when the list is
off (charts are a list-page hint, not the list). No example hit this before
P32 (no example declared list_page.enabled: false).
Consequences
- The app-shell feature-map row (
codegen.webinsync/ee.md) moves from "partial (slot shell not adopted)" to landed-plus-more: the six-position slot system with the events/timeline slot, tenant scoping (P31), and the four-position switchable layout on top. split/tabsare opt-in per projection; the classic list page stays the default, so existing apps render unchanged.help_panelremains a/docs+ search combination rather than a dedicated shell position — revisit if an app needs in-shell help content.- The EE
transport.rsClientTransportseam (InProcess/Browser/LocalStore/ Replay) stays mapped to Mosaic's cfg-awareapi/api_put/api_postfetch helpers (P30) — a singlesend()entry point is not worth a rename while every call site is generated code.