@noodleseed/assistant 1.6.0 → 1.8.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.
Files changed (50) hide show
  1. package/README.md +78 -20
  2. package/dist/{chunk-UXPKSCID.js → chunk-3YYTUJPJ.js} +2 -2
  3. package/dist/{chunk-NSBPE2FW.js → chunk-5FUTL2UF.js} +6 -1
  4. package/dist/chunk-BLMJHF7U.js +544 -0
  5. package/dist/chunk-BLMJHF7U.js.map +1 -0
  6. package/dist/{chunk-ZKXOX67J.js → chunk-JMBUJSFP.js} +233 -87
  7. package/dist/chunk-JMBUJSFP.js.map +1 -0
  8. package/dist/chunk-RYQBXTLR.js +9089 -0
  9. package/dist/chunk-RYQBXTLR.js.map +1 -0
  10. package/dist/{client-9kup0-ws.d.cts → client-BxoNZkWp.d.cts} +50 -1
  11. package/dist/{client-BRjZMXlk.d.ts → client-ERQCAH_0.d.ts} +50 -1
  12. package/dist/client.cjs +22983 -94
  13. package/dist/client.cjs.map +1 -1
  14. package/dist/client.d.cts +2 -1
  15. package/dist/client.d.ts +2 -1
  16. package/dist/client.js +3 -2
  17. package/dist/index.cjs +10706 -2177
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +3 -2
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.js +4 -4
  22. package/dist/react/client.cjs +23688 -0
  23. package/dist/react/client.cjs.map +1 -0
  24. package/dist/react/client.d.cts +24 -0
  25. package/dist/react/client.d.ts +24 -0
  26. package/dist/react/client.js +76 -0
  27. package/dist/react/client.js.map +1 -0
  28. package/dist/react.cjs +10706 -2176
  29. package/dist/react.cjs.map +1 -1
  30. package/dist/react.d.cts +2 -1
  31. package/dist/react.d.ts +2 -1
  32. package/dist/react.js +7 -6
  33. package/dist/react.js.map +1 -1
  34. package/dist/server.cjs.map +1 -1
  35. package/dist/server.d.cts +3 -0
  36. package/dist/server.d.ts +3 -0
  37. package/dist/server.js +1 -1
  38. package/dist/server.js.map +1 -1
  39. package/dist/token-Y6KCR3JT.js +70 -0
  40. package/dist/token-Y6KCR3JT.js.map +1 -0
  41. package/dist/token-util-XFYMRPAB.js +6 -0
  42. package/dist/{v4-2UXPDMWJ.js → v4-KBRL3YLO.js} +3 -3
  43. package/dist/v4-KBRL3YLO.js.map +1 -0
  44. package/package.json +14 -3
  45. package/dist/chunk-GTYYQNGT.js +0 -708
  46. package/dist/chunk-GTYYQNGT.js.map +0 -1
  47. package/dist/chunk-ZKXOX67J.js.map +0 -1
  48. /package/dist/{chunk-UXPKSCID.js.map → chunk-3YYTUJPJ.js.map} +0 -0
  49. /package/dist/{chunk-NSBPE2FW.js.map → chunk-5FUTL2UF.js.map} +0 -0
  50. /package/dist/{v4-2UXPDMWJ.js.map → token-util-XFYMRPAB.js.map} +0 -0
@@ -2,7 +2,7 @@ import {
2
2
  AssistantClientError,
3
3
  copyAssistantModelContext,
4
4
  createAssistantClient
5
- } from "./chunk-GTYYQNGT.js";
5
+ } from "./chunk-RYQBXTLR.js";
6
6
  import {
7
7
  _enum,
8
8
  _null,
@@ -24,10 +24,10 @@ import {
24
24
  string,
25
25
  union,
26
26
  unknown
27
- } from "./chunk-UXPKSCID.js";
27
+ } from "./chunk-3YYTUJPJ.js";
28
28
  import {
29
29
  __require
30
- } from "./chunk-NSBPE2FW.js";
30
+ } from "./chunk-5FUTL2UF.js";
31
31
 
32
32
  // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
33
33
  var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
