@theoremjs/web 0.3.4-next.375.ga2d1878 → 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. */
@@ -94,7 +94,11 @@ export type ChatUpdate<T> = T | ((prev: T) => T);
94
94
  */
95
95
  export type ChatController = {
96
96
  getState(): ChatState;
97
- /** Called after each change, once per batch of changes. Returns the unsubscribe. */
97
+ /**
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.
101
+ */
98
102
  subscribe(listener: () => void): () => void;
99
103
  /** The latest options. `conversation`, `conversationView` and `initialText` are read only at creation. */
100
104
  update(options: ChatOptions): void;
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,6 +126,10 @@ class Chat {
124
126
  getState = () => this.#state;
125
127
  subscribe = (listener) => {
126
128
  this.#listeners.add(listener);
129
+ if (!this.#live) {
130
+ this.#live = true;
131
+ this.#settle();
132
+ }
127
133
  return () => this.#listeners.delete(listener);
128
134
  };
129
135
  update = (options) => {
@@ -377,11 +383,13 @@ class Chat {
377
383
  #resume(iface) {
378
384
  if (this.#resumed)
379
385
  return;
380
- this.#resumed = true;
381
386
  const ref = this.#conversation;
387
+ const shown = this.#options.conversationView;
388
+ if (ref !== undefined && !shown && !this.#live)
389
+ return;
390
+ this.#resumed = true;
382
391
  if (ref === undefined || !keepsConversation(iface))
383
392
  return;
384
- const shown = this.#options.conversationView;
385
393
  if (shown) {
386
394
  this.#draw(shown, this.#state.session, []);
387
395
  return;
@@ -399,6 +407,8 @@ class Chat {
399
407
  * invariant: A restored page gate cannot authorize callback execution. Only active dispatch grants do.
400
408
  */
401
409
  #answerPageGate() {
410
+ if (!this.#live)
411
+ return;
402
412
  const gated = this.#state.phase === 'gated' ? this.#state.session.gatedTool : null;
403
413
  if (gated?.gateKind !== 'page' || !gated.operationId)
404
414
  return;
@@ -613,6 +623,7 @@ class Chat {
613
623
  const { iface } = this.#options;
614
624
  if (!iface || this.#busy)
615
625
  return;
626
+ this.#live = true;
616
627
  const { message } = options;
617
628
  const priorDelivery = this.#delivery;
618
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.375.ga2d1878",
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.375.ga2d1878"
28
+ "@theoremjs/agents": "^0.3.4-next.379.gf91c8b4"
29
29
  }
30
30
  }