@assistant-ui/store 0.2.9 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +36 -65
  2. package/dist/AuiIf.d.ts +31 -0
  3. package/dist/AuiIf.d.ts.map +1 -1
  4. package/dist/AuiIf.js +22 -0
  5. package/dist/AuiIf.js.map +1 -1
  6. package/dist/Derived.d.ts +1 -3
  7. package/dist/Derived.d.ts.map +1 -1
  8. package/dist/RenderChildrenWithAccessor.d.ts.map +1 -1
  9. package/dist/RenderChildrenWithAccessor.js +11 -7
  10. package/dist/RenderChildrenWithAccessor.js.map +1 -1
  11. package/dist/useAui.d.ts +59 -0
  12. package/dist/useAui.d.ts.map +1 -1
  13. package/dist/useAui.js +39 -21
  14. package/dist/useAui.js.map +1 -1
  15. package/dist/useAuiEvent.d.ts +41 -0
  16. package/dist/useAuiEvent.d.ts.map +1 -1
  17. package/dist/useAuiEvent.js +41 -0
  18. package/dist/useAuiEvent.js.map +1 -1
  19. package/dist/useAuiState.d.ts +25 -8
  20. package/dist/useAuiState.d.ts.map +1 -1
  21. package/dist/useAuiState.js +25 -8
  22. package/dist/useAuiState.js.map +1 -1
  23. package/dist/utils/react-assistant-context.d.ts +18 -5
  24. package/dist/utils/react-assistant-context.d.ts.map +1 -1
  25. package/dist/utils/react-assistant-context.js +16 -5
  26. package/dist/utils/react-assistant-context.js.map +1 -1
  27. package/dist/utils/splitClients.d.ts.map +1 -1
  28. package/dist/utils/splitClients.js.map +1 -1
  29. package/package.json +5 -5
  30. package/src/AuiIf.ts +35 -1
  31. package/src/Derived.ts +1 -1
  32. package/src/RenderChildrenWithAccessor.tsx +10 -8
  33. package/src/__tests__/RenderChildrenWithAccessor.test.tsx +136 -0
  34. package/src/useAui.ts +101 -32
  35. package/src/useAuiEvent.ts +41 -0
  36. package/src/useAuiState.ts +25 -8
  37. package/src/utils/react-assistant-context.tsx +18 -5
  38. package/src/utils/splitClients.ts +4 -2
@@ -2,18 +2,35 @@ import { useSyncExternalStore, useDebugValue } from "react";
2
2
  import { useAui } from "./useAui.js";
3
3
  import { getProxiedAssistantState } from "./utils/proxied-assistant-state.js";
4
4
  /**
5
- * Hook to access a slice of the assistant state with automatic subscription
5
+ * Subscribes to a slice of {@link AssistantState} and re-renders the
6
+ * component whenever that slice changes.
6
7
  *
7
- * @param selector - Function to select a slice of the state
8
- * @returns The selected state slice
8
+ * The `selector` is called on every store update; its return value is
9
+ * compared by `Object.is`, and the component re-renders only when the
10
+ * selected slice changes. Returning the entire state object is not
11
+ * supported and throws at runtime — select a specific field instead, or
12
+ * compose multiple `useAuiState` calls. Returning a new object or array
13
+ * literal, including spreading `s.thread` into a new object, causes a
14
+ * re-render on every store update; either select primitives or return a
15
+ * memoized reference.
16
+ *
17
+ * @param selector - Pure function that derives a value from the current
18
+ * assistant state. Should be cheap and referentially stable for equal
19
+ * inputs (plain field reads, primitives, or memoized values).
20
+ * @returns The currently selected slice.
9
21
  *
10
22
  * @example
11
- * ```typescript
12
- * const aui = useAui({
13
- * foo: RootScope({ ... }),
14
- * });
23
+ * ```tsx
24
+ * // Disable a button while a run is in flight.
25
+ * const isRunning = useAuiState((s) => s.thread.isRunning);
26
+ * ```
15
27
  *
16
- * const bar = useAuiState((s) => s.foo.bar);
28
+ * @example
29
+ * ```tsx
30
+ * // Prefer multiple selectors over an inline object literal, which would
31
+ * // create a new reference on every render.
32
+ * const text = useAuiState((s) => s.composer.text);
33
+ * const canSend = useAuiState((s) => s.composer.canSend);
17
34
  * ```
18
35
  */
