@theoremjs/web 0.3.4-next.377.g4dbec64 → 0.3.4-next.379.gf91c8b4

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.
package/README.md CHANGED
@@ -176,7 +176,7 @@ await chat.submit();
176
176
  ```
177
177
 
178
178
  - `getState()` returns a `ChatState`. It is a new object after each change, and the same object between changes. So you can compare it by reference, as React's `useSyncExternalStore` does.
179
- - `subscribe(listener)` calls the listener once for each group of changes, after the code that made them has run. It returns the function that unsubscribes.
179
+ - `subscribe(listener)` calls the listener once for each group of changes, after the code that made them has run. It returns the function that unsubscribes. Until the first subscribe or send, the chat asks nothing of the host. Then it reads a resumed conversation that came without its `conversationView`, and answers a restored page gate as unanswered.
180
180
  - `update(options)` gives the controller your latest options, such as a new `context`, `slots`, or `pageTools`. Each turn reads the options as they are when it is sent. `conversation`, `conversationView`, and `initialText` are read only when the controller is created.
181
181
  - Each method is bound to the controller. Pass it as a callback as it is.
182
182
 
package/client/chat.d.ts CHANGED
@@ -28,8 +28,8 @@ export type ChatOptions = {
28
28
  onConversationChange?: (ref: string) => void;
29
29
  /**
30
30
  * The value chosen for each of the profile's `inputs.slots`. Sent with every turn. A slot you
31
- * name here is fixed to that value; one you leave out is chosen with `pickSlot`, starting at
32
- * its first allowed value.
31
+ * name here is fixed to that value; one you leave out is the visitor's to pick (the chat's
32
+ * `pickSlot`), starting at its first allowed value.
33
33
  */
34
34
  slots?: Record<string, string>;
35
35
  /** Told each time the visitor picks a slot, with every slot's value. */
@@ -95,8 +95,9 @@ export type ChatUpdate<T> = T | ((prev: T) => T);
95
95
  export type ChatController = {
96
96
  getState(): ChatState;
97
97
  /**
98
- * Called after each change, once per batch of changes. Returns the unsubscribe. A conversation
99
- * resumed without its `conversationView` is read from the host once the first listener subscribes.
98
+ * Called after each change, once per batch of changes. Returns the unsubscribe. Until the first
99
+ * subscribe or send, the chat asks nothing of the host: a conversation resumed without its
100
+ * `conversationView` is read then, and a restored page gate answered then.
100
101
  */
101
102
  subscribe(listener: () => void): () => void;
102
103
  /** The latest options. `conversation`, `conversationView` and `initialText` are read only at creation. */
package/client/chat.js CHANGED
@@ -91,6 +91,8 @@ class Chat {
91
91
  #delivery = null;
92
92
  #picked = {};
93
93
  #resumed = false;
94
+ // why: A chat no one uses yet (one React's strict mode builds and drops) asks nothing of the host.
95
+ #live = false;
94
96
  #defaultsFrom;
95
97
  #answeredPage = null;
96
98
  #pageToolsWarned;
@@ -124,8 +126,10 @@ class Chat {
124
126
  getState = () => this.#state;
125
127
  subscribe = (listener) => {
126
128
  this.#listeners.add(listener);
127
- if (this.#listeners.size === 1)
129
+ if (!this.#live) {
130
+ this.#live = true;
128
131
  this.#settle();
132
+ }
129
133
  return () => this.#listeners.delete(listener);
130
134
  };
131
135
  update = (options) => {
@@ -381,8 +385,7 @@ class Chat {
381
385
  return;
382
386
  const ref = this.#conversation;
383
387
  const shown = this.#options.conversationView;
384
- // why: A chat no one follows yet (one React's strict mode builds and drops) reads nothing from the host.
385
- if (ref !== undefined && !shown && this.#listeners.size === 0)
388
+ if (ref !== undefined && !shown && !this.#live)
386
389
  return;
387
390
  this.#resumed = true;
388
391
  if (ref === undefined || !keepsConversation(iface))
@@ -404,6 +407,8 @@ class Chat {
404
407
  * invariant: A restored page gate cannot authorize callback execution. Only active dispatch grants do.
405
408
  */
406
409
  #answerPageGate() {
410
+ if (!this.#live)
411
+ return;
407
412
  const gated = this.#state.phase === 'gated' ? this.#state.session.gatedTool : null;
408
413
  if (gated?.gateKind !== 'page' || !gated.operationId)
409
414
  return;
@@ -618,6 +623,7 @@ class Chat {
618
623
  const { iface } = this.#options;
619
624
  if (!iface || this.#busy)
620
625
  return;
626
+ this.#live = true;
621
627
  const { message } = options;
622
628
  const priorDelivery = this.#delivery;
623
629
  const abort = options.turn ? new AbortController() : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theoremjs/web",
3
- "version": "0.3.4-next.377.g4dbec64",
3
+ "version": "0.3.4-next.379.gf91c8b4",
4
4
  "description": "Theorem on the web platform, with no UI framework: Web-standard server handlers that serve a profile, and the browser client that talks to them.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,6 +25,6 @@
25
25
  },
26
26
  "peerDependencies": {
27
27
  "zod": "^4.6.5",
28
- "@theoremjs/agents": "^0.3.4-next.377.g4dbec64"
28
+ "@theoremjs/agents": "^0.3.4-next.379.gf91c8b4"
29
29
  }
30
30
  }