@noodleseed/assistant 1.9.0 → 1.10.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
@@ -259,7 +259,7 @@ if (part.type === "data-view") {
259
259
  key={`${part.data.id}:${part.data.resourceUri}`}
260
260
  client={client}
261
261
  view={part.data}
262
- theme="dark"
262
+ theme={resolvedTheme}
263
263
  onError={(failure) => reportAssistantError(failure)}
264
264
  />
265
265
  );
@@ -269,7 +269,9 @@ if (part.type === "data-view") {
269
269
  `NoodleAppView` owns the double iframe and AppBridge. Its lifecycle identity is the supplied client plus
270
270
  `view.id` plus `view.resourceUri`: ordinary parent rerenders and fresh view/callback objects retain the
271
271
  iframe, while a semantic view replacement or unmount requests standard App teardown and closes the bridge.
272
- Do not also key an ancestor by the whole view object or a callback.
272
+ Do not also key an ancestor by the whole view object or a callback. Pass the embedding application's
273
+ resolved `"light"` or `"dark"` theme; later changes are published through MCP Apps host context without
274
+ replacing the iframe.
273
275
 
274
276
  Outside React, subscribe to the DOM-free AI SDK `UIMessage` state without registering a custom element or
275
277
  touching browser storage:
@@ -393,10 +395,19 @@ exact control over assistant-specific roles:
393
395
  ```tsx
394
396
  <NoodleAssistant
395
397
  sessionEndpoint="/api/assistant/session"
398
+ theme={resolvedTheme}
396
399
  appearance={{
397
400
  light: {
398
- panel: { surface: "#FFFFFF", text: "#101828", border: "#E4E7EC" },
399
- composer: { surface: "#F9FAFB", text: "#101828", border: "#D0D5DD" },
401
+ panel: {
402
+ surface: "var(--app-surface)",
403
+ text: "var(--app-text)",
404
+ border: "var(--app-border)",
405
+ },
406
+ composer: {
407
+ surface: "var(--app-input)",
408
+ text: "var(--app-text)",
409
+ border: "var(--app-border)",
410
+ },
400
411
  confirmation: { surface: "#F8FAFC", text: "#101828", border: "#CBD5E1" },
401
412
  primaryButton: { surface: "#635BFF", text: "#FFFFFF" },
402
413
  },
@@ -405,11 +416,38 @@ exact control over assistant-specific roles:
405
416
  />
406
417
  ```
407
418
 
408
- The same object is available as `element.appearance`. It covers canvas, panel, header, assistant/user
409
- messages, composer, suggestions, confirmation, primary/secondary buttons, launcher, code, and MCP App frame
410
- roles in light and dark modes. Exact colors are preserved; insufficient contrast emits the typed
411
- `assistant-appearance-warning` event. Stable public CSS variables remain available for stylesheet-based
412
- integration:
419
+ The same object is available as `element.appearance`. CSS custom properties inherit through the assistant
420
+ host into its shadow tree, so `var(--app-token)` references reuse the embedding application's existing
421
+ tokens without copying literal colors. Exact parseable literal colors are preserved and checked;
422
+ insufficient contrast emits the typed `assistant-appearance-warning` event. Contrast for unresolved CSS
423
+ references remains the host application's responsibility.
424
+
425
+ The complete typed appearance role map is:
426
+
427
+ | Appearance role | Public CSS custom properties |
428
+ | --- | --- |
429
+ | `canvas` | `--ns-assistant-canvas` |
430
+ | `text` | `--ns-assistant-text` |
431
+ | `mutedText` | `--ns-assistant-muted-text` |
432
+ | `link` | `--ns-assistant-link` |
433
+ | `focus` | `--ns-assistant-focus` |
434
+ | `success` | `--ns-assistant-success` |
435
+ | `warning` | `--ns-assistant-warning` |
436
+ | `danger` | `--ns-assistant-danger` |
437
+ | `panel` | `--ns-assistant-panel`, `--ns-assistant-panel-text`, `--ns-assistant-panel-border` |
438
+ | `header` | `--ns-assistant-header`, `--ns-assistant-header-text`, `--ns-assistant-header-border` |
439
+ | `assistantMessage` | `--ns-assistant-assistant-message`, `--ns-assistant-assistant-message-text`, `--ns-assistant-assistant-message-border` |
440
+ | `userMessage` | `--ns-assistant-user-message`, `--ns-assistant-user-message-text`, `--ns-assistant-user-message-border` |
441
+ | `composer` | `--ns-assistant-composer`, `--ns-assistant-composer-text`, `--ns-assistant-composer-border` |
442
+ | `suggestion` | `--ns-assistant-suggestion`, `--ns-assistant-suggestion-text`, `--ns-assistant-suggestion-border` |
443
+ | `confirmation` | `--ns-assistant-confirmation`, `--ns-assistant-confirmation-text`, `--ns-assistant-confirmation-border` |
444
+ | `primaryButton` | `--ns-assistant-primary-button`, `--ns-assistant-primary-button-text`, `--ns-assistant-primary-button-border` |
445
+ | `secondaryButton` | `--ns-assistant-secondary-button`, `--ns-assistant-secondary-button-text`, `--ns-assistant-secondary-button-border` |
446
+ | `launcher` | `--ns-assistant-launcher`, `--ns-assistant-launcher-text`, `--ns-assistant-launcher-border` |
447
+ | `code` | `--ns-assistant-code`, `--ns-assistant-code-text`, `--ns-assistant-code-border` |
448
+ | `app` | `--ns-assistant-app`, `--ns-assistant-app-text`, `--ns-assistant-app-border` |
449
+
450
+ Stable public CSS variables also remain available directly for stylesheet-based integration:
413
451
 
414
452
  ```css
415
453
  noodle-assistant {
@@ -427,6 +465,11 @@ defaults. The public slots are `launcher-icon`,
427
465
  embedding-page integration API, not a way to put HTML or callbacks in deployment configuration. Internal
428
466
  shadow-DOM selectors and classes are not public API.
429
467
 
468
+ `theme="auto"` follows the browser's operating-system color preference. When the embedding application has
469
+ its own theme toggle, pass its resolved `"light"` or `"dark"` value to `NoodleAssistant` and every
470
+ `NoodleAppView`. Updates change the assistant in place and notify mounted MCP Apps through standard host
471
+ context.
472
+
430
473
  Server branding controls customer name, themed logo/mark/avatar assets, semantic light/dark colors, density,
431
474
  radius, typography, and automatic theme. `embeddedAssistant(...)` controls floating/inline/drawer layout,
432
475
  position and dimensions, mobile and launcher/header/avatar/timestamp behavior, visible labels, suggested
@@ -3106,6 +3106,7 @@ function mountAssistantApp(detail, actions, options = {}) {
3106
3106
  let mountedTransport;
3107
3107
  let destroyed = false;
3108
3108
  let initialized = false;
3109
+ let hostContext = createAssistantAppHostContext(actions.theme);
3109
3110
  let destroyPromise;
3110
3111
  const renderTimer = setTimeout(() => {
3111
3112
  if (initialized || !frame.isConnected) return;
@@ -3132,7 +3133,7 @@ function mountAssistantApp(detail, actions, options = {}) {
3132
3133
  message: { text: {} }
3133
3134
  },
3134
3135
  {
3135
- hostContext: createAssistantAppHostContext(actions.theme)
3136
+ hostContext
3136
3137
  }
3137
3138
  );
3138
3139
  mountedBridge = bridge;
@@ -3207,6 +3208,7 @@ function mountAssistantApp(detail, actions, options = {}) {
3207
3208
  if (destroyed) return;
3208
3209
  initialized = true;
3209
3210
  clearTimeout(renderTimer);
3211
+ bridge.setHostContext(hostContext);
3210
3212
  const current = options.getDetail?.() ?? detail;
3211
3213
  void bridge.sendToolInput({ arguments: current.arguments ?? {} });
3212
3214
  void bridge.sendToolResult({
@@ -3222,6 +3224,11 @@ function mountAssistantApp(detail, actions, options = {}) {
3222
3224
  },
3223
3225
  { once: true }
3224
3226
  );
3227
+ const updateTheme = (theme) => {
3228
+ if (destroyed) return;
3229
+ hostContext = createAssistantAppHostContext(theme);
3230
+ if (initialized) mountedBridge?.setHostContext(hostContext);
3231
+ };
3225
3232
  const destroy = () => {
3226
3233
  if (destroyPromise) return destroyPromise;
3227
3234
  destroyed = true;
@@ -3240,9 +3247,12 @@ function mountAssistantApp(detail, actions, options = {}) {
3240
3247
  })();
3241
3248
  return destroyPromise;
3242
3249
  };
3243
- mountedApps.set(card, { destroy });
3250
+ mountedApps.set(card, { destroy, updateTheme });
3244
3251
  return card;
3245
3252
  }
3253
+ function updateAssistantAppTheme(card, theme) {
3254
+ mountedApps.get(card)?.updateTheme(theme);
3255
+ }
3246
3256
  function unmountAssistantApp(card) {
3247
3257
  return mountedApps.get(card)?.destroy() ?? Promise.resolve();
3248
3258
  }
@@ -7168,7 +7178,7 @@ function contrastWarnings(theme, mode) {
7168
7178
  for (const [role, foreground, background] of pairs) {
7169
7179
  if (!foreground || !background) continue;
7170
7180
  const ratio = contrastRatio2(foreground, background);
7171
- if (ratio >= 4.5) continue;
7181
+ if (ratio === void 0 || ratio >= 4.5) continue;
7172
7182
  warnings.push({
7173
7183
  code: "low_contrast",
7174
7184
  theme: mode,
@@ -7182,13 +7192,16 @@ function contrastWarnings(theme, mode) {
7182
7192
  return warnings;
7183
7193
  }
7184
7194
  function contrastRatio2(first, second) {
7185
- const lighter = Math.max(luminance(first), luminance(second));
7186
- const darker = Math.min(luminance(first), luminance(second));
7195
+ const firstLuminance = luminance(first);
7196
+ const secondLuminance = luminance(second);
7197
+ if (firstLuminance === void 0 || secondLuminance === void 0) return void 0;
7198
+ const lighter = Math.max(firstLuminance, secondLuminance);
7199
+ const darker = Math.min(firstLuminance, secondLuminance);
7187
7200
  return (lighter + 0.05) / (darker + 0.05);
7188
7201
  }
7189
7202
  function luminance(color) {
7190
7203
  const match = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
7191
- if (!match) return 1;
7204
+ if (!match) return void 0;
7192
7205
  const values = match.slice(1).map((value) => {
7193
7206
  const channel = Number.parseInt(value, 16) / 255;
7194
7207
  return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
@@ -8021,6 +8034,9 @@ var NoodleAssistantElement = class extends HTMLElementBase {
8021
8034
  })
8022
8035
  );
8023
8036
  }
8037
+ for (const app of this.shadowRoot?.querySelectorAll(".noodle-app-card") ?? []) {
8038
+ updateAssistantAppTheme(app, mode);
8039
+ }
8024
8040
  }
8025
8041
  #setSessionState(state) {
8026
8042
  this.#sessionState = state;
@@ -8196,6 +8212,7 @@ function registerNoodleAssistant() {
8196
8212
 
8197
8213
  export {
8198
8214
  mountAssistantApp,
8215
+ updateAssistantAppTheme,
8199
8216
  unmountAssistantApp,
8200
8217
  ASSISTANT_TAG_NAME,
8201
8218
  NoodleAssistantElement,
@@ -8206,4 +8223,4 @@ export {
8206
8223
  dompurify/dist/purify.es.mjs:
8207
8224
  (*! @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 *)
8208
8225
  */
8209
- //# sourceMappingURL=chunk-4B3XXGRP.js.map
8226
+ //# sourceMappingURL=chunk-RSNMY27B.js.map