@noodleseed/assistant 1.21.0 → 1.22.0

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
@@ -222,7 +222,10 @@ instead of `sessionEndpoint` — the two are mutually exclusive, and a public em
222
222
  ```
223
223
 
224
224
  A public embed opens its session on **first open**, never on mount — every mount would otherwise spend
225
- one of the surface's daily admission budget. Add `signIn: true` to the surface and it becomes **mixed**:
225
+ one of the surface's daily admission budget. While that first session is resolving, the launcher remains as
226
+ a loading affordance and the panel stays hidden. The panel's first visible frame therefore already carries
227
+ the deployment's compiled name, labels, presentation, and light/dark brand tokens instead of flashing the
228
+ built-in defaults. Add `signIn: true` to the surface and it becomes **mixed**:
226
229
  anonymous visitors start immediately, and identity-dependent capabilities raise the sign-in flow below.
227
230
  Before shipping, preflight the host with `noodle assistant embed --check --surface public` (or `mixed`) —
228
231
  it verifies `script-src` too, the one CSP directive whose failure runs no widget code at all.
@@ -7118,6 +7118,33 @@ var AssistantElementPageContextController = class {
7118
7118
  }
7119
7119
  };
7120
7120
 
7121
+ // src/element-presentation-gate.ts
7122
+ var AssistantElementPresentationGate = class {
7123
+ #host;
7124
+ #primeSession;
7125
+ #focusComposer;
7126
+ constructor(host, primeSession, focusComposer) {
7127
+ this.#host = host;
7128
+ this.#primeSession = primeSession;
7129
+ this.#focusComposer = focusComposer;
7130
+ }
7131
+ syncOpenState() {
7132
+ const open = this.#host.hasAttribute("open");
7133
+ const presentationReady = this.#host.hasAttribute("data-presentation-ready");
7134
+ const launcher = this.#host.shadowRoot?.querySelector(".launcher");
7135
+ launcher?.setAttribute("aria-expanded", String(open && presentationReady));
7136
+ launcher?.setAttribute("aria-busy", String(open && !presentationReady));
7137
+ if (launcher) launcher.disabled = open && !presentationReady;
7138
+ if (open && this.#host.isConnected) this.#primeSession();
7139
+ }
7140
+ reveal() {
7141
+ if (this.#host.hasAttribute("data-presentation-ready")) return;
7142
+ this.#host.setAttribute("data-presentation-ready", "");
7143
+ this.syncOpenState();
7144
+ if (this.#host.hasAttribute("open")) queueMicrotask(this.#focusComposer);
7145
+ }
7146
+ };
7147
+
7121
7148
  // src/element-scroll-controller.ts
7122
7149
  var AssistantElementScrollController = class {
7123
7150
  #region;
@@ -7233,9 +7260,10 @@ var ASSISTANT_ELEMENT_STYLES = `<style>
7233
7260
  .launcher:hover { transform: translateY(-2px) scale(1.03); box-shadow: 0 8px 26px rgba(0,0,0,.16); }
7234
7261
  .launcher img { width: 28px; height: 28px; border-radius: 50%; object-fit: contain; }
7235
7262
  .panel { display: none; flex-direction: column; width: min(var(--ns-assistant-panel-width, var(--ns-assistant-default-panel-width)), calc(100vw - 40px)); min-height: var(--ns-assistant-min-height, var(--ns-assistant-default-min-height)); max-height: min(var(--ns-assistant-max-height, var(--ns-assistant-default-max-height)), calc(100vh - 40px)); overflow: hidden; color: var(--ns-assistant-panel-text, var(--ns-assistant-text, var(--ns-assistant-default-text))); background: var(--ns-assistant-panel, var(--ns-assistant-default-panel)); border: 1px solid var(--ns-assistant-panel-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: var(--ns-assistant-panel-radius, var(--ns-assistant-default-panel-radius)); box-shadow: var(--ns-assistant-shadow, var(--ns-assistant-default-shadow)); transform-origin: bottom right; isolation: isolate; animation: halo-panel-in .28s cubic-bezier(.23,1,.32,1) both; }
7236
- :host([open]) .panel { display: flex; }
7263
+ :host([open][data-presentation-ready]) .panel { display: flex; }
7237
7264
  .panel { position: relative; }
7238
- :host([open]) .launcher { display: none; }
7265
+ :host([open][data-presentation-ready]) .launcher { display: none; }
7266
+ :host([open]:not([data-presentation-ready])) .launcher { cursor: progress; opacity: .72; transform: none; }
7239
7267
  header { display: flex; flex: 0 0 auto; gap: 10px; align-items: center; min-height: 64px; padding: 12px 18px; color: var(--ns-assistant-header-text, inherit); background: var(--ns-assistant-header, transparent); border-bottom: 1px solid color-mix(in srgb, var(--ns-assistant-header-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))) 78%, transparent); }