@@ -3022,10 +3022,17 @@ function GX(X2, Y) {
3022
3022
  // src/app-host.ts
3023
3023
  var PROXY_DOCUMENT = `<!doctype html><meta charset="utf-8"><style>html,body,iframe{border:0;margin:0;width:100%;height:100%;overflow:hidden}body{background:transparent}</style><script>
3024
3024
  let inner;
3025
+ const ready={jsonrpc:'2.0',method:'ui/notifications/sandbox-proxy-ready',params:{}};
3026
+ // Re-announce until the host acknowledges with the resource: when this document is hosted
3027
+ // cross-origin, the first announcement can arrive before the host's load handler is listening.
3028
+ const announce=setInterval(()=>parent.postMessage(ready,'*'),120);
3029
+ setTimeout(()=>clearInterval(announce),15000);
3025
3030
  addEventListener('message',(event)=>{
3026
3031
  if(event.source===parent){
3027
3032
  const message=event.data;
3028
3033
  if(message?.method==='ui/notifications/sandbox-resource-ready'){
3034
+ clearInterval(announce);
3035
+ if(inner)return;
3029
3036
  inner=document.createElement('iframe');
3030
3037
  inner.setAttribute('sandbox',message.params?.sandbox||'allow-scripts');
3031
3038
  inner.setAttribute('referrerpolicy','no-referrer');
@@ -3038,7 +3045,7 @@ addEventListener('message',(event)=>{
3038
3045
  parent.postMessage(event.data,'*');
3039
3046
  }
3040
3047
  });
3041
- parent.postMessage({jsonrpc:'2.0',method:'ui/notifications/sandbox-proxy-ready',params:{}},'*');
3048
+ parent.postMessage(ready,'*');
3042
3049
  </script>`;
3043
3050
  function isAllowedAppLink(value, allowedDomains, baseUrl) {
3044
3051
  try {
@@ -3056,7 +3063,25 @@ function isAllowedAppLink(value, allowedDomains, baseUrl) {
3056
3063
  return void 0;
3057
3064
  }
3058
3065
  }
3059
- function mountAssistantApp(detail, actions) {
3066
+ function createAssistantAppHostContext(theme) {
3067
+ return {
3068
+ theme,
3069
+ displayMode: "inline",
3070
+ availableDisplayModes: ["inline", "fullscreen"],
3071
+ platform: "web"
3072
+ };
3073
+ }
3074
+ var APP_RENDER_TIMEOUT_MS = 1e4;
3075
+ function resolveSandboxUrl(value) {
3076
+ if (!value) return void 0;
3077
+ try {
3078
+ const parsed = new URL(value);
3079
+ return parsed.protocol === "https:" || parsed.protocol === "http:" ? parsed.href : void 0;
3080
+ } catch {
3081
+ return void 0;
3082
+ }
3083
+ }
3084
+ function mountAssistantApp(detail, actions, options = {}) {
3060
3085
  if (!detail.html) return void 0;
3061
3086
  const card = document.createElement("section");
3062
3087
  card.className = "noodle-app-card";
@@ -3071,8 +3096,19 @@ function mountAssistantApp(detail, actions) {
3071
3096
  frame.title = detail.title ?? detail.tool;
3072
3097
  frame.setAttribute("sandbox", "allow-scripts");
3073
3098
  frame.setAttribute("referrerpolicy", "no-referrer");
3074
- frame.srcdoc = PROXY_DOCUMENT;
3099
+ const sandboxUrl = resolveSandboxUrl(options.sandboxUrl);
3100
+ if (sandboxUrl) frame.src = sandboxUrl;
3101
+ else frame.srcdoc = PROXY_DOCUMENT;
3075
3102
  card.append(frame);
3103
+ let initialized = false;
3104
+ const renderTimer = setTimeout(() => {
3105
+ if (initialized || !frame.isConnected) return;
3106
+ const note = document.createElement("div");
3107
+ note.className = "noodle-app-fallback";
3108
+ note.textContent = "This app view could not be displayed.";
3109
+ frame.replaceWith(note);
3110
+ options.onRenderFailure?.({ code: "view_render_timeout", retryable: false });
3111
+ }, APP_RENDER_TIMEOUT_MS);
3076
3112
  frame.addEventListener(
3077
3113
  "load",
3078
3114
  () => {
@@ -3089,11 +3125,7 @@ function mountAssistantApp(detail, actions) {
3089
3125
  message: { text: {} }
3090
3126
  },
3091
3127
  {
3092
- hostContext: {
3093
- displayMode: "inline",
3094
- availableDisplayModes: ["inline", "fullscreen"],
3095
- platform: "web"
3096
- }
3128
+ hostContext: createAssistantAppHostContext(actions.theme)
3097
3129
  }
3098
3130
  );
3099
3131
  bridge.onsandboxready = () => {
@@ -3138,13 +3170,18 @@ function mountAssistantApp(detail, actions) {
3138
3170
  bridge.onlistresources = async (params) => await actions.client.requestApp("resources/list", params ?? {});
3139
3171
  bridge.onreadresource = async (params) => await actions.client.requestApp("resources/read", params);
3140
3172
  bridge.oninitialized = () => {
3173
+ initialized = true;
3174
+ clearTimeout(renderTimer);
3141
3175
  void bridge.sendToolInput({ arguments: detail.arguments ?? {} });
3142
3176
  void bridge.sendToolResult({
3143
3177
  content: [{ type: "text", text: JSON.stringify(detail.result) }],
3144
3178
  ...typeof detail.result === "object" && detail.result !== null && !Array.isArray(detail.result) ? { structuredContent: detail.result } : {}
3145
3179
  });
3146
3180
  };
3147
- void bridge.connect(new E(target, target)).catch(() => card.remove());
3181
+ void bridge.connect(new E(target, target)).catch(() => {
3182
+ clearTimeout(renderTimer);
3183
+ card.remove();
3184
+ });
3148
3185
  },
3149
3186
  { once: true }
3150
3187
  );
@@ -3158,10 +3195,10 @@ var common = {
3158
3195
  baseFontSize: 15,
3159
3196
  messageFontSize: 15,
3160
3197
  lineHeight: 1.5,
3161
- panelRadius: 20,
3162
- cardRadius: 14,
3163
- inputRadius: 14,
3164
- buttonRadius: 12,
3198
+ panelRadius: 24,
3199
+ cardRadius: 16,
3200
+ inputRadius: 999,
3201
+ buttonRadius: 999,
3165
3202
  launcherRadius: 999,
3166
3203
  spacing: 8,
3167
3204
  backdropBlur: 18,
@@ -3268,7 +3305,7 @@ var DEFAULT_APPEARANCE = {
3268
3305
  },
3269
3306
  presentation: {
3270
3307
  panel: {
3271
- surface: "solid",
3308
+ surface: "glass",
3272
3309
  elevation: "soft",
3273
3310
  border: "subtle"
3274
3311
  },
@@ -6575,8 +6612,7 @@ var AssistantElementEventController = class {
6575
6612
  if (name === "message_started") {
6576
6613
  this.#startStream("message");
6577
6614
  const body = this.#host.appendMessage("user", String(data.message ?? ""));
6578
- const messages = this.#host.messages();
6579
- if (body && messages) this.#revealLatestMessage(messages);
6615
+ if (body) this.#host.revealLatest();
6580
6616
  this.#host.element.dispatchEvent(new CustomEvent("assistant-message-started"));
6581
6617
  return;
6582
6618
  }
@@ -6642,6 +6678,7 @@ var AssistantElementEventController = class {
6642
6678
  this.#clearStream();
6643
6679
  this.#completedTool = void 0;
6644
6680
  this.#host.element.removeAttribute("has-messages");
6681
+ this.#host.revealLatest();
6645
6682
  this.#host.element.dispatchEvent(new CustomEvent("assistant-session-reset"));
6646
6683
  }
6647
6684
  #finishInteraction(id, action) {
@@ -6652,6 +6689,7 @@ var AssistantElementEventController = class {
6652
6689
  messages.append(
6653
6690
  createResultCard(this.#completedTool.result, labels.completed, labels.redacted)
6654
6691
  );
6692
+ this.#host.revealLatest();
6655
6693
  }
6656
6694
  }
6657
6695
  this.#proposalCards.get(id)?.remove();
@@ -6689,7 +6727,7 @@ var AssistantElementEventController = class {
6689
6727
  });
6690
6728
  messages.append(card);
6691
6729
  this.#proposalCards.set(id, card);
6692
- this.#revealLatestMessage(messages);
6730
+ this.#host.revealLatest();
6693
6731
  this.#host.element.dispatchEvent(
6694
6732
  new CustomEvent("assistant-tool-proposed", {
6695
6733
  detail: { id, tool, title, description, arguments: arguments_, reviewSchema }
@@ -6713,7 +6751,7 @@ var AssistantElementEventController = class {
6713
6751
  });
6714
6752
  messages.append(card);
6715
6753
  this.#proposalCards.set(id, card);
6716
- this.#revealLatestMessage(messages);
6754
+ this.#host.revealLatest();
6717
6755
  this.#host.element.dispatchEvent(
6718
6756
  new CustomEvent("assistant-input-requested", {
6719
6757
  detail: { id, message, requestedSchema }
@@ -6726,6 +6764,7 @@ var AssistantElementEventController = class {
6726
6764
  this.#streamKind = kind;
6727
6765
  }
6728
6766
  #clearStream() {
6767
+ this.#assistantBody?.parentElement?.classList.remove("streaming");
6729
6768
  this.#assistantBody = void 0;
6730
6769
  this.#assistantText = "";
6731
6770
  this.#streamKind = void 0;
@@ -6738,20 +6777,10 @@ var AssistantElementEventController = class {
6738
6777
  const body = this.#assistantBody ?? this.#host.appendMessage("assistant", "");
6739
6778
  this.#assistantBody = body;
6740
6779
  if (!body) return;
6780
+ body.parentElement?.classList.add("streaming");
6741
6781
  body.innerHTML = html2;
6742
- const messages = this.#host.messages();
6743
- if (messages) this.#revealLatestMessage(messages);
6744
- }
6745
- #revealLatestMessage(messages) {
6746
6782
  this.#host.element.setAttribute("has-messages", "");
6747
- const distance = messages.scrollHeight - messages.scrollTop - messages.clientHeight;
6748
- const control = this.#host.element.shadowRoot?.querySelector(".new-messages");
6749
- if (distance <= 160) {
6750
- messages.scrollTop = messages.scrollHeight;
6751
- if (control) control.hidden = true;
6752
- } else if (control) {
6753
- control.hidden = false;
6754
- }
6783
+ this.#host.revealLatest();
6755
6784
  }
6756
6785
  };
6757
6786
  function isRecord(value) {
@@ -6776,6 +6805,107 @@ function queryRequired(root, selector) {
6776
6805
  return element;
6777
6806
  }
6778
6807
 
6808
+ // src/element-scroll-controller.ts
6809
+ var AssistantElementScrollController = class {
6810
+ #region;
6811
+ #messages;
6812
+ #control;
6813
+ #mutationObserver;
6814
+ #resizeObserver;
6815
+ #syncScheduled = false;
6816
+ #followingLatest = true;
6817
+ #lastScrollTop = 0;
6818
+ connect(elements) {
6819
+ this.disconnect();
6820
+ this.#region = elements.region;
6821
+ this.#messages = elements.messages;
6822
+ this.#control = elements.control;
6823
+ this.#lastScrollTop = elements.messages.scrollTop;
6824
+ this.#followingLatest = this.#distanceFromBottom() <= 30;
6825
+ elements.messages.addEventListener("scroll", this.#handleScroll, { passive: true });
6826
+ elements.control.addEventListener("click", this.#handleControlClick);
6827
+ if (globalThis.MutationObserver) {
6828
+ this.#mutationObserver = new MutationObserver(() => this.#scheduleContentSync());
6829
+ this.#mutationObserver.observe(elements.messages, {
6830
+ childList: true,
6831
+ subtree: true,
6832
+ attributes: true,
6833
+ attributeFilter: ["style"]
6834
+ });
6835
+ }
6836
+ if (globalThis.ResizeObserver) {
6837
+ this.#resizeObserver = new ResizeObserver(() => this.#scheduleContentSync());
6838
+ this.#resizeObserver.observe(elements.messages);
6839
+ }
6840
+ this.sync();
6841
+ }
6842
+ disconnect() {
6843
+ this.#messages?.removeEventListener("scroll", this.#handleScroll);
6844
+ this.#control?.removeEventListener("click", this.#handleControlClick);
6845
+ this.#mutationObserver?.disconnect();
6846
+ this.#resizeObserver?.disconnect();
6847
+ this.#region?.removeAttribute("data-can-scroll-up");
6848
+ this.#region?.removeAttribute("data-can-scroll-down");
6849
+ this.#region = void 0;
6850
+ this.#messages = void 0;
6851
+ this.#control = void 0;
6852
+ this.#mutationObserver = void 0;
6853
+ this.#resizeObserver = void 0;
6854
+ this.#syncScheduled = false;
6855
+ this.#followingLatest = true;
6856
+ this.#lastScrollTop = 0;
6857
+ }
6858
+ revealLatest() {
6859
+ if (!this.#messages) return;
6860
+ if (this.#followingLatest || this.#distanceFromBottom() <= 160) {
6861
+ this.scrollToBottom();
6862
+ return;
6863
+ }
6864
+ this.sync();
6865
+ }
6866
+ scrollToBottom() {
6867
+ if (!this.#messages) return;
6868
+ this.#followingLatest = true;
6869
+ this.#messages.scrollTop = this.#messages.scrollHeight;
6870
+ this.sync();
6871
+ }
6872
+ sync() {
6873
+ if (!this.#messages || !this.#region || !this.#control) return;
6874
+ const distance = this.#distanceFromBottom();
6875
+ this.#region.toggleAttribute("data-can-scroll-up", this.#messages.scrollTop > 20);
6876
+ this.#region.toggleAttribute("data-can-scroll-down", distance > 20);
6877
+ this.#control.hidden = distance <= 100;
6878
+ }
6879
+ #handleScroll = () => {
6880
+ if (!this.#messages) return;
6881
+ const distance = this.#distanceFromBottom();
6882
+ const movedUp = this.#messages.scrollTop < this.#lastScrollTop - 5;
6883
+ this.#lastScrollTop = this.#messages.scrollTop;
6884
+ if (movedUp && distance > 50) this.#followingLatest = false;
6885
+ if (distance < 30) this.#followingLatest = true;
6886
+ this.sync();
6887
+ };
6888
+ #handleControlClick = () => {
6889
+ this.scrollToBottom();
6890
+ };
6891
+ #distanceFromBottom() {
6892
+ if (!this.#messages) return 0;
6893
+ return this.#messages.scrollHeight - this.#messages.scrollTop - this.#messages.clientHeight;
6894
+ }
6895
+ #scheduleContentSync() {
6896
+ if (this.#syncScheduled) return;
6897
+ this.#syncScheduled = true;
6898
+ queueMicrotask(() => {
6899
+ this.#syncScheduled = false;
6900
+ if (this.#followingLatest) {
6901
+ this.scrollToBottom();
6902
+ } else {
6903
+ this.sync();
6904
+ }
6905
+ });
6906
+ }
6907
+ };
6908
+
6779
6909
  // src/element-styles.ts
6780
6910
  var ASSISTANT_ELEMENT_STYLES = `<style>
6781
6911
  *, *::before, *::after { box-sizing: border-box; }
@@ -6789,40 +6919,48 @@ var ASSISTANT_ELEMENT_STYLES = `<style>
6789
6919
  .launcher { display: grid; place-items: center; width: 48px; height: 48px; border: 1px solid var(--ns-assistant-launcher-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: 999px; color: var(--ns-assistant-launcher-text, var(--ns-assistant-text, var(--ns-assistant-default-text))); background: var(--ns-assistant-launcher, var(--ns-assistant-panel, var(--ns-assistant-default-panel))); box-shadow: 0 8px 28px rgba(0,0,0,.14); cursor: pointer; transition: transform .24s cubic-bezier(.25,1,.5,1), box-shadow .2s ease; }
6790
6920
  .launcher:hover { transform: translateY(-2px) scale(1.03); box-shadow: 0 8px 26px rgba(0,0,0,.16); }
6791
6921
  .launcher img { width: 28px; height: 28px; border-radius: 50%; object-fit: contain; }
6792
- .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; animation: halo-panel-in .28s cubic-bezier(.23,1,.32,1) both; }
6922
+ .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; }
6793
6923
  :host([open]) .panel { display: flex; }
6794
6924
  .panel { position: relative; }
6795
6925
  :host([open]) .launcher { display: none; }
6796
- 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 var(--ns-assistant-header-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); }
6797
- header strong { flex: 1; font-size: 16px; font-weight: 600; letter-spacing: -.01em; }
6926
+ 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); }
6927
+ header strong { flex: 1; font-size: 16px; font-weight: 600; letter-spacing: 0; }
6798
6928
  .brand-logo { display: block; max-width: 120px; max-height: 28px; object-fit: contain; }
6799
- .close, .send { display: grid; place-items: center; border: 0; border-radius: 999px; cursor: pointer; }
6800
- .close { width: 44px; height: 44px; padding: 0; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); background: transparent; font-size: 24px; font-weight: 300; line-height: 1; transition: background .15s ease, color .15s ease; }
6929
+ .close, .send { display: grid; place-items: center; border: 0; border-radius: var(--ns-assistant-button-radius, var(--ns-assistant-default-button-radius)); cursor: pointer; }
6930
+ .close { position: relative; width: 44px; height: 44px; padding: 0; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); background: transparent; transition: background .15s ease, color .15s ease, transform .15s ease; }
6931
+ .close::before { content: ''; width: 18px; height: 18px; background: currentColor; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 6 12 12M18 6 6 18' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 6 12 12M18 6 6 18' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat; }
6801
6932
  .close:hover { color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: var(--ns-assistant-elevated, var(--ns-assistant-default-elevated)); }
6933
+ .close:active { transform: scale(.96); }
6802
6934
  .welcome { flex: 0 0 auto; padding: 30px 22px 18px; }
6803
- .welcome h2 { margin: 0; font-size: 1.42em; line-height: 1.25; letter-spacing: -.025em; }
6935
+ .welcome h2 { margin: 0; font-size: 1.35em; line-height: 1.25; letter-spacing: 0; }
6804
6936
  .welcome p { max-width: 42ch; margin: 7px 0 0; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); }
6805
6937
  :host([has-messages]) .welcome, :host([has-messages]) .suggested-prompts { display: none; }
6806
6938
  .suggested-prompts { display: flex; flex: 0 0 auto; flex-wrap: wrap; gap: 8px; padding: 0 20px 16px; }
6807
6939
  .suggested-prompts:empty { display: none; }
6808
- .suggested-prompts button { min-height: 44px; padding: 9px 14px; color: var(--ns-assistant-suggestion-text, var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text))); background: var(--ns-assistant-suggestion, transparent); border: 1px solid var(--ns-assistant-suggestion-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: 12px; cursor: pointer; transition: background .2s ease, color .2s ease, transform .2s ease; }
6940
+ .suggested-prompts button { min-height: 44px; padding: 8px 14px; color: var(--ns-assistant-suggestion-text, var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text))); background: var(--ns-assistant-suggestion, color-mix(in srgb, var(--ns-assistant-input, var(--ns-assistant-default-input)) 58%, transparent)); border: 1px solid var(--ns-assistant-suggestion-border, color-mix(in srgb, var(--ns-assistant-divider, var(--ns-assistant-default-divider)) 82%, transparent)); border-radius: 999px; cursor: pointer; font-size: .86em; transition: background .2s ease, color .2s ease, border-color .2s ease, transform .2s ease; }
6809
6941
  .suggested-prompts button:hover { color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: var(--ns-assistant-elevated, var(--ns-assistant-default-elevated)); transform: translateY(-1px); }
6810
- .messages { flex: 1 1 auto; min-height: 0; padding: 18px 20px; overflow-y: auto; background: var(--ns-assistant-canvas, transparent); overscroll-behavior: contain; scroll-behavior: smooth; }
6942
+ .messages-region { position: relative; display: flex; flex: 1 1 auto; min-height: 0; overflow: hidden; }
6943
+ .messages-region::before, .messages-region::after { content: ''; position: absolute; right: 0; left: 0; z-index: 1; height: 18px; pointer-events: none; opacity: 0; transition: opacity .18s ease; }
6944
+ .messages-region::before { top: 0; background: linear-gradient(to bottom, color-mix(in srgb, var(--ns-assistant-panel, var(--ns-assistant-default-panel)) 92%, transparent), transparent); }
6945
+ .messages-region::after { bottom: 0; background: linear-gradient(to top, color-mix(in srgb, var(--ns-assistant-panel, var(--ns-assistant-default-panel)) 92%, transparent), transparent); }
6946
+ .messages-region[data-can-scroll-up]::before, .messages-region[data-can-scroll-down]::after { opacity: 1; }
6947
+ .messages { flex: 1 1 auto; width: 100%; min-height: 0; padding: 18px 20px 24px; overflow-y: auto; background: var(--ns-assistant-canvas, transparent); overscroll-behavior: contain; scroll-behavior: smooth; }
6811
6948
  .messages::-webkit-scrollbar { width: 6px; }
6812
6949
  .messages::-webkit-scrollbar-thumb { background: var(--ns-assistant-divider, var(--ns-assistant-default-divider)); border-radius: 999px; }
6813
6950
  .message { width: fit-content; max-width: 88%; margin: 0 0 14px; padding: 0; border-radius: var(--ns-assistant-card-radius, var(--ns-assistant-default-card-radius)); background: transparent; white-space: pre-wrap; animation: halo-message-in .2s ease both; }
6814
6951
  .message > span, .message > .markdown { overflow-wrap: anywhere; }
6815
6952
  .message .avatar { float: left; width: 24px; height: 24px; margin-right: 9px; border-radius: 50%; object-fit: cover; }
6816
6953
  .message time { display: block; margin-top: 4px; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); font-size: .75em; line-height: 1.2; }
6817
- .message-copy { min-width: 44px; min-height: 32px; margin-top: 6px; padding: 4px 8px; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); background: transparent; border: 0; border-radius: 8px; cursor: pointer; font-size: .78em; opacity: 0; transition: opacity .15s ease, background .15s ease; }
6954
+ .message-copy { min-width: 44px; min-height: 32px; margin-top: 6px; padding: 4px 10px; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); background: transparent; border: 0; border-radius: var(--ns-assistant-button-radius, var(--ns-assistant-default-button-radius)); cursor: pointer; font-size: .78em; opacity: 0; transition: opacity .15s ease, background .15s ease; }
6818
6955
  .message:hover .message-copy, .message:focus-within .message-copy { opacity: 1; }