19
36
  export const useAuiState = (selector) => {
@@ -1 +1 @@
1
- {"version":3,"file":"useAuiState.js","sourceRoot":"","sources":["../src/useAuiState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,EAAE,MAAM,EAAE,oBAAiB;AAClC,OAAO,EAAE,wBAAwB,EAAE,2CAAwC;AAE3E;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAI,QAAsC,EAAK,EAAE;IAC1E,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,YAAY,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAEnD,MAAM,KAAK,GAAG,oBAAoB,CAChC,GAAG,CAAC,SAAS,EACb,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,EAC5B,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAC7B,CAAC;IAEF,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,oGAAoG,CACrG,CAAC;IACJ,CAAC;IAED,aAAa,CAAC,KAAK,CAAC,CAAC;IAErB,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
1
+ {"version":3,"file":"useAuiState.js","sourceRoot":"","sources":["../src/useAuiState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,EAAE,MAAM,EAAE,oBAAiB;AAClC,OAAO,EAAE,wBAAwB,EAAE,2CAAwC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAI,QAAsC,EAAK,EAAE;IAC1E,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,YAAY,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAEnD,MAAM,KAAK,GAAG,oBAAoB,CAChC,GAAG,CAAC,SAAS,EACb,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,EAC5B,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAC7B,CAAC;IAEF,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,oGAAoG,CACrG,CAAC;IACJ,CAAC;IAED,aAAa,CAAC,KAAK,CAAC,CAAC;IAErB,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
@@ -6,17 +6,30 @@ export declare const DefaultAssistantClient: AssistantClient;
6
6
  export declare const createRootAssistantClient: () => AssistantClient;
7
7
  export declare const useAssistantContextValue: () => AssistantClient;
8
8
  /**
9
- * Provider component for AssistantClient
9
+ * Supplies an `AssistantClient` to the React tree.
10
+ *
11
+ * Place near the root of any subtree that uses {@link useAui} or the
12
+ * primitives built on it. Components rendered outside an `AuiProvider`
13
+ * receive a default client whose scope accessors throw on use, so
14
+ * missing-provider mistakes surface at the point of use.
15
+ *
16
+ * When mounting a runtime built with one of the runtime hooks, use
17
+ * {@link AssistantRuntimeProvider} — it installs an `AuiProvider`
18
+ * internally — rather than wiring `AuiProvider` yourself.
10
19
  *
11
20
  * @example
12
- * ```typescript
13
- * <AuiProvider value={aui}>
14
- * <YourApp />
15
- * </AuiProvider>
21
+ * ```tsx
22
+ * function ScopedAssistant({ children, scopes }) {
23
+ * const aui = useAui(scopes);
24
+ *
25
+ * return <AuiProvider value={aui}>{children}</AuiProvider>;
26
+ * }
16
27
  * ```
17
28
  */
18
29
  export declare const AuiProvider: ({ value, children, }: {
30
+ /** Assistant client to expose to descendants. */
19
31
  value: AssistantClient;
32
+ /** Subtree that may read from the client. */
20
33
  children: React.ReactNode;
21
34
  }) => React.ReactElement;
22
35
  //# sourceMappingURL=react-assistant-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"react-assistant-context.d.ts","sourceRoot":"","sources":["../../src/utils/react-assistant-context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,eAAe,EAA2B,2BAAwB;AAmDhF,iEAAiE;AACjE,eAAO,MAAM,sBAAsB,EAAE,eAIlC,CAAC;AAMJ,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB,QAAO,eAUzC,CAAC;AAOL,eAAO,MAAM,wBAAwB,QAAO,eAE3C,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAI,sBAGzB;IACD,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,KAAG,KAAK,CAAC,YAMT,CAAC"}
1
+ {"version":3,"file":"react-assistant-context.d.ts","sourceRoot":"","sources":["../../src/utils/react-assistant-context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,eAAe,EAA2B,2BAAwB;AAmDhF,iEAAiE;AACjE,eAAO,MAAM,sBAAsB,EAAE,eAIlC,CAAC;AAMJ,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB,QAAO,eAUzC,CAAC;AAOL,eAAO,MAAM,wBAAwB,QAAO,eAE3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,WAAW,GAAI,sBAGzB;IACD,iDAAiD;IACjD,KAAK,EAAE,eAAe,CAAC;IACvB,6CAA6C;IAC7C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,KAAG,KAAK,CAAC,YAMT,CAAC"}
@@ -53,13 +53,24 @@ export const useAssistantContextValue = () => {
53
53
  return useContext(AssistantContext);
54
54
  };
55
55
  /**
56
- * Provider component for AssistantClient
56
+ * Supplies an `AssistantClient` to the React tree.
57
+ *
58
+ * Place near the root of any subtree that uses {@link useAui} or the
59
+ * primitives built on it. Components rendered outside an `AuiProvider`
60
+ * receive a default client whose scope accessors throw on use, so
61
+ * missing-provider mistakes surface at the point of use.
62
+ *
63
+ * When mounting a runtime built with one of the runtime hooks, use
64
+ * {@link AssistantRuntimeProvider} — it installs an `AuiProvider`
65
+ * internally — rather than wiring `AuiProvider` yourself.
57
66
  *
58
67
  * @example
59
- * ```typescript
60
- * <AuiProvider value={aui}>
61
- * <YourApp />
62
- * </AuiProvider>
68
+ * ```tsx
69
+ * function ScopedAssistant({ children, scopes }) {
70
+ * const aui = useAui(scopes);
71
+ *
72
+ * return <AuiProvider value={aui}>{children}</AuiProvider>;
73
+ * }
63
74
  * ```
64
75
  */
65
76
  export const AuiProvider = ({ value, children, }) => {
@@ -1 +1 @@
1
- {"version":3,"file":"react-assistant-context.js","sourceRoot":"","sources":["../../src/utils/react-assistant-context.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAElD,OAAO,EACL,2BAA2B,EAC3B,8BAA8B,GAC/B,qCAAkC;AACnC,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,8BAA2B;AAE/E,MAAM,eAAe,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;AAEvC,MAAM,sBAAsB,GAAG,CAC7B,OAAe,EACiB,EAAE;IAClC,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE;QACf,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC,CAAmC,CAAC;IACrC,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC;IACjB,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC;IAChB,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,MAAM,kCACJ,SAAQ,gBAAgB;IAGxB,GAAG,CAAC,CAAU,EAAE,IAAqB;QACnC,IAAI,IAAI,KAAK,WAAW;YAAE,OAAO,eAAe,CAAC;QACjD,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,eAAe,CAAC;QAC1C,IAAI,IAAI,KAAK,8BAA8B;YACzC,OAAO,2CAA2C,CAAC;QACrD,MAAM,aAAa,GAAG,uBAAuB,CAC3C,IAAI,EACJ,wBAAwB,CACzB,CAAC;QACF,IAAI,aAAa,KAAK,KAAK;YAAE,OAAO,aAAa,CAAC;QAClD,OAAO,sBAAsB,CAC3B,oHAAoH,CACrH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,8BAA8B,CAAC,CAAC;IAC7D,CAAC;IAED,GAAG,CAAC,CAAU,EAAE,IAAqB;QACnC,OAAO,CACL,IAAI,KAAK,WAAW;YACpB,IAAI,KAAK,IAAI;YACb,IAAI,KAAK,8BAA8B,CACxC,CAAC;IACJ,CAAC;CACF;AACD,iEAAiE;AACjE,MAAM,CAAC,MAAM,sBAAsB,GACjC,IAAI,KAAK,CACP,EAAqB,EACrB,IAAI,kCAAkC,EAAE,CACzC,CAAC;AAEJ,MAAM,2CAA2C,GAAG,2BAA2B,CAC7E,sBAAsB,CACvB,CAAC;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAoB,EAAE,CAC7D,IAAI,KAAK,CAAkB,EAAqB,EAAE;IAChD,GAAG,CAAC,CAAkB,EAAE,IAAqB;QAC3C,MAAM,aAAa,GAAG,uBAAuB,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QACvE,IAAI,aAAa,KAAK,KAAK;YAAE,OAAO,aAAa,CAAC;QAElD,OAAO,sBAAsB,CAC3B,sCAAsC,MAAM,CAAC,IAAI,CAAC,aAAa,CAChE,CAAC;IACJ,CAAC;CACF,CAAC,CAAC;AAEL;;GAEG;AACH,MAAM,gBAAgB,GAAG,aAAa,CAAkB,sBAAsB,CAAC,CAAC;AAEhF,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAoB,EAAE;IAC5D,OAAO,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAC1B,KAAK,EACL,QAAQ,GAIT,EAAsB,EAAE;IACvB,OAAO,CACL,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACpC,QAAQ,GACiB,CAC7B,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"react-assistant-context.js","sourceRoot":"","sources":["../../src/utils/react-assistant-context.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAElD,OAAO,EACL,2BAA2B,EAC3B,8BAA8B,GAC/B,qCAAkC;AACnC,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,8BAA2B;AAE/E,MAAM,eAAe,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;AAEvC,MAAM,sBAAsB,GAAG,CAC7B,OAAe,EACiB,EAAE;IAClC,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE;QACf,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC,CAAmC,CAAC;IACrC,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC;IACjB,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC;IAChB,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,MAAM,kCACJ,SAAQ,gBAAgB;IAGxB,GAAG,CAAC,CAAU,EAAE,IAAqB;QACnC,IAAI,IAAI,KAAK,WAAW;YAAE,OAAO,eAAe,CAAC;QACjD,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,eAAe,CAAC;QAC1C,IAAI,IAAI,KAAK,8BAA8B;YACzC,OAAO,2CAA2C,CAAC;QACrD,MAAM,aAAa,GAAG,uBAAuB,CAC3C,IAAI,EACJ,wBAAwB,CACzB,CAAC;QACF,IAAI,aAAa,KAAK,KAAK;YAAE,OAAO,aAAa,CAAC;QAClD,OAAO,sBAAsB,CAC3B,oHAAoH,CACrH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,8BAA8B,CAAC,CAAC;IAC7D,CAAC;IAED,GAAG,CAAC,CAAU,EAAE,IAAqB;QACnC,OAAO,CACL,IAAI,KAAK,WAAW;YACpB,IAAI,KAAK,IAAI;YACb,IAAI,KAAK,8BAA8B,CACxC,CAAC;IACJ,CAAC;CACF;AACD,iEAAiE;AACjE,MAAM,CAAC,MAAM,sBAAsB,GACjC,IAAI,KAAK,CACP,EAAqB,EACrB,IAAI,kCAAkC,EAAE,CACzC,CAAC;AAEJ,MAAM,2CAA2C,GAAG,2BAA2B,CAC7E,sBAAsB,CACvB,CAAC;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAoB,EAAE,CAC7D,IAAI,KAAK,CAAkB,EAAqB,EAAE;IAChD,GAAG,CAAC,CAAkB,EAAE,IAAqB;QAC3C,MAAM,aAAa,GAAG,uBAAuB,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QACvE,IAAI,aAAa,KAAK,KAAK;YAAE,OAAO,aAAa,CAAC;QAElD,OAAO,sBAAsB,CAC3B,sCAAsC,MAAM,CAAC,IAAI,CAAC,aAAa,CAChE,CAAC;IACJ,CAAC;CACF,CAAC,CAAC;AAEL;;GAEG;AACH,MAAM,gBAAgB,GAAG,aAAa,CAAkB,sBAAsB,CAAC,CAAC;AAEhF,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAoB,EAAE;IAC5D,OAAO,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAC1B,KAAK,EACL,QAAQ,GAMT,EAAsB,EAAE;IACvB,OAAO,CACL,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACpC,QAAQ,GACiB,CAC7B,CAAC;AACJ,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"splitClients.d.ts","sourceRoot":"","sources":["../../src/utils/splitClients.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,cAAc,EAAE,sBAAmB;AAC1D,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,WAAW,EACZ,2BAAwB;AAEzB,OAAO,KAAK,EAAE,MAAM,EAAE,qBAAkB;AAGxC,MAAM,MAAM,WAAW,GAAG,OAAO,CAC/B,MAAM,CAAC,WAAW,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,CAChD,CAAC;AACF,MAAM,MAAM,cAAc,GAAG,OAAO,CAClC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC,CACjD,CAAC;AAsDF,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,CAAC,KAAK,EACrB,YAAY,eAAe;;;CAQ5B,CAAC"}
1
+ {"version":3,"file":"splitClients.d.ts","sourceRoot":"","sources":["../../src/utils/splitClients.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,cAAc,EAAE,sBAAmB;AAC1D,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,WAAW,EACZ,2BAAwB;AAEzB,OAAO,KAAK,EAAE,MAAM,EAAE,qBAAkB;AAGxC,MAAM,MAAM,WAAW,GAAG,OAAO,CAC/B,MAAM,CAAC,WAAW,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,CAChD,CAAC;AACF,MAAM,MAAM,cAAc,GAAG,OAAO,CAClC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC,CACjD,CAAC;AAwDF,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,CAAC,KAAK,EACrB,YAAY,eAAe;;;CAQ5B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"splitClients.js","sourceRoot":"","sources":["../../src/utils/splitClients.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAuB,sBAAmB;AAM1D,OAAO,EAAE,kBAAkB,EAAE,oCAAiC;AAE9D,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAS5C;;;GAGG;AACH,SAAS,YAAY,CAAC,OAAqB,EAAE,UAA2B;IACtE,oEAAoE;IACpE,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,EAG1B,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEnD,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,OAAO,OAAO,EAAE,CAAC;QACf,OAAO,GAAG,KAAK,CAAC;QAChB,KAAK,MAAM,aAAa,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,IAAI,aAAa,CAAC,IAAI,KAAM,OAAmB;gBAAE,SAAS;YAC1D,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC9C,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAEhC,MAAM,SAAS,GAAG,kBAAkB,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YACzD,IAAI,SAAS,EAAE,CAAC;gBACd,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;gBAC9B,OAAO,GAAG,IAAI,CAAC;gBACf,MAAM,CAAC,4DAA4D;YACrE,CAAC;QACH,CAAC;IACH,CAAC;IAED,oCAAoC;IACpC,MAAM,WAAW,GAAgB,EAAE,CAAC;IACpC,MAAM,cAAc,GAAmB,EAAE,CAAC;IAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAGrD,EAAE,CAAC;QACJ,IAAI,aAAa,CAAC,IAAI,KAAM,OAAmB,EAAE,CAAC;YAChD,cAAc,CAAC,GAAG,CAAC,GAAG,aAA4C,CAAC;QACrE,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,GAAG,CAAC,GAAG,aAA2C,CAAC;QACjE,CAAC;IACH,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,oBAAoB,GAAG,CAAmB,MAAS,EAAE,EAAE;IAC3D,wEAAwE;IACxE,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACnE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,OAAqB,EACrB,UAA2B,EAC3B,EAAE;IACF,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAE1E,OAAO;QACL,WAAW,EAAE,oBAAoB,CAAC,WAAW,CAAC;QAC9C,cAAc,EAAE,oBAAoB,CAAC,cAAc,CAAC;KACrD,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"splitClients.js","sourceRoot":"","sources":["../../src/utils/splitClients.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAuB,sBAAmB;AAM1D,OAAO,EAAE,kBAAkB,EAAE,oCAAiC;AAE9D,OAAO,EAAE,OAAO,EAAwB,MAAM,mBAAmB,CAAC;AASlE;;;GAGG;AACH,SAAS,YAAY,CAAC,OAAqB,EAAE,UAA2B;IACtE,oEAAoE;IACpE,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,EAG1B,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEnD,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,OAAO,OAAO,EAAE,CAAC;QACf,OAAO,GAAG,KAAK,CAAC;QAChB,KAAK,MAAM,aAAa,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,IAAI,aAAa,CAAC,IAAI,KAAM,OAAmB;gBAAE,SAAS;YAC1D,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC9C,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAEhC,MAAM,SAAS,GAAG,kBAAkB,CAClC,aAAa,CAAC,IAA4C,CAC3D,CAAC;YACF,IAAI,SAAS,EAAE,CAAC;gBACd,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;gBAC9B,OAAO,GAAG,IAAI,CAAC;gBACf,MAAM,CAAC,4DAA4D;YACrE,CAAC;QACH,CAAC;IACH,CAAC;IAED,oCAAoC;IACpC,MAAM,WAAW,GAAgB,EAAE,CAAC;IACpC,MAAM,cAAc,GAAmB,EAAE,CAAC;IAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAGrD,EAAE,CAAC;QACJ,IAAI,aAAa,CAAC,IAAI,KAAM,OAAmB,EAAE,CAAC;YAChD,cAAc,CAAC,GAAG,CAAC,GAAG,aAA4C,CAAC;QACrE,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,GAAG,CAAC,GAAG,aAA2C,CAAC;QACjE,CAAC;IACH,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,oBAAoB,GAAG,CAAmB,MAAS,EAAE,EAAE;IAC3D,wEAAwE;IACxE,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACnE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,OAAqB,EACrB,UAA2B,EAC3B,EAAE;IACF,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAE1E,OAAO;QACL,WAAW,EAAE,oBAAoB,CAAC,WAAW,CAAC;QAC9C,cAAc,EAAE,oBAAoB,CAAC,cAAc,CAAC;KACrD,CAAC;AACJ,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/store",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Tap-based state management for @assistant-ui",
5
5
  "keywords": [
6
6
  "state-management",
@@ -30,7 +30,7 @@
30
30
  "use-effect-event": "^2.0.3"
31
31
  },
32
32
  "peerDependencies": {
33
- "@assistant-ui/tap": "^0.5.10",
33
+ "@assistant-ui/tap": "^0.5.11",
34
34
  "@types/react": "*",
35
35
  "react": "^18 || ^19"
36
36
  },
@@ -43,11 +43,11 @@
43
43
  "@testing-library/react": "^16.3.2",
44
44
  "@types/react": "^19.2.14",
45
45
  "@types/react-dom": "^19.2.3",
46
- "jsdom": "^29.1.0",
46
+ "jsdom": "^29.1.1",
47
47
  "react": "^19.2.5",
48
48
  "vitest": "^4.1.5",
49
- "@assistant-ui/tap": "0.5.10",
50
- "@assistant-ui/x-buildutils": "0.0.6"
49
+ "@assistant-ui/tap": "0.5.11",
50
+ "@assistant-ui/x-buildutils": "0.0.8"
51
51
  },
52
52
  "publishConfig": {
53
53
  "access": "public",
package/src/AuiIf.ts CHANGED
@@ -5,10 +5,44 @@ import { useAuiState } from "./useAuiState";
5
5
  import type { AssistantState } from "./types/client";
6
6
 
7
7
  export namespace AuiIf {
8
- export type Props = PropsWithChildren<{ condition: AuiIf.Condition }>;
8
+ /** Props for `AuiIf`. */
9
+ export type Props = PropsWithChildren<{
10
+ /**
11
+ * Selector that decides whether to render `children`. Children render
12
+ * when this returns `true` and unmount when it returns `false`.
13
+ */
14
+ condition: AuiIf.Condition;
15
+ }>;
16
+
17
+ /**
18
+ * Selector passed to `AuiIf`. Receives the assistant state and must
19
+ * return a boolean.
20
+ */
9
21
  export type Condition = (state: AssistantState) => boolean;
10
22
  }
11
23
 
24
+ /**
25
+ * Conditionally renders children based on a slice of assistant state.
26
+ *
27
+ * A thin wrapper around {@link useAuiState} that renders its children
28
+ * when `condition` returns `true` and unmounts them when it returns
29
+ * `false`. Keeps render logic declarative without mounting unused
30
+ * subtrees.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * <AuiIf condition={(s) => s.thread.isRunning}>
35
+ * <CancelButton />
36
+ * </AuiIf>
37
+ * ```
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * <AuiIf condition={(s) => s.thread.messages.length === 0}>
42
+ * <EmptyState />
43
+ * </AuiIf>
44
+ * ```
45
+ */
12
46
  export const AuiIf: FC<AuiIf.Props> = ({ children, condition }) => {
13
47
  const result = useAuiState(condition);
14
48
  return result ? children : null;
package/src/Derived.ts CHANGED
@@ -42,5 +42,5 @@ export namespace Derived {
42
42
  */
43
43
  export type Props<K extends ClientNames> = {
44
44
  get: (client: AssistantClient) => ReturnType<AssistantClientAccessor<K>>;
45
- } & (ClientMeta<K> | { getMeta: (client: AssistantClient) => ClientMeta<K> });
45
+ } & ClientMeta<K>;
46
46
  }
@@ -10,18 +10,20 @@ export const useGetItemAccessor = <T,>(
10
10
  ) => {
11
11
  const aui = useAui();
12
12
 
13
- // if the consumer never accesses the item, do not trigger rerenders
14
- const cacheRef = useRef<T | undefined>(undefined);
13
+ // Track access with a dedicated flag:
14
+ // useSyncExternalStore may call getSnapshot() after commit (tearing checks),
15
+ // which would re-cache the current state and mask later real updates.
16
+ // Use the current state as the pre-access snapshot so the post-commit check
17
+ // matches getItemState(aui) and doesn't schedule an unnecessary re-render.
18
+ const accessedRef = useRef(false);
19
+ const currentValue = accessedRef.current ? null : getItemState(aui);
15
20
  useAuiState(() => {
16
- if (cacheRef.current === undefined) {
17
- cacheRef.current = getItemState(aui);
18
- }
19
- return cacheRef.current;
21
+ if (!accessedRef.current) return currentValue;
22
+ return getItemState(aui);
20
23
  });
21
24
 
22
25
  return () => {
23
- cacheRef.current = undefined; // clear the cache (rerender on next state change)
24
-
26
+ accessedRef.current = true;
25
27
  return getItemState(aui);
26
28
  };
27
29
  };
@@ -0,0 +1,136 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import type { ReactNode } from "react";
4
+ import { act, render } from "@testing-library/react";
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+ import { AuiProvider } from "../utils/react-assistant-context";
7
+ import { RenderChildrenWithAccessor } from "../RenderChildrenWithAccessor";
8
+ import { PROXIED_ASSISTANT_STATE_SYMBOL } from "../utils/proxied-assistant-state";
9
+
10
+ afterEach(() => {
11
+ vi.restoreAllMocks();
12
+ });
13
+
14
+ type Listener = () => void;
15
+
16
+ const createTestAuiClient = () => {
17
+ const listeners = new Set<Listener>();
18
+ let itemState: { value: number; isEditing: boolean } = {
19
+ value: 1,
20
+ isEditing: false,
21
+ };
22
+
23
+ const proxiedState = {
24
+ item: itemState,
25
+ };
26
+
27
+ const client = {
28
+ subscribe: (listener: Listener) => {
29
+ listeners.add(listener);
30
+ return () => listeners.delete(listener);
31
+ },
32
+ on: () => () => {},
33
+ [PROXIED_ASSISTANT_STATE_SYMBOL]: proxiedState,
34
+ } as const;
35
+
36
+ return {
37
+ client,
38
+ getItemState: () => itemState,
39
+ update: (next: Partial<typeof itemState>) => {
40
+ itemState = { ...itemState, ...next };
41
+ proxiedState.item = itemState;
42
+ // biome-ignore lint/suspicious/useIterableCallbackReturn: forEach callback intentionally has no return
43
+ listeners.forEach((listener) => listener());
44
+ },
45
+ };
46
+ };
47
+
48
+ describe("RenderChildrenWithAccessor", () => {
49
+ it("re-renders when accessed state updates (regression: issue #3838)", () => {
50
+ const testClient = createTestAuiClient();
51
+ const wrapper = ({ children }: { children: ReactNode }) => (
52
+ <AuiProvider value={testClient.client as never}>{children}</AuiProvider>
53
+ );
54
+
55
+ const { container } = render(
56
+ <RenderChildrenWithAccessor
57
+ getItemState={() => testClient.getItemState()}
58
+ >
59
+ {(getItem) => {
60
+ const item = getItem();
61
+ return <div>{item.isEditing ? "editing" : "viewing"}</div>;
62
+ }}
63
+ </RenderChildrenWithAccessor>,
64
+ { wrapper },
65
+ );
66
+
67
+ expect(container.textContent).toBe("viewing");
68
+
69
+ act(() => {
70
+ testClient.update({ isEditing: true });
71
+ });
72
+
73
+ expect(container.textContent).toBe("editing");
74
+
75
+ act(() => {
76
+ testClient.update({ isEditing: false });
77
+ });
78
+
79
+ expect(container.textContent).toBe("viewing");
80
+ });
81
+
82
+ it("does not schedule an extra render on first access (initial snapshot matches getItemState)", () => {
83
+ const testClient = createTestAuiClient();
84
+ const wrapper = ({ children }: { children: ReactNode }) => (
85
+ <AuiProvider value={testClient.client as never}>{children}</AuiProvider>
86
+ );
87
+
88
+ const renderSpy = vi.fn();
89
+
90
+ render(
91
+ <RenderChildrenWithAccessor
92
+ getItemState={() => testClient.getItemState()}
93
+ >
94
+ {(getItem) => {
95
+ renderSpy();
96
+ const item = getItem();
97
+ return <div>{item.value}</div>;
98
+ }}
99
+ </RenderChildrenWithAccessor>,
100
+ { wrapper },
101
+ );
102
+
103
+ // first mount accesses the item; useSyncExternalStore's post-commit
104
+ // tearing check should see a stable snapshot and not force a re-render
105
+ expect(renderSpy).toHaveBeenCalledTimes(1);
106
+ });
107
+
108
+ it("does not re-render when item is never accessed", () => {
109
+ const testClient = createTestAuiClient();
110
+ const wrapper = ({ children }: { children: ReactNode }) => (
111
+ <AuiProvider value={testClient.client as never}>{children}</AuiProvider>
112
+ );
113
+
114
+ const renderSpy = vi.fn();
115
+
116
+ render(
117
+ <RenderChildrenWithAccessor
118
+ getItemState={() => testClient.getItemState()}
119
+ >
120
+ {() => {
121
+ renderSpy();
122
+ return <div>static</div>;
123
+ }}
124
+ </RenderChildrenWithAccessor>,
125
+ { wrapper },
126
+ );
127
+
128
+ const initialRenderCount = renderSpy.mock.calls.length;
129
+
130
+ act(() => {
131
+ testClient.update({ value: 99 });
132
+ });
133
+
134
+ expect(renderSpy.mock.calls.length).toBe(initialRenderCount);
135
+ });
136
+ });
package/src/useAui.ts CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  resource,
6
6
  tapMemo,
7
7
  tapResources,
8
- tapEffectEvent,
9
8
  tapEffect,
10
9
  tapRef,
11
10
  tapResource,
@@ -19,7 +18,7 @@ import type {
19
18
  ClientElement,
20
19
  ClientMeta,
21
20
  } from "./types/client";
22
- import type { Derived, DerivedElement } from "./Derived";
21
+ import type { DerivedElement } from "./Derived";
23
22
  import {
24
23
  useAssistantContextValue,
25
24
  DefaultAssistantClient,
@@ -209,24 +208,6 @@ const RootClientsAccessorsResource = resource(
209
208
  },
210
209
  );
211
210
 
212
- type MetaMemo<K extends ClientNames> = {
213
- meta?: ClientMeta<K>;
214
- dep?: unknown;
215
- };
216
-
217
- const getMeta = <K extends ClientNames>(
218
- props: Derived.Props<K>,
219
- clientRef: { parent: AssistantClient; current: AssistantClient | null },
220
- memo: MetaMemo<K>,
221
- ): ClientMeta<K> => {
222
- if ("source" in props && "query" in props) return props;
223
- if (memo.dep === props) return memo.meta!;
224
- const meta = props.getMeta(clientRef.current!);
225
- memo.meta = meta;
226
- memo.dep = props;
227
- return meta;
228
- };
229
-
230
211
  const DerivedClientAccessorResource = resource(
231
212
  <K extends ClientNames>({
232
213
  element,
@@ -237,17 +218,24 @@ const DerivedClientAccessorResource = resource(
237
218
  clientRef: { parent: AssistantClient; current: AssistantClient | null };
238
219
  name: K;
239
220
  }) => {
240
- const get = tapEffectEvent(() => element.props);
221
+ // Track the latest props on a ref updated in render. The fiber is
222
+ // keyed on the scope's meta by DerivedClientsAccessorsResource, so
223
+ // source/query are stable for this fiber's lifetime and the only
224
+ // value that can change between renders for the same fiber is the
225
+ // identity of the `get` closure. Routing reads through the ref
226
+ // avoids the one-commit lag that the previous `tapEffectEvent`
227
+ // path imposed.
228
+ const propsRef = tapRef(element.props);
229
+ propsRef.current = element.props;
241
230
 
242
231
  return tapMemo(() => {
243
- const clientFunction = () => get().get(clientRef.current!);
244
- const metaMemo = {};
232
+ const clientFunction = () => propsRef.current.get(clientRef.current!);
245
233
  Object.defineProperties(clientFunction, {
246
234
  source: {
247
- get: () => getMeta(get(), clientRef, metaMemo).source,
235
+ value: propsRef.current.source,
248
236
  },
249
237
  query: {
250
- get: () => getMeta(get(), clientRef, metaMemo).query,
238
+ value: propsRef.current.query,
251
239
  },
252
240
  name: {
253
241
  value: name,
@@ -259,6 +247,26 @@ const DerivedClientAccessorResource = resource(
259
247
  },
260
248
  );
261
249
 
250
+ const serializeMeta = <K extends ClientNames>(
251
+ name: K,
252
+ meta: ClientMeta<K>,
253
+ ): string => {
254
+ // Sort top-level keys so {a, b} and {b, a} hash to the same fiber
255
+ // identity, and guard JSON.stringify against unusual values (BigInt,
256
+ // circular refs) so render never throws here.
257
+ let queryKey: string;
258
+ try {
259
+ const sorted: Record<string, unknown> = {};
260
+ for (const k of Object.keys(meta.query as object).sort()) {
261
+ sorted[k] = (meta.query as Record<string, unknown>)[k];
262
+ }
263
+ queryKey = JSON.stringify(sorted);
264
+ } catch {
265
+ queryKey = String(meta.query);
266
+ }
267
+ return `${name}::${meta.source}::${queryKey}`;
268
+ };
269
+
262
270
  const DerivedClientsAccessorsResource = resource(
263
271
  ({
264
272
  clients,
@@ -270,16 +278,18 @@ const DerivedClientsAccessorsResource = resource(
270
278
  return tapShallowMemoArray(
271
279
  tapResources(
272
280
  () =>
273
- Object.keys(clients).map((key) =>
274
- withKey(
275
- key,
281
+ Object.keys(clients).map((key) => {
282
+ const name = key as keyof typeof clients;
283
+ const element = clients[name]!;
284
+ return withKey(
285
+ serializeMeta(name, element.props),
276
286
  DerivedClientAccessorResource({
277
- element: clients[key as keyof typeof clients]!,
287
+ element,
278
288
  clientRef,
279
- name: key as keyof typeof clients,
289
+ name,
280
290
  }),
281
- ),
282
- ),
291
+ );
292
+ }),
283
293
  [clients, clientRef],
284
294
  ),
285
295
  );
@@ -359,8 +369,67 @@ export namespace useAui {
359
369
  };
360
370
  }
361
371
 
372
+ /**
373
+ * Returns the current `AssistantClient` from context.
374
+ *
375
+ * Read the client supplied by the nearest {@link AuiProvider} or
376
+ * {@link AssistantRuntimeProvider}, then access a scope on it —
377
+ * `aui.thread()`, `aui.composer()`, `aui.message()`, and so on. Pair
378
+ * with {@link useAuiState} to read reactive state and {@link useAuiEvent}
379
+ * to subscribe to events. The returned client also exposes lower-level
380
+ * methods such as `aui.on(...)` and `aui.subscribe(...)`; prefer
381
+ * `useAuiEvent` for React event subscriptions.
382
+ *
383
+ * Rendered outside a provider, the returned client's scope accessors
384
+ * throw a descriptive error whenever they are called.
385
+ *
386
+ * @example
387
+ * ```tsx
388
+ * const aui = useAui();
389
+ *
390
+ * const onSend = () => aui.composer().send();
391
+ * const onCancel = () => aui.thread().cancelRun();
392
+ * ```
393
+ *
394
+ * @example
395
+ * ```tsx
396
+ * // Combine with useAuiState to drive disabled state.
397
+ * const aui = useAui();
398
+ * const isRunning = useAuiState((s) => s.thread.isRunning);
399
+ *
400
+ * return (
401
+ * <button disabled={isRunning} onClick={() => aui.composer().send()}>
402
+ * Send
403
+ * </button>
404
+ * );
405
+ * ```
406
+ */
362
407
  export function useAui(): AssistantClient;
408
+ /**
409
+ * Extends the parent `AssistantClient` with additional scopes.
410
+ *
411
+ * Advanced overload used when building primitives or providers — for example,
412
+ * when a custom provider needs to register a `message`, `part`, or other scope
413
+ * onto the client visible to its descendants. Application code rarely reaches
414
+ * for this; use {@link useAui} with no arguments to read the existing client.
415
+ *
416
+ * @example
417
+ * ```tsx
418
+ * const aui = useAui({
419
+ * message: Derived({
420
+ * source: "thread",
421
+ * query: { index: 0 },
422
+ * get: (aui) => aui.thread().message({ index: 0 }),
423
+ * }),
424
+ * });
425
+ *
426
+ * const role = useAuiState((s) => s.message.role);
427
+ * ```
428
+ */
363
429
  export function useAui(clients: useAui.Props): AssistantClient;
430
+ /**
431
+ * Extends an explicit parent `AssistantClient` with additional scopes.
432
+ */
364
433
  export function useAui(
365
434
  clients: useAui.Props,
366
435
  config: { parent: null | AssistantClient },
@@ -8,6 +8,47 @@ import type {
8
8
  } from "./types/events";
9
9
  import { normalizeEventSelector } from "./types/events";
10
10
 
11
+ /**
12
+ * Subscribes to an assistant event for the lifetime of the component.
13
+ *
14
+ * The subscription is established on mount and re-established whenever the
15
+ * scope or event name changes. The `callback` is wrapped in an effect-event
16
+ * shim, so the latest closure is invoked on each emission — you do not
17
+ * need to memoize it.
18
+ *
19
+ * @param selector - Either a dotted event name like
20
+ * `"thread.modelContextUpdate"` or an object `{ scope, event }`. Use
21
+ * `scope: "*"` to subscribe at the root client and receive emissions
22
+ * from any descendant scope, regardless of which one is in React
23
+ * context.
24
+ * @param callback - Invoked with the event payload. The most recent
25
+ * reference is always called. Return values are ignored, async callbacks
26
+ * are not awaited, and the callback cannot be called during render.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * // React to transient model-context changes.
31
+ * useAuiEvent("thread.modelContextUpdate", ({ threadId }) => {
32
+ * analytics.track("model_context_update", { threadId });
33
+ * });
34
+ * ```
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * // React to thread switches.
39
+ * useAuiEvent("threadListItem.switchedTo", () => {
40
+ * resetLocalState();
41
+ * });
42
+ * ```
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * // Listen from the root client rather than the current React context.
47
+ * useAuiEvent({ scope: "*", event: "thread.modelContextUpdate" }, (payload) => {
48
+ * analytics.track("model_context_update", payload);
49
+ * });
50
+ * ```
51
+ */
11
52
  export const useAuiEvent = <TEvent extends AssistantEventName>(
12
53
  selector: AssistantEventSelector<TEvent>,
13
54
  callback: AssistantEventCallback<TEvent>,