# @assistant-ui/store Specification React integration for tap. Type-safe client-based state via module augmentation. ## Types ### ScopeRegistry ```typescript declare module "@assistant-ui/store" { interface ScopeRegistry { name: { methods: MethodsType; // must include getState(): StateType meta?: { source: ClientNames; query: QueryType }; events?: { "name.event": PayloadType }; }; } } ``` ### Core Types ```typescript type ClientOutput = ClientSchemas[K]["methods"] & ClientMethods; type ClientMethods = { [key: string]: (...args: any[]) => any }; type AssistantClientAccessor = (() => Methods) & ({ source; query } | { source: "root"; query: {} } | { source: null; query: null }); type AssistantClient = { [K]: AssistantClientAccessor; subscribe(cb): Unsubscribe; on(selector, cb): Unsubscribe }; type AssistantState = { [K]: ReturnType }; ``` ## API ### useAui ```typescript useAui(): AssistantClient; useAui(clients: { [K]?: ClientElement | DerivedElement }): AssistantClient; ``` Flow: splitClients → apply transformScopes → mount root clients → create derived accessors → merge with parent. ### useAuiState ```typescript useAuiState(selector: (state: AssistantState) => T): T; ``` `useSyncExternalStore` with proxied state. **Throws** if selector returns proxy (must return specific value). ### useAuiEvent ```typescript useAuiEvent(selector: E | { scope: EventScope; event: E }, callback: (payload) => void): void; ``` Selectors: `"client.event"` | `{ scope: "parent", event }` | `{ scope: "*", event }`. Wildcard `"*"` receives all. ### AuiProvider / AuiIf ```typescript {children} boolean}>{children} ``` ### Derived ```typescript Derived({ source, query, get: (client) => methods }); ``` Returns marker element. Meta (`source`, `query`) keys the derived scope's resource fiber — a different meta yields a new client function in the same render pass, so consumers see the new derivation immediately rather than after the next commit. ### attachTransformScopes ```typescript attachTransformScopes(resource, (scopes, parent) => newScopes): void; ``` Attaches a function that receives the current scopes config and the parent `AssistantClient`, and returns a new scopes config. The transform can inspect `parent[key].source` to check whether a scope exists in parent context (`null` = not provided). Transforms are collected from root elements and run iteratively (new root elements added by transforms are also processed). Single transform per resource; throws on duplicate attach. ### useAssistantClientRef / useAssistantEmit ```typescript useAssistantClientRef(): { current: AssistantClient }; useAssistantEmit(): (event: E, payload) => void; // Stable via useEffectEvent ``` ### useClientResource ```typescript useClientResource(element: ResourceElement): { state: InferClientState; methods: TMethods; key: string | number | undefined }; ``` Wraps resource element to create stable client proxy. Adds client to stack for event scoping. Use for 1:1 client mappings. State is inferred from the `getState()` method if present. ### useClientLookup ```typescript useClientLookup( getElements: () => readonly ResourceElement[], getElementsDeps: readonly unknown[] ): { state: InferClientState[]; get: (lookup: { index: number } | { key: string }) => TMethods }; ``` Wraps each element with `useClientResource`. Throws on lookup miss. ### useClientList ```typescript useClientList({ initialValues: TData[]; getKey: (data: TData) => string; resource: ContravariantResource>; }): { state: InferClientState[]; get: (lookup: { index: number } | { key: string }) => TMethods; add: (data: TData) => void }; type ResourceProps = { key: string; getInitialData: () => TData; remove: () => void }; ``` Wraps useClientLookup. `getInitialData` may only be called once. Throws on duplicate key add. ## Events ```typescript type AssistantEventName = keyof ClientEventMap | "*"; type AssistantEventScope = "*" | EventSource | AncestorsOf>; type AssistantEventSelector = E | { scope: Scope; event: E }; ``` Flow: `useAssistantEmit` captures client stack → `emit` queues via microtask → NotificationManager notifies → scope filtering. ## Implementation | Component | Behavior | |-----------|----------| | **useClientResource** | Mounts element → stable proxy via `useMemo` → delegates to ref → `SYMBOL_GET_OUTPUT` for internal access | | **ProxiedState** | Proxy intercepts `state.foo` → `aui.foo()` → `SYMBOL_GET_OUTPUT` | | **Client Stack** | Context stack per level. Emit captures stack. Listeners filter by matching stack | | **NotificationManager** | Handles events (`on`/`emit`) and state subscriptions (`subscribe`/`notifySubscribers`) | | **splitClients** | Separate root/derived → collect and run `transformScopes` iteratively → filter parent-provided scopes | ## Design | Audience | API Surface | |----------|-------------| | Users | `useAui`, `useAuiState`, `useAuiEvent`, `AuiProvider`, `AuiIf`, `Derived` | | Authors | Above + `use*`, `attachTransformScopes`, `ClientOutput`, `ScopeRegistry` | | Internal | `utils/*` | **Terminology**: Client (React Query pattern), methods (not actions), meta (optional source/query), events (optional). ## Invariants 1. `ScopeRegistry` must have ≥1 client (compile error otherwise) 2. Resources return methods object matching `ClientOutput` (with `getState()` for state access) 3. Events: `"clientName.eventName"` format 4. `meta.source` must be valid `ClientNames` 5. `useAuiState` selector cannot return whole state 6. Single transformScopes per resource; transform receives `(scopes, parent)` to inspect parent context