7240
7268
  header strong { flex: 1; font-size: 16px; font-weight: 600; letter-spacing: 0; }
7241
7269
  .brand-logo { display: block; max-width: 120px; max-height: 28px; object-fit: contain; }
@@ -7861,6 +7889,11 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7861
7889
  #automaticPageContext = new AssistantElementPageContextController(
7862
7890
  () => !this.#pageContext
7863
7891
  );
7892
+ #presentationGate = new AssistantElementPresentationGate(
7893
+ this,
7894
+ () => this.#primeSession(),
7895
+ () => this.focusComposer()
7896
+ );
7864
7897
  #pendingUserEcho;
7865
7898
  #sessionState = "idle";
7866
7899
  #startOpenApplied = false;
@@ -7931,6 +7964,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7931
7964
  if (!this.sessionEndpoint) this.sessionEndpoint = this.getAttribute("session-endpoint") ?? "";
7932
7965
  if (!this.embedId) this.embedId = this.getAttribute("embed-id") ?? "";
7933
7966
  if (!this.serviceUrl) this.serviceUrl = this.getAttribute("service-url") ?? "";
7967
+ if (!this.sessionEndpoint && !this.embedId) this.#presentationGate.reveal();
7934
7968
  this.#media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
7935
7969
  this.#media?.addEventListener("change", this.#handleSystemTheme);
7936
7970
  document.addEventListener("keydown", this.#handleDocumentKeydown);
@@ -7947,7 +7981,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7947
7981
  } else if (!this.embedId) {
7948
7982
  this.#primeSession();
7949
7983
  }
7950
- this.#syncOpenState();
7984
+ this.#presentationGate.syncOpenState();
7951
7985
  }
7952
7986
  disconnectedCallback() {
7953
7987
  this.#conversationGeneration += 1;
@@ -7965,7 +7999,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7965
7999
  }
7966
8000
  attributeChangedCallback(name) {
7967
8001
  if (name === "theme") this.#applyTheme();
7968
- if (name === "open") this.#syncOpenState();
8002
+ if (name === "open") this.#presentationGate.syncOpenState();
7969
8003
  }
7970
8004
  open() {
7971
8005
  this.setAttribute("open", "");
@@ -8155,6 +8189,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
8155
8189
  const bootstrap = client.connect().catch((error) => {
8156
8190
  if (generation !== this.#conversationGeneration || this.#client !== client) return;
8157
8191
  this.#dispatchClientError(error, "session_failed");
8192
+ this.#presentationGate.reveal();
8158
8193
  }).finally(() => {
8159
8194
  if (this.#sessionBootstrap === bootstrap) this.#sessionBootstrap = void 0;
8160
8195
  });
@@ -8164,6 +8199,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
8164
8199
  this.dispatchEvent(new CustomEvent("assistant-event", { detail: event }));
8165
8200
  this.#events.handle(event);
8166
8201
  if (event.event === "session_started") {
8202
+ this.#presentationGate.reveal();
8167
8203
  this.#setSessionState("ready");
8168
8204
  this.#messages?.querySelector(".conversation-error")?.remove();
8169
8205
  }
@@ -8333,11 +8369,6 @@ var NoodleAssistantElement = class extends HTMLElementBase {
8333
8369
  this.#sessionState = state;
8334
8370
  syncAssistantSessionVisualState(this, this.shadowRoot, state, this.#appearance);
8335
8371
  }
8336
- #syncOpenState() {
8337
- const open = this.hasAttribute("open");
8338
- this.shadowRoot?.querySelector(".launcher")?.setAttribute("aria-expanded", String(open));
8339
- if (open && this.isConnected) this.#primeSession();
8340
- }
8341
8372
  #closeAndRestoreFocus() {
8342
8373
  this.close();
8343
8374
  this.shadowRoot?.querySelector(".launcher")?.focus();
@@ -8493,4 +8524,4 @@ export {
8493
8524
  dompurify/dist/purify.es.mjs:
8494
8525
  (*! @license DOMPurify 3.4.11 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.11/LICENSE *)
8495
8526
  */
8496
- //# sourceMappingURL=chunk-LEKEIHJV.js.map
8527
+ //# sourceMappingURL=chunk-6TT3R555.js.map