Mosaic one model, many lenses

ADR 0031 — Tenant-scoped Leptos web shell + CSR/vectordb compile fixes

This ADR closes the "tenant scoping not adopted" gap in the web lens and fixes two pre-existing compile bugs in the CSR + vectordb path.

1. Tenant-scoped Leptos web shell

Context

EE's app shell (SlotShell) routes and scopes the UI by tenant. Mosaic already had the backend machinery (realms, x-realm-{dim} headers, token-borne realms, and a realm/tenant bar on the static admin HTML pages) but the Leptos pages (dashboard, list/detail, projections, events, CQRS, schedules, flags, notifications, knowledge, search) did not send the realm header. So switching tenant on an admin page scoped that page, but the Leptos pages kept showing the default (seeded) tenant — an inconsistent, partially-scoped shell.

Decision

When the app declares realms, the Leptos web shell is now tenant-scoped:

  • Dimension selection — the shell scopes by the root realm dimension (e.g. tenant), else the first declared dimension, else the first aggregate's realm dimension. This mirrors how an operator thinks about "which tenant am I in?" and matches the admin realm bar's source.
  • Tenant bar — the shell's nav-actions gains a realm input + apply button (only when a realm dimension exists). The selected tenant is persisted in localStorage under mosaic-realm-{dim} and restored on load.
  • Realm header — every client fetch helper (api, api_put, api_post) stamps the x-realm-{dim} header from the stored tenant, so all Leptos pages are scoped to the selected tenant in every hydration mode (CSR, and the hydrated full/islands browser bundle).
  • Apply reloads the page so the new tenant's data is fetched fresh (the SSR branches keep reading the in-process store, which is already realm-filtered server-side).

The realm machinery is emitted only when a realm dimension exists and is #[cfg(target_arch = "wasm32")]-gated, so non-realm and server-rendered builds are byte-identical.

2. CSR + vectordb compile fixes

Two pre-existing bugs blocked any CSR app with a vector_db from compiling (no example exercised that combination until now):

  1. The knowledge page's search closure was let do_search = move || { … } (0-arg) but used as on:click=do_search (1-arg). Fixed to move |_|.
  2. The knowledge page's collection selector used <select value=kb_sel …>, but Leptos does not support a value binding on a <select>. The select now relies on native display + on:change to sync the signal that the search reads.

Both are covered by rendering a CSR + vectordb (+ realms) app and compiling its web lens for wasm32-unknown-unknown in local validation.

Consequences

  • The Leptos web shell is now consistently tenant-scoped, matching the admin pages and EE's tenant routing. This closes the "tenant scoping not adopted" item in the app-shell feature-map row.
  • The six-position SlotShell itself is still not adopted (Mosaic uses a 4-position switchable shell) — an intentional design difference, not a functional gap.
  • CSR apps with a knowledge base now compile and run the knowledge/search pages.
  • Tenant selection is per-browser (localStorage); a server-side session-scoped tenant would be a follow-up if multi-tenant SSO sessions need it.