6819
6956
  .conversation-status { margin: 4px auto 14px; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); font-size: .82em; text-align: center; }
6820
- .conversation-error { display: flex; gap: 12px; align-items: center; justify-content: space-between; margin: 8px 0 16px; padding: 12px 14px; color: var(--ns-assistant-danger, var(--ns-assistant-default-danger)); border: 1px solid color-mix(in srgb, var(--ns-assistant-danger, var(--ns-assistant-default-danger)) 28%, transparent); border-radius: 12px; background: color-mix(in srgb, var(--ns-assistant-danger, var(--ns-assistant-default-danger)) 7%, transparent); }
6957
+ .conversation-error { display: flex; gap: 12px; align-items: center; justify-content: space-between; margin: 8px 0 16px; padding: 12px 14px; color: var(--ns-assistant-danger, var(--ns-assistant-default-danger)); border: 1px solid color-mix(in srgb, var(--ns-assistant-danger, var(--ns-assistant-default-danger)) 28%, transparent); border-radius: var(--ns-assistant-card-radius, var(--ns-assistant-default-card-radius)); background: color-mix(in srgb, var(--ns-assistant-danger, var(--ns-assistant-default-danger)) 7%, transparent); }
6821
6958
  .conversation-error p { margin: 0; }
6822
- .conversation-error button { min-height: 44px; padding: 8px 12px; color: var(--ns-assistant-secondary-button-text, inherit); background: var(--ns-assistant-secondary-button, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); border: 1px solid var(--ns-assistant-secondary-button-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: 10px; cursor: pointer; font-weight: 600; }
6823
- .new-messages { position: absolute; left: 50%; bottom: 82px; z-index: 1; min-height: 36px; padding: 7px 12px; color: var(--ns-assistant-secondary-button-text, inherit); background: var(--ns-assistant-secondary-button, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); border: 1px solid var(--ns-assistant-secondary-button-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: 999px; box-shadow: 0 4px 18px rgba(0,0,0,.12); transform: translateX(-50%); cursor: pointer; }
6824
- .message.assistant { color: var(--ns-assistant-assistant-message-text, inherit); background: var(--ns-assistant-assistant-message, transparent); border: 1px solid var(--ns-assistant-assistant-message-border, transparent); }
6825
- .message.user { display: grid; justify-items: start; gap: 3px; margin-left: auto; padding: 10px 16px 8px; color: var(--ns-assistant-user-message-text, var(--ns-assistant-text, var(--ns-assistant-default-text))); background: var(--ns-assistant-user-message, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); border: 1px solid var(--ns-assistant-user-message-border, transparent); font-size: .94em; }
6959
+ .conversation-error button { min-height: 44px; padding: 8px 14px; color: var(--ns-assistant-secondary-button-text, inherit); background: var(--ns-assistant-secondary-button, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); border: 1px solid var(--ns-assistant-secondary-button-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: var(--ns-assistant-button-radius, var(--ns-assistant-default-button-radius)); cursor: pointer; font-weight: 600; }
6960
+ .new-messages { position: absolute; left: 50%; bottom: 14px; z-index: 2; width: 44px; height: 44px; padding: 0; color: var(--ns-assistant-secondary-button-text, var(--ns-assistant-text, var(--ns-assistant-default-text))); background: var(--ns-assistant-secondary-button, color-mix(in srgb, var(--ns-assistant-input, var(--ns-assistant-default-input)) 92%, transparent)); border: 1px solid var(--ns-assistant-secondary-button-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: var(--ns-assistant-button-radius, var(--ns-assistant-default-button-radius)); box-shadow: 0 8px 24px rgba(0,0,0,.12); transform: translateX(-50%); cursor: pointer; }
6961
+ .new-messages::before { content: ''; display: block; width: 18px; height: 18px; margin: auto; background: currentColor; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 9 6 6 6-6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 9 6 6 6-6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; }
6962
+ .message.assistant { max-width: 100%; color: var(--ns-assistant-assistant-message-text, inherit); background: var(--ns-assistant-assistant-message, transparent); border: 1px solid var(--ns-assistant-assistant-message-border, transparent); }
6963
+ .message.user { display: grid; max-width: 85%; justify-items: start; gap: 3px; margin-left: auto; padding: 10px 16px 8px; color: var(--ns-assistant-user-message-text, var(--ns-assistant-text, var(--ns-assistant-default-text))); background: var(--ns-assistant-user-message, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); border: 1px solid var(--ns-assistant-user-message-border, transparent); border-radius: var(--ns-assistant-card-radius, var(--ns-assistant-default-card-radius)); font-size: .94em; }
6826
6964
  .message.user time { margin-top: 0; }
6827
6965
  .markdown { white-space: normal; }
6828
6966
  .markdown p { margin: 0 0 8px; }
@@ -6837,13 +6975,14 @@ var ASSISTANT_ELEMENT_STYLES = `<style>
6837
6975
  .markdown ul, .markdown ol { margin: 8px 0; padding-left: 22px; }
6838
6976
  .markdown li { margin: 3px 0; }
6839
6977
  .markdown blockquote { margin: 10px 0; padding-left: 12px; border-left: 3px solid var(--ns-assistant-divider, var(--ns-assistant-default-divider)); color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); }
6840
- .markdown h1, .markdown h2, .markdown h3, .markdown h4, .markdown h5, .markdown h6 { margin: 14px 0 7px; line-height: 1.25; letter-spacing: -.015em; }
6978
+ .markdown h1, .markdown h2, .markdown h3, .markdown h4, .markdown h5, .markdown h6 { margin: 14px 0 7px; line-height: 1.25; letter-spacing: 0; }
6841
6979
  .markdown h1:first-child, .markdown h2:first-child, .markdown h3:first-child { margin-top: 0; }
6842
6980
  .markdown h1 { font-size: 1.3em; } .markdown h2 { font-size: 1.18em; } .markdown h3 { font-size: 1.08em; }
6981
+ .message.assistant.streaming .markdown::after { content: ''; display: inline-block; width: 2px; height: 1em; margin-left: 3px; border-radius: 999px; background: var(--ns-assistant-accent, var(--ns-assistant-default-accent)); vertical-align: -.14em; animation: halo-caret-blink 1s steps(2, start) infinite; }
6843
6982
  .message.thinking > span { display: inline-block; color: transparent; background: linear-gradient(90deg, var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)) 25%, var(--ns-assistant-text, var(--ns-assistant-default-text)) 50%, var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)) 75%); background-size: 300% 100%; background-clip: text; -webkit-background-clip: text; font-style: italic; font-weight: 500; animation: halo-shimmer 2s linear infinite; }
6844
6983
  .tool-proposal { display: grid; gap: 12px; align-items: start; margin: 8px 0 16px; padding: 18px; color: var(--ns-assistant-confirmation-text, inherit); border: 1px solid var(--ns-assistant-confirmation-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: var(--ns-assistant-card-radius, 14px); background: var(--ns-assistant-confirmation, var(--ns-assistant-input, var(--ns-assistant-default-input))); animation: halo-message-in .2s ease both; }
6845
6984
  .proposal-eyebrow { color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); font-size: .75em; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
6846
- .tool-proposal h3 { margin: -4px 0 0; font-size: 1.08em; line-height: 1.3; letter-spacing: -.01em; }
6985
+ .tool-proposal h3 { margin: -4px 0 0; font-size: 1.08em; line-height: 1.3; letter-spacing: 0; }
6847
6986
  .proposal-description { margin: -4px 0 0; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); }
6848
6987
  .proposal-arguments { display: grid; grid-template-columns: minmax(100px, .7fr) minmax(0, 1.3fr); gap: 8px 14px; margin: 0; padding: 12px 0; border-block: 1px solid var(--ns-assistant-divider, var(--ns-assistant-default-divider)); font-size: .9em; }
6849
6988
  .proposal-arguments .proposal-arguments { grid-column: 1 / -1; padding: 4px 0 0; border: 0; }
@@ -6854,7 +6993,7 @@ var ASSISTANT_ELEMENT_STYLES = `<style>
6854
6993
  .proposal-details summary { min-height: 36px; padding: 8px 0; cursor: pointer; font-weight: 600; }
6855
6994
  .proposal-details .proposal-arguments { margin-top: 4px; }
6856
6995
  .proposal-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: end; }
6857
- .tool-proposal button { min-height: 44px; padding: 9px 14px; border: 1px solid var(--ns-assistant-secondary-button-border, transparent); border-radius: var(--ns-assistant-button-radius, 12px); color: var(--ns-assistant-secondary-button-text, var(--ns-assistant-text, var(--ns-assistant-default-text))); background: var(--ns-assistant-secondary-button, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); cursor: pointer; font-weight: 600; }
6996
+ .tool-proposal button { min-height: 44px; padding: 9px 16px; border: 1px solid var(--ns-assistant-secondary-button-border, transparent); border-radius: var(--ns-assistant-button-radius, var(--ns-assistant-default-button-radius)); color: var(--ns-assistant-secondary-button-text, var(--ns-assistant-text, var(--ns-assistant-default-text))); background: var(--ns-assistant-secondary-button, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); cursor: pointer; font-weight: 600; }
6858
6997
  .tool-proposal .proposal-accept { color: var(--ns-assistant-primary-button-text, var(--ns-assistant-accent-text, var(--ns-assistant-default-accent-text))); background: var(--ns-assistant-primary-button, var(--ns-assistant-accent, var(--ns-assistant-default-accent))); border-color: var(--ns-assistant-primary-button-border, transparent); }
6859
6998
  .tool-proposal button:disabled { opacity: .55; cursor: not-allowed; }
6860
6999
  .tool-proposal button { min-height: 45px; }
@@ -6863,25 +7002,26 @@ var ASSISTANT_ELEMENT_STYLES = `<style>
6863
7002
  .input-request form { display: grid; gap: 10px; align-items: stretch; min-height: 0; margin: 0; padding: 0; border: 0; border-radius: 0; background: transparent; box-shadow: none; }
6864
7003
  .input-request form:focus-within { box-shadow: none; }
6865
7004
  .input-request label { display: grid; gap: 4px; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); font-size: .9em; }
6866
- .input-request input, .input-request select { width: 100%; padding: 8px 10px; color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: var(--ns-assistant-input, var(--ns-assistant-default-input)); border: 1px solid var(--ns-assistant-divider, var(--ns-assistant-default-divider)); border-radius: var(--ns-assistant-input-radius, var(--ns-assistant-default-input-radius)); outline: 0; font: inherit; }
6867
- .input-request select[multiple] { min-height: 88px; }
6868
- .input-request input[type="checkbox"] { width: auto; justify-self: start; accent-color: var(--ns-assistant-accent, var(--ns-assistant-default-accent)); }
7005
+ .input-request input, .input-request select { width: 100%; min-height: 44px; padding: 8px 14px; color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: var(--ns-assistant-input, var(--ns-assistant-default-input)); border: 1px solid var(--ns-assistant-divider, var(--ns-assistant-default-divider)); border-radius: var(--ns-assistant-input-radius, var(--ns-assistant-default-input-radius)); outline: 0; font: inherit; }
7006
+ .input-request select[multiple] { min-height: 88px; border-radius: var(--ns-assistant-card-radius, var(--ns-assistant-default-card-radius)); }
7007
+ .input-request input[type="checkbox"] { width: auto; min-height: auto; justify-self: start; accent-color: var(--ns-assistant-accent, var(--ns-assistant-default-accent)); }
6869
7008
  .input-request input:focus-visible, .input-request select:focus-visible { border-color: var(--ns-assistant-focus, var(--ns-assistant-default-focus)); box-shadow: 0 0 0 2px var(--ns-assistant-focus, var(--ns-assistant-default-focus)); }
6870
7009
  .input-request .proposal-actions { margin-top: 2px; }
6871
7010
  .noodle-app-card { position: relative; display: grid; gap: 8px; width: 100%; margin: 4px 0 16px; overflow: hidden; color: var(--ns-assistant-app-text, inherit); border: 1px solid var(--ns-assistant-app-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: var(--ns-assistant-card-radius, var(--ns-assistant-default-card-radius)); background: var(--ns-assistant-app, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); }
6872
7011
  .noodle-app-title { padding: 10px 12px 0; font-size: .86em; font-weight: 650; }
6873
7012
  .noodle-app-frame { display: block; width: 100%; min-height: 120px; border: 0; background: transparent; }
7013
+ .noodle-app-fallback { padding: 14px 16px; font-size: 13px; color: var(--ns-assistant-muted, var(--ns-assistant-default-muted, #6b7280)); }
6874
7014
  .noodle-app-card[data-fullscreen] { position: fixed; inset: 0; z-index: 2; margin: 0; border-radius: 0; background: var(--ns-assistant-panel, var(--ns-assistant-default-panel)); }
6875
7015
  .noodle-app-card[data-fullscreen] .noodle-app-frame { height: 100% !important; }
6876
- form { display: flex; flex: 0 0 auto; gap: 8px; align-items: center; min-height: 56px; margin: 8px 16px 16px; padding: 6px 6px 6px 16px; color: var(--ns-assistant-composer-text, inherit); border: 1px solid var(--ns-assistant-composer-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: var(--ns-assistant-input-radius, 14px); background: var(--ns-assistant-composer, var(--ns-assistant-input, var(--ns-assistant-default-input))); box-shadow: 0 1px 2px rgba(0,0,0,.04); transition: border-color .15s ease, box-shadow .15s ease; }
7016
+ form { display: flex; flex: 0 0 auto; gap: 8px; align-items: center; min-height: 56px; margin: 8px 16px 16px; padding: 6px 6px 6px 16px; color: var(--ns-assistant-composer-text, inherit); border: 1px solid var(--ns-assistant-composer-border, color-mix(in srgb, var(--ns-assistant-divider, var(--ns-assistant-default-divider)) 86%, transparent)); border-radius: var(--ns-assistant-input-radius, var(--ns-assistant-default-input-radius)); background: var(--ns-assistant-composer, color-mix(in srgb, var(--ns-assistant-input, var(--ns-assistant-default-input)) 92%, transparent)); box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 8px 24px rgba(0,0,0,.035); transition: border-color .15s ease, box-shadow .15s ease; }
6877
7017
  form:focus-within { box-shadow: inset 0 0 0 1px var(--ns-assistant-focus, var(--ns-assistant-default-focus)), 0 0 0 2px var(--ns-assistant-focus, var(--ns-assistant-default-focus)); }
6878
7018
  textarea { flex: 1; min-height: 24px; max-height: 120px; resize: none; padding: 0; color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: transparent; border: 0; border-radius: 0; outline: 0; box-shadow: none; appearance: none; -webkit-appearance: none; font: inherit; font-size: 16px; line-height: 24px; }
6879
7019
  textarea::placeholder { color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); }
6880
7020
  textarea:focus, textarea:focus-visible { outline: none; box-shadow: none; }
6881
7021
  button:focus-visible { outline: 3px solid var(--ns-assistant-focus, var(--ns-assistant-default-focus)); outline-offset: 2px; }
6882
- .send { position: relative; display: block; width: 44px; height: 44px; flex: 0 0 44px; padding: 0; overflow: hidden; color: transparent; background: var(--ns-assistant-primary-button, var(--ns-assistant-accent, var(--ns-assistant-default-accent))); font-size: 0; line-height: 0; transition: transform .15s ease, background .15s ease; }
7022
+ .send { position: relative; display: block; width: 44px; height: 44px; flex: 0 0 44px; padding: 0; overflow: hidden; color: transparent; border-radius: 999px; background: var(--ns-assistant-primary-button, var(--ns-assistant-accent, var(--ns-assistant-default-accent))); font-size: 0; line-height: 0; transition: transform .15s ease, background .15s ease; }
6883
7023
  .send::before { content: ''; position: absolute; top: 50%; left: 50%; width: 17px; height: 17px; background: var(--ns-assistant-primary-button-text, var(--ns-assistant-accent-text, var(--ns-assistant-default-accent-text))); transform: translate(-50%, -50%); -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 19V5M5 12l7-7 7 7' fill='none' stroke='black' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 19V5M5 12l7-7 7 7' fill='none' stroke='black' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; }
6884
- .send:hover { transform: scale(1.05); background: var(--ns-assistant-hover, var(--ns-assistant-default-hover)); }
7024
+ .send:hover { transform: scale(1.05); background: var(--ns-assistant-hover, color-mix(in srgb, var(--ns-assistant-primary-button, var(--ns-assistant-accent, var(--ns-assistant-default-accent))) 88%, var(--ns-assistant-primary-button-text, var(--ns-assistant-accent-text, var(--ns-assistant-default-accent-text))) 12%)); }
6885
7025
  :host([busy]) .send::before { width: 13px; height: 13px; border-radius: 3px; -webkit-mask: none; mask: none; }
6886
7026
  .send:disabled, textarea:disabled { opacity: .55; cursor: not-allowed; }
6887
7027
  .legal { display: flex; gap: 12px; justify-content: center; padding: 0 12px 9px; font-size: .75em; }
@@ -6891,9 +7031,10 @@ var ASSISTANT_ELEMENT_STYLES = `<style>
6891
7031
  @keyframes halo-panel-in { from { opacity: 0; transform: translateY(14px) scale(.985); } to { opacity: 1; transform: none; } }
6892
7032
  @keyframes halo-message-in { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
6893
7033
  @keyframes halo-shimmer { from { background-position: 100% center; } to { background-position: 0 center; } }
7034
+ @keyframes halo-caret-blink { 50% { opacity: 0; } }
6894
7035
  :host([data-density="compact"]) .welcome { padding: 20px 18px 12px; }
6895
- @media (max-width: 640px) { :host([mobile-fullscreen][open]) { inset: 0; } :host([mobile-fullscreen][open]) .panel { width: 100vw; min-height: 100vh; max-height: 100vh; border: 0; border-radius: 0; } }
6896
- @media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; } .messages { scroll-behavior: auto; } }
7036
+ @media (max-width: 640px) { :host([mobile-fullscreen][open]) { inset: 0; } :host([mobile-fullscreen][open]) .panel { width: 100vw; min-height: 100vh; max-height: 100vh; border: 0; border-radius: 0; } :host([mobile-fullscreen]) header { padding-top: max(12px, env(safe-area-inset-top)); padding-right: max(18px, env(safe-area-inset-right)); padding-left: max(18px, env(safe-area-inset-left)); } :host([mobile-fullscreen]) form { margin-right: max(16px, env(safe-area-inset-right)); margin-bottom: max(16px, env(safe-area-inset-bottom)); margin-left: max(16px, env(safe-area-inset-left)); } :host([mobile-fullscreen]) .legal, :host([mobile-fullscreen]) ::slotted([slot="conversation-footer"]) { padding-bottom: max(10px, env(safe-area-inset-bottom)); } }
7037
+ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; } .messages { scroll-behavior: auto; } .message.assistant.streaming .markdown::after { animation: none; } }
6897
7038
  @media (forced-colors: active) { .message.thinking > span { color: CanvasText; background: none; } }
