React SPA connecting to the server via WebSocket (`useChat` hook).

Before editing host-app UI, read `../DESIGN.md` and the relevant rules in `../.agents/rules/`. `../DESIGN.md` owns visual direction and semantic choices; topic rules own syntax, component source owns APIs and dimensions, and the theme CSS owns token values. Generic design and shadcn skills may help with craft and component mechanics, but project guidance wins on aesthetics, installed-component policy, and approval requirements.

`../DESIGN.md` applies to host chrome and product surfaces. It does not apply to workspace widget/view internals or generated applets; use their workspace-local design guidance.

## Entities

- **Workspace** — Top-level container. Has a name and contains widgets and a chat. One workspace is active at a time.
- **Widget** — A configurable card inside the workspace's main area. Displayed in a grid. When there are no widgets, the chat fills the whole panel.
- **Chat** — The agent conversation. Contains a scrollable message list and an input. Always lives in the panel. Two modes: sidebar (a bounded pane beside the widgets, or the full panel when there are no widgets) and floating (popover). Floating only applies when there are widgets and they need the room.

## Conventions

- UI components in `components/ui/` are shadcn built on Base UI React.
- `lib/cn.ts` is `clsx` + `tailwind-merge`.

## Vendored React

React is not bundled into the app. It ships as locally-vendored ESM in `vendor/react/` (development and production builds, generated by `scripts/build-vendor.ts`) and is served at `/vendor/react/*`, so moi runs fully offline — no CDN fetch.

- An importmap in `index.html` maps `react`, `react-dom`, and the jsx runtimes to the `/vendor/react/*` URLs. A preload script in the same file imports them, stores the modules on `globalThis.__esm`, and only then calls `__init` to mount the app — this load order is load-bearing.
- `externalize-react.ts` is a Bun bundler plugin that replaces those specifiers in bundled code with reads from `globalThis.__esm`. The point is ONE shared React instance across the host app and every applet/widget bundle (which resolve the same importmap); bundling React in would give each its own copy and break hooks.
- `structured-clone-shim.ts` must be the first import in `index.tsx`, before any tldraw module loads.
- HMR works across the bundle/vendor boundary; the `import.meta.hot.accept()` in `externalize-react.ts` is what stops every edit from escalating to a full page reload. See the comment in `index.tsx` before touching any of this.

## Frontend boundaries

- `app/` owns routes and the app shell. Route files load data and compose features; keep feature UI out of them.
- `features/<name>/` owns that feature's components, hooks, state, API hooks, and nearby tests.
- `components/ui/` contains installed shadcn primitives. `components/shared/` contains reusable app components with no feature knowledge.
- `api/` contains shared request helpers, query keys, and query policies. Feature endpoints stay in the feature's `api.ts`.
- `runtime/` contains browser-wide lifecycle code such as HMR and shared workspace events.
- `lib/` contains small feature-neutral utilities. Do not use it as a catch-all for components or feature state.
- App code may compose features. Shared components and primitives must not import from `features/` or `app/`.
- Keep tests next to the code they cover. Extract a component, hook, or pure helper when a file starts mixing multiple responsibilities.
