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 Overview. Displayed in a grid.
- **Chat** — The agent conversation. Contains a scrollable message list and an input. Available on Overview, Scratchpad, and views through a docked sidebar or floating popup.

## Conventions

- Follow the [repo file naming rules](../AGENTS.md#file-naming-and-references): component, hook, and test filenames use kebab-case.

- UI components in `components/ui/` are host facades over the shared Moi registry components built on Base UI React. Local compatibility copies explain why they cannot re-export yet.
- `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/<version>/` (development and production builds, generated by `scripts/build-vendor.ts`) and is served at `/vendor/react/<version>/<mode>/*`, so Moi runs fully offline — no CDN fetch. The version and build mode in the URL are why the server serves these files as `immutable` in every mode, and why a prebuilt `moi` and `bun run dev` on one port cannot poison each other's cache; the shell (`index.html`) and hashed chunks carry their own cache headers, so nothing unhashed is ever given a `max-age`.

- An importmap in `index.html` maps `react`, `react-dom`, and the jsx runtimes to the `/vendor/react/<version>/development/*` URLs (`bun run vendor:react` rewrites the version on a bump; `scripts/build-client.ts` switches the mode to `production` in `dist/`). 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.
- `dev/` is the separate entrypoint for isolated sandboxes. `dev/index.ts` selects the sandbox by pathname; each sandbox installs its own mocks before loading config or mounting. Register its HTML route in `server/web.ts` and keep mocks out of normal app startup.
- `features/<name>/` owns that feature's components, hooks, state, API hooks, and nearby tests.
- `components/ui/` contains host facades for shared UI 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.
- `app/app-provider.tsx` owns app-wide services that live exactly as long as the app, such as the workspace events socket; `runtime/app-context.ts` exposes them to hooks. Put state there when it must survive route switches. Hooks that read it do nothing outside the provider, so sandboxes and component tests open no connections.
- `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.