6898
7039
  @media (hover: none) { .message-copy { opacity: 1; } }
6899
7040
  @media (max-width: 560px) { :host([data-mode="floating"]) { inset: auto 12px 12px; } :host([open]) .panel { width: calc(100vw - 24px); max-height: calc(100vh - 24px); } }
@@ -7212,6 +7353,8 @@ var presentationStyles = `<style>
7212
7353
  border: var(--ns-assistant-panel-border, var(--ns-assistant-presentation-border));
7213
7354
  border-radius: var(--ns-assistant-panel-radius, var(--ns-assistant-presentation-panel-radius, var(--ns-assistant-default-panel-radius)));
7214
7355
  box-shadow: var(--ns-assistant-shadow, var(--ns-assistant-presentation-shadow));
7356
+ backdrop-filter: blur(var(--ns-assistant-backdrop-blur, var(--ns-assistant-default-backdrop-blur)));
7357
+ -webkit-backdrop-filter: blur(var(--ns-assistant-backdrop-blur, var(--ns-assistant-default-backdrop-blur)));
7215
7358
  }
7216
7359
  :host([data-panel-surface="solid"]) .panel {
7217
7360
  backdrop-filter: none;
@@ -7405,6 +7548,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7405
7548
  #hostAppearance;
7406
7549
  #appearanceWarningKeys = /* @__PURE__ */ new Set();
7407
7550
  #appearanceStyleSnapshot = /* @__PURE__ */ new Map();
7551
+ #scroll = new AssistantElementScrollController();
7408
7552
  sessionEndpoint = "";
7409
7553
  get appearance() {
7410
7554
  return this.#hostAppearance;
@@ -7432,19 +7576,29 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7432
7576
  labels: () => this.#appearance.labels,
7433
7577
  respond: (id, response) => this.respond(id, response),
7434
7578
  dispatchError: (detail) => this.#dispatchError(detail),
7579
+ revealLatest: () => this.#revealLatest(),
7435
7580
  renderView: (detail) => {
7436
7581
  const messages = this.#messages;
7437
7582
  const client = this.#client;
7438
7583
  if (!messages || !client) return;
7439
- const view = mountAssistantApp(detail, {
7440
- client,
7441
- sendMessage: (message) => this.sendMessage(message),
7442
- updateModelContext: (update) => this.updateModelContext(update)
7443
- });
7584
+ const sandboxUrl = client.appSandboxUrl?.();
7585
+ const view = mountAssistantApp(
7586
+ detail,
7587
+ {
7588
+ client,
7589
+ sendMessage: (message) => this.sendMessage(message),
7590
+ updateModelContext: (update) => this.updateModelContext(update),
7591
+ theme: this.#resolvedMode()
7592
+ },
7593
+ {
7594
+ ...sandboxUrl === void 0 ? {} : { sandboxUrl },
7595
+ onRenderFailure: (failure) => this.#dispatchError(failure)
7596
+ }
7597
+ );
7444
7598
  if (!view) return;
7445
7599
  messages.append(view);
7446
7600
  this.setAttribute("has-messages", "");
7447
- messages.scrollTop = messages.scrollHeight;
7601
+ this.#revealLatest();
7448
7602
  }
7449
7603
  });
7450
7604
  connectedCallback() {
@@ -7467,6 +7621,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7467
7621
  this.#clientEndpoint = "";
7468
7622
  this.#unsubscribeClient = void 0;
7469
7623
  this.#sessionBootstrap = void 0;
7624
+ this.#scroll.disconnect();
7470
7625
  this.#setBusy(false);
7471
7626
  this.#media?.removeEventListener("change", this.#handleSystemTheme);
7472
7627
  document.removeEventListener("keydown", this.#handleDocumentKeydown);
@@ -7749,18 +7904,10 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7749
7904
  this.#messages.append(status);
7750
7905
  }
7751
7906
  #scrollToBottom() {
7752
- if (this.#messages) this.#messages.scrollTop = this.#messages.scrollHeight;
7907
+ this.#scroll.scrollToBottom();
7753
7908
  }
7754
7909
  #revealLatest() {
7755
- if (!this.#messages) return;
7756
- const distance = this.#messages.scrollHeight - this.#messages.scrollTop - this.#messages.clientHeight;
7757
- const control = this.shadowRoot?.querySelector(".new-messages");
7758
- if (distance <= 160) {
7759
- this.#scrollToBottom();
7760
- if (control) control.hidden = true;
7761
- } else if (control) {
7762
- control.hidden = false;
7763
- }
7910
+ this.#scroll.revealLatest();
7764
7911
  }
7765
7912
  #dispatchError(detail) {
7766
7913
  this.dispatchEvent(new CustomEvent("assistant-error", { detail }));
@@ -7877,6 +8024,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7877
8024
  }
7878
8025
  #render() {
7879
8026
  if (!this.shadowRoot) return;
8027
+ this.#scroll.disconnect();
7880
8028
  const conversation = this.#messages ? [...this.#messages.childNodes] : [];
7881
8029
  const appearance = this.#appearance;
7882
8030
  this.setAttribute("role", "complementary");
@@ -7890,11 +8038,13 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7890
8038
  this.shadowRoot.innerHTML = `${ASSISTANT_ELEMENT_STYLES}${presentationStyles}
7891
8039
  <button class="launcher" type="button" aria-label="${escapeAttribute(appearance.labels.open)}" aria-controls="assistant-panel" aria-expanded="${String(this.hasAttribute("open"))}"><slot name="launcher-icon"></slot><span class="visually-hidden" data-session-status aria-live="polite" hidden></span></button>
7892
8040
  <section class="panel" id="assistant-panel" role="dialog" aria-label="${escapeAttribute(appearance.brand.name)}">
7893
- <header><slot name="header-leading"></slot><img class="brand-logo" alt=""><strong></strong><slot name="header-actions"></slot><button class="close" type="button" aria-label="${escapeAttribute(appearance.labels.close)}">\xD7</button></header>
8041
+ <header><slot name="header-leading"></slot><img class="brand-logo" alt=""><strong></strong><slot name="header-actions"></slot><button class="close" type="button" aria-label="${escapeAttribute(appearance.labels.close)}"></button></header>
7894
8042
  <div class="welcome"><slot name="empty-state"></slot></div>
7895
8043
  <div class="suggested-prompts"></div>
7896
- <div class="messages" aria-live="polite"></div>
7897
- <button class="new-messages" type="button" hidden></button>
8044
+ <div class="messages-region">
8045
+ <div class="messages" aria-live="polite"></div>
8046
+ <button class="new-messages" type="button" aria-label="${escapeAttribute(appearance.labels.newMessages)}" hidden></button>
8047
+ </div>
7898
8048
  <form><slot name="composer-leading"></slot><textarea rows="1"></textarea><button class="send" type="submit"></button><slot name="composer-trailing"></slot></form>
7899
8049
  <nav class="legal" aria-label="Legal"></nav>
7900
8050
  <slot name="conversation-footer"></slot>
@@ -7956,15 +8106,11 @@ var NoodleAssistantElement = class extends HTMLElementBase {
7956
8106
  form.requestSubmit();
7957
8107
  });
7958
8108
  const newMessages = queryRequired(this.shadowRoot, ".new-messages");
7959
- newMessages.textContent = appearance.labels.newMessages;
7960
- newMessages.addEventListener("click", () => {
7961
- this.#scrollToBottom();
7962
- newMessages.hidden = true;
7963
- });
7964
- this.#messages?.addEventListener("scroll", () => {
7965
- if (this.#messages && this.#messages.scrollHeight - this.#messages.scrollTop - this.#messages.clientHeight < 48) {
7966
- newMessages.hidden = true;
7967
- }
8109
+ newMessages.setAttribute("aria-label", appearance.labels.newMessages);
8110
+ this.#scroll.connect({
8111
+ region: queryRequired(this.shadowRoot, ".messages-region"),
8112
+ messages: queryRequired(this.shadowRoot, ".messages"),
8113
+ control: newMessages
7968
8114
  });
7969
8115
  this.#applyTheme();
7970
8116
  }
@@ -7999,4 +8145,4 @@ export {
7999
8145
  dompurify/dist/purify.es.mjs:
8000
8146
  (*! @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 *)
8001
8147
  */
8002
- //# sourceMappingURL=chunk-ZKXOX67J.js.map
8148
+ //# sourceMappingURL=chunk-JMBUJSFP.js.map