@mcp-b/char 0.1.3 → 0.1.4

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.
@@ -94,8 +94,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
94
94
  styles: mergeStyles(previous.styles, patch.styles),
95
95
  containerDimensions: shallowMerge(previous.containerDimensions, patch.containerDimensions),
96
96
  deviceCapabilities: shallowMerge(previous.deviceCapabilities, patch.deviceCapabilities),
97
- safeAreaInsets: shallowMerge(previous.safeAreaInsets, patch.safeAreaInsets),
98
- hostCapabilities: shallowMerge(previous.hostCapabilities, patch.hostCapabilities)
97
+ safeAreaInsets: shallowMerge(previous.safeAreaInsets, patch.safeAreaInsets)
99
98
  };
100
99
  }
101
100
 
@@ -507,25 +506,6 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
507
506
  "pip"
508
507
  ];
509
508
  /**
510
- * Normalizes an API key by trimming whitespace and collapsing empty strings.
511
- */
512
- function normalizeApiKey(value) {
513
- const trimmed = value?.trim();
514
- return trimmed ? trimmed : void 0;
515
- }
516
- /**
517
- * Produces a sanitized dev-mode configuration and drops empty payloads.
518
- */
519
- function resolveDevMode(devMode) {
520
- if (!devMode) return void 0;
521
- const normalized = {
522
- anthropicApiKey: normalizeApiKey(devMode.anthropicApiKey),
523
- openaiApiKey: normalizeApiKey(devMode.openaiApiKey),
524
- useLocalApi: devMode.useLocalApi === true ? true : void 0
525
- };
526
- return !!normalized.anthropicApiKey || !!normalized.openaiApiKey || !!normalized.useLocalApi ? normalized : void 0;
527
- }
528
- /**
529
509
  * Runtime guard for the message envelope emitted by the iframe.
530
510
  */
531
511
  function isCharIframeMessageData(value) {
@@ -664,7 +644,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
664
644
  */
665
645
  var CharAgentElement = class extends HTMLElement {
666
646
  static observedAttributes = [
667
- "dev-mode",
647
+ "publishable-key",
668
648
  "enable-debug-tools",
669
649
  "display-mode",
670
650
  "api-base"
@@ -714,12 +694,12 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
714
694
  return;
715
695
  }
716
696
  if (!this.style.display) this.style.display = "block";
717
- const resolvedDevMode = resolveDevMode(this._resolveDevMode());
718
- const apiBase = this._resolveApiBaseOverride() ?? (resolvedDevMode?.useLocalApi ? window.location.origin : WEBMCP_PRODUCTION_API_BASE);
697
+ const apiBase = this._resolveApiBaseOverride() ?? WEBMCP_PRODUCTION_API_BASE;
719
698
  const iframeOrigin = this._resolveIframeOrigin(apiBase);
720
699
  const iframe = this._createIframe(apiBase);
721
700
  this._iframe = iframe;
722
- if (resolvedDevMode?.anthropicApiKey) this._pendingAuth = { anthropicApiKey: resolvedDevMode.anthropicApiKey };
701
+ const publishableKey = this.publishableKey ?? this.getAttribute("publishable-key") ?? void 0;
702
+ if (publishableKey) this._pendingAuth = { publishableKey };
723
703
  this._proxy = new CharIframeProxy(iframe, iframeOrigin);
724
704
  this._proxy.start();
725
705
  this._messageListener = this._createMessageListener(iframe, iframeOrigin);
@@ -928,15 +908,21 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
928
908
  /**
929
909
  * Reacts to observed attribute updates after iframe readiness.
930
910
  * Attributes that affect iframe boot configuration are intentionally ignored
931
- * after mount (`dev-mode`, `enable-debug-tools`, `api-base`).
911
+ * after mount (`enable-debug-tools`, `api-base`).
932
912
  */
933
913
  attributeChangedCallback(name, _oldValue, newValue) {
934
914
  if (!this._iframe || !this._iframeReady) return;
935
915
  switch (name) {
916
+ case "publishable-key":
917
+ if (newValue) {
918
+ this._pendingAuth = { publishableKey: newValue };
919
+ const iframeOrigin = this._getIframeOrigin();
920
+ if (iframeOrigin) this._postAuth(iframeOrigin);
921
+ }
922
+ break;
936
923
  case "display-mode":
937
924
  this.setHostContext({ displayMode: resolveDisplayModeFromAttribute(newValue) });
938
925
  break;
939
- case "dev-mode":
940
926
  case "enable-debug-tools":
941
927
  case "api-base": break;
942
928
  }
@@ -947,23 +933,17 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
947
933
  * The token is stored as a JavaScript property (not as a DOM attribute),
948
934
  * preventing exposure to DOM inspection and session replay tools.
949
935
  *
950
- * @param options - Authentication payload for either ID token or ticket auth.
936
+ * @param options - Authentication payload.
951
937
  * @returns `true` when the payload is accepted; `false` when validation fails.
952
938
  */
953
939
  connect(options) {
954
- if (!options?.idToken && !options?.ticketAuth) {
955
- this._emitCharError("MISSING_TOKEN", "connect() requires either idToken or ticketAuth parameter");
956
- return false;
957
- }
958
- if (options?.idToken && !options?.ticketAuth && !options?.clientId) {
959
- this._emitCharError("MISSING_CLIENT_ID", "connect() requires clientId when using idToken authentication");
940
+ if (!options?.publishableKey) {
941
+ this._emitCharError("MISSING_PUBLISHABLE_KEY", "connect() requires publishableKey");
960
942
  return false;
961
943
  }
962
944
  this._pendingAuth = {
963
- idToken: options.ticketAuth ? void 0 : options.idToken,
964
- clientId: options.ticketAuth ? void 0 : options.clientId,
965
- organizationId: options.ticketAuth ? void 0 : options.organizationId,
966
- ticketAuth: options.ticketAuth
945
+ publishableKey: options.publishableKey,
946
+ idToken: options.idToken
967
947
  };
968
948
  const iframeOrigin = this._getIframeOrigin();
969
949
  if (this._iframeReady && iframeOrigin) this._postAuth(iframeOrigin);
@@ -982,7 +962,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
982
962
  }
983
963
  /**
984
964
  * Disconnect from the Char agent.
985
- * Clears the authentication token.
965
+ * Clears pending auth state and posts a disconnect message to the iframe.
986
966
  *
987
967
  * @returns `true` when the disconnect message was sent; `false` when the iframe was not ready.
988
968
  */
@@ -1046,11 +1026,6 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1046
1026
  };
1047
1027
  const safeAreaInsets = getSafeAreaInsets();
1048
1028
  const displayMode = resolveDisplayModeFromAttribute(displayModeAttr) ?? "inline";
1049
- const hostCapabilities = {
1050
- supportedDisplayModes: [...DISPLAY_MODES],
1051
- supportsOpenLink: true,
1052
- supportsTeardown: true
1053
- };
1054
1029
  const context = {
1055
1030
  theme,
1056
1031
  styles: { variables: vars },
@@ -1061,9 +1036,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1061
1036
  timeZone,
1062
1037
  platform: detectPlatform(),
1063
1038
  deviceCapabilities: getDeviceCapabilities(),
1064
- safeAreaInsets,
1065
- userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
1066
- hostCapabilities
1039
+ safeAreaInsets
1067
1040
  };
1068
1041
  this._hostContext = context;
1069
1042
  if (!this._iframe?.contentWindow) {
@@ -1106,22 +1079,15 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1106
1079
  */
1107
1080
  _postAuth(iframeOrigin) {
1108
1081
  if (!this._pendingAuth || !this._iframe?.contentWindow) return;
1109
- if (!this._pendingAuth.ticketAuth && !this._pendingAuth.anthropicApiKey && !this._pendingAuth.idToken) {
1110
- console.error("[Char] _postAuth called but _pendingAuth has no auth credentials");
1082
+ if (!this._pendingAuth.publishableKey) {
1083
+ console.error("[Char] _postAuth called but _pendingAuth has no publishableKey");
1111
1084
  this._pendingAuth = null;
1112
1085
  return;
1113
1086
  }
1114
- const message = this._pendingAuth.ticketAuth ? {
1115
- type: "char-auth",
1116
- ticketAuth: this._pendingAuth.ticketAuth
1117
- } : this._pendingAuth.anthropicApiKey ? {
1087
+ const message = {
1118
1088
  type: "char-auth",
1119
- anthropicApiKey: this._pendingAuth.anthropicApiKey
1120
- } : {
1121
- type: "char-auth",
1122
- idToken: this._pendingAuth.idToken,
1123
- clientId: this._pendingAuth.clientId,
1124
- organizationId: this._pendingAuth.organizationId
1089
+ publishableKey: this._pendingAuth.publishableKey,
1090
+ idToken: this._pendingAuth.idToken
1125
1091
  };
1126
1092
  this._iframe.contentWindow.postMessage(message, iframeOrigin);
1127
1093
  }
@@ -1268,36 +1234,6 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1268
1234
  this._containerResizeObserver.observe(this);
1269
1235
  }
1270
1236
  /**
1271
- * Resolve devMode from property (React 19) or attribute.
1272
- *
1273
- * @returns Parsed dev-mode configuration when valid.
1274
- */
1275
- _resolveDevMode() {
1276
- if (this.devMode && typeof this.devMode === "object") return this.devMode;
1277
- const devModeAttr = this.getAttribute("dev-mode");
1278
- if (!devModeAttr) return void 0;
1279
- try {
1280
- const parsed = JSON.parse(devModeAttr);
1281
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1282
- const msg = `dev-mode attribute must be a JSON object, got: ${Array.isArray(parsed) ? "array" : typeof parsed}`;
1283
- console.warn(`[Char] ${msg}`);
1284
- this._emitCharError("INVALID_DEV_MODE", msg);
1285
- return;
1286
- }
1287
- const obj = parsed;
1288
- return {
1289
- anthropicApiKey: typeof obj.anthropicApiKey === "string" ? obj.anthropicApiKey : void 0,
1290
- openaiApiKey: typeof obj.openaiApiKey === "string" ? obj.openaiApiKey : void 0,
1291
- useLocalApi: typeof obj.useLocalApi === "boolean" ? obj.useLocalApi : void 0
1292
- };
1293
- } catch (e) {
1294
- const msg = `Failed to parse dev-mode attribute as JSON: ${e instanceof Error ? e.message : String(e)}`;
1295
- console.warn(`[Char] ${msg}`);
1296
- this._emitCharError("INVALID_DEV_MODE", msg);
1297
- return;
1298
- }
1299
- }
1300
- /**
1301
1237
  * Resolve apiBase override from property (React 19) or attribute.
1302
1238
  *
1303
1239
  * @returns Sanitized API base URL without trailing slash.
@@ -1326,22 +1262,36 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1326
1262
  //#region src/shell-component.ts
1327
1263
  const SHELL_OPEN_CHANGE_EVENT = "char-shell-open-change";
1328
1264
  const SHELL_Z_INDEX = 2147483e3;
1329
- const DEFAULT_FULLSCREEN_BREAKPOINT = 1024;
1330
- const DEFAULT_PANEL_WIDTH = 420;
1331
- const DEFAULT_PIP_WIDTH = 300;
1332
- const DEFAULT_PIP_HEIGHT = 96;
1333
- const DEFAULT_PIP_POSITION = "bottom-center";
1265
+ const FULLSCREEN_BREAKPOINT = 1024;
1266
+ const PANEL_WIDTH = 420;
1267
+ const PIP_WIDTH = 400;
1268
+ const PIP_HEIGHT = 60;
1269
+ const PIP_POSITION = "bottom-center";
1334
1270
  const SHELL_HEALTH_TIMEOUT_MS = 1e4;
1335
- const PANEL_BORDER = "1px solid rgba(15, 23, 42, 0.12)";
1336
1271
  const SAFE_BOTTOM = "calc(16px + env(safe-area-inset-bottom, 0px))";
1337
- const SHELL_LAYOUT_TRANSITION = "opacity 280ms ease, width 220ms ease, min-width 220ms ease, flex-basis 220ms ease";
1272
+ const THEME_LIGHT = {
1273
+ background: "oklch(1 0 0)",
1274
+ foreground: "oklch(0.141 0.005 285.823)",
1275
+ border: "oklch(0.92 0.004 286.32)",
1276
+ mutedForeground: "oklch(0.552 0.016 285.938)",
1277
+ primary: "oklch(0.21 0.006 285.885)"
1278
+ };
1279
+ const THEME_DARK = {
1280
+ background: "oklch(0.141 0.005 285.823)",
1281
+ foreground: "oklch(0.985 0 0)",
1282
+ border: "oklch(1 0 0 / 10%)",
1283
+ mutedForeground: "oklch(0.705 0.015 286.067)",
1284
+ primary: "oklch(0.92 0.004 286.32)"
1285
+ };
1286
+ const BODY_MARGIN_TRANSITION = "margin-right 220ms ease";
1287
+ const SHELL_LAYOUT_TRANSITION = "opacity 220ms ease, width 220ms ease, min-width 220ms ease, flex-basis 220ms ease";
1338
1288
  const SHELL_REVEAL_FADE_MS = 280;
1339
1289
  const PIP_MIN_COLLAPSED_WIDTH = 70;
1340
1290
  const PIP_MIN_COLLAPSED_HEIGHT = 30;
1341
1291
  const PIP_MAX_MEASURED_HEIGHT = 320;
1342
1292
  const PIP_COLLAPSE_DELAY_MS = 400;
1343
1293
  const PIP_PILL_HEIGHT = 40;
1344
- const PIP_MORPH_TRANSITION = "width 280ms ease, height 280ms ease, bottom 280ms ease, border-radius 280ms ease, opacity 200ms ease, transform 280ms ease";
1294
+ const PIP_MORPH_TRANSITION = "width 280ms ease, height 280ms ease, bottom 280ms ease, border-radius 280ms ease, opacity 280ms ease, transform 280ms ease";
1345
1295
  const PIP_MORPH_DURATION_MS = 280;
1346
1296
  const PIP_SCROLL_HIDE_OFFSET = 100;
1347
1297
  const PIP_COLLAPSED_WIDTH_RATIO = .75;
@@ -1349,23 +1299,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1349
1299
  const PIP_SCROLL_DIRECTION_THRESHOLD = 40;
1350
1300
  const WHEEL_DELTA_LINE_MULTIPLIER = 15;
1351
1301
  const WHEEL_DELTA_PAGE_MULTIPLIER = 300;
1352
- function normalizePositiveInteger(value, fallback) {
1353
- if (!Number.isFinite(value) || value <= 0) {
1354
- console.warn(`[Char] Invalid value ${value}, using fallback ${fallback}`);
1355
- return fallback;
1356
- }
1357
- return Math.round(value);
1358
- }
1359
- function parsePositiveIntegerAttribute(value, fallback) {
1360
- if (value === null || value.trim() === "") return fallback;
1361
- const parsed = Number.parseInt(value, 10);
1362
- if (!Number.isFinite(parsed)) return fallback;
1363
- return normalizePositiveInteger(parsed, fallback);
1364
- }
1365
- function normalizePipPosition(value) {
1366
- if (value === "bottom-right" || value === "bottom-left") return value;
1367
- return DEFAULT_PIP_POSITION;
1368
- }
1302
+ const PANEL_MIN_WIDTH = 320;
1303
+ const PANEL_MAX_WIDTH = 700;
1304
+ const RESIZE_NUB_WIDTH = 6;
1305
+ const EDGE_TAB_WIDTH = 44;
1306
+ const EDGE_TAB_HEIGHT = 48;
1307
+ const EDGE_TAB_DRAG_THRESHOLD_PX = 4;
1308
+ const ICON_MESSAGE = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/></svg>`;
1309
+ const ICON_CHEVRON_RIGHT = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>`;
1369
1310
  function getPipPositionStyles(position) {
1370
1311
  if (position === "bottom-right") return {
1371
1312
  left: "auto",
@@ -1422,25 +1363,28 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1422
1363
  var CharAgentShellElement = class extends HTMLElement {
1423
1364
  static observedAttributes = [
1424
1365
  "open",
1425
- "fullscreen-breakpoint",
1426
- "panel-width",
1427
- "pip-position",
1428
- "pip-width",
1429
- "pip-height",
1430
- "dev-mode",
1431
1366
  "enable-debug-tools",
1432
1367
  "api-base"
1433
1368
  ];
1434
1369
  _agent = null;
1435
1370
  _open = false;
1436
- _fullscreenBreakpoint = DEFAULT_FULLSCREEN_BREAKPOINT;
1437
- _panelWidth = DEFAULT_PANEL_WIDTH;
1438
- _pipWidth = DEFAULT_PIP_WIDTH;
1439
- _pipHeight = DEFAULT_PIP_HEIGHT;
1440
- _pipPosition = DEFAULT_PIP_POSITION;
1371
+ _savedBodyTransition = null;
1372
+ _bodyMarginTimeoutId = null;
1373
+ _currentPanelWidth = PANEL_WIDTH;
1374
+ _resizeNubElement = null;
1375
+ _edgeTabElement = null;
1376
+ _resizeDragging = false;
1377
+ _resizeDragOverlay = null;
1378
+ _resizeStartX = 0;
1379
+ _resizeStartWidth = 0;
1380
+ _edgeTabDragStartX = 0;
1381
+ _edgeTabDragging = false;
1441
1382
  _isNarrowViewport = false;
1442
1383
  _mediaQuery = null;
1443
1384
  _mediaQueryHandler = null;
1385
+ _themeObserver = null;
1386
+ _themeMediaQuery = null;
1387
+ _themeMediaHandler = null;
1444
1388
  _pendingConnectOptions = null;
1445
1389
  _pendingHostContext = {};
1446
1390
  _isApplyingOpenAttribute = false;
@@ -1458,6 +1402,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1458
1402
  _pipAnimationTimeoutId = null;
1459
1403
  _pendingPipMeasuredHeight = null;
1460
1404
  _tapOutsideListener = null;
1405
+ _lastAppliedDisplayMode = null;
1461
1406
  _pipHiddenByScroll = false;
1462
1407
  _scrollListener = null;
1463
1408
  _scrollRafId = null;
@@ -1477,75 +1422,36 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1477
1422
  set open(value) {
1478
1423
  this.setOpen(value);
1479
1424
  }
1480
- get fullscreenBreakpoint() {
1481
- return this._fullscreenBreakpoint;
1482
- }
1483
- set fullscreenBreakpoint(value) {
1484
- const normalized = normalizePositiveInteger(value, DEFAULT_FULLSCREEN_BREAKPOINT);
1485
- if (normalized === this._fullscreenBreakpoint) return;
1486
- this._fullscreenBreakpoint = normalized;
1487
- this.setAttribute("fullscreen-breakpoint", String(normalized));
1488
- this._setupViewportObserver();
1489
- this._applyDisplayModeAndLayout();
1490
- }
1491
- get panelWidth() {
1492
- return this._panelWidth;
1493
- }
1494
- set panelWidth(value) {
1495
- const normalized = normalizePositiveInteger(value, DEFAULT_PANEL_WIDTH);
1496
- if (normalized === this._panelWidth) return;
1497
- this._panelWidth = normalized;
1498
- this.setAttribute("panel-width", String(normalized));
1499
- this._applyDisplayModeAndLayout();
1500
- }
1501
- get pipWidth() {
1502
- return this._pipWidth;
1503
- }
1504
- set pipWidth(value) {
1505
- const normalized = normalizePositiveInteger(value, DEFAULT_PIP_WIDTH);
1506
- if (normalized === this._pipWidth) return;
1507
- this._pipWidth = normalized;
1508
- this.setAttribute("pip-width", String(normalized));
1509
- this._applyDisplayModeAndLayout();
1510
- }
1511
- get pipHeight() {
1512
- return this._pipHeight;
1513
- }
1514
- set pipHeight(value) {
1515
- const normalized = normalizePositiveInteger(value, DEFAULT_PIP_HEIGHT);
1516
- if (normalized === this._pipHeight) return;
1517
- this._pipHeight = normalized;
1518
- this.setAttribute("pip-height", String(normalized));
1519
- this._applyDisplayModeAndLayout();
1520
- }
1521
- get pipPosition() {
1522
- return this._pipPosition;
1523
- }
1524
- set pipPosition(value) {
1525
- const normalized = normalizePipPosition(value);
1526
- if (normalized === this._pipPosition) return;
1527
- this._pipPosition = normalized;
1528
- this.setAttribute("pip-position", normalized);
1529
- this._applyDisplayModeAndLayout();
1530
- }
1531
1425
  connectedCallback() {
1532
1426
  this._upgradeProperty("open");
1533
- this._upgradeProperty("fullscreenBreakpoint");
1534
- this._upgradeProperty("panelWidth");
1535
- this._upgradeProperty("pipPosition");
1536
- this._upgradeProperty("pipWidth");
1537
- this._upgradeProperty("pipHeight");
1538
- this._upgradeProperty("devMode");
1539
1427
  this._upgradeProperty("apiBase");
1540
1428
  registerChar();
1541
1429
  this._ensureAgent();
1542
1430
  this._readConfigFromAttributes();
1543
1431
  this._syncForwardedAttributes();
1544
1432
  this._setupViewportObserver();
1433
+ this._setupThemeObserver();
1545
1434
  this._applyDisplayModeAndLayout();
1546
1435
  this._flushPendingConnectAndContext();
1547
1436
  }
1548
1437
  disconnectedCallback() {
1438
+ this._removeBodyMargin();
1439
+ if (this._bodyMarginTimeoutId !== null) {
1440
+ window.clearTimeout(this._bodyMarginTimeoutId);
1441
+ this._bodyMarginTimeoutId = null;
1442
+ }
1443
+ this._removeResizeDragOverlay();
1444
+ this._teardownThemeObserver();
1445
+ document.removeEventListener("mousemove", this._onEdgeTabMouseMove);
1446
+ document.removeEventListener("mouseup", this._onEdgeTabMouseUp);
1447
+ document.removeEventListener("touchmove", this._onEdgeTabTouchMove);
1448
+ document.removeEventListener("touchend", this._onEdgeTabTouchEnd);
1449
+ this._removeEdgeTab();
1450
+ this._hideResizeNub();
1451
+ if (this._resizeNubElement) {
1452
+ this._resizeNubElement.remove();
1453
+ this._resizeNubElement = null;
1454
+ }
1549
1455
  this._stopHealthWait();
1550
1456
  this._cancelRevealFade();
1551
1457
  this._teardownViewportObserver();
@@ -1574,48 +1480,6 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1574
1480
  });
1575
1481
  return;
1576
1482
  }
1577
- case "fullscreen-breakpoint": {
1578
- const next = parsePositiveIntegerAttribute(newValue, DEFAULT_FULLSCREEN_BREAKPOINT);
1579
- if (next !== this._fullscreenBreakpoint) {
1580
- this._fullscreenBreakpoint = next;
1581
- this._setupViewportObserver();
1582
- this._applyDisplayModeAndLayout();
1583
- }
1584
- return;
1585
- }
1586
- case "panel-width": {
1587
- const next = parsePositiveIntegerAttribute(newValue, DEFAULT_PANEL_WIDTH);
1588
- if (next !== this._panelWidth) {
1589
- this._panelWidth = next;
1590
- this._applyDisplayModeAndLayout();
1591
- }
1592
- return;
1593
- }
1594
- case "pip-width": {
1595
- const next = parsePositiveIntegerAttribute(newValue, DEFAULT_PIP_WIDTH);
1596
- if (next !== this._pipWidth) {
1597
- this._pipWidth = next;
1598
- this._applyDisplayModeAndLayout();
1599
- }
1600
- return;
1601
- }
1602
- case "pip-height": {
1603
- const next = parsePositiveIntegerAttribute(newValue, DEFAULT_PIP_HEIGHT);
1604
- if (next !== this._pipHeight) {
1605
- this._pipHeight = next;
1606
- this._applyDisplayModeAndLayout();
1607
- }
1608
- return;
1609
- }
1610
- case "pip-position": {
1611
- const next = normalizePipPosition(newValue);
1612
- if (next !== this._pipPosition) {
1613
- this._pipPosition = next;
1614
- this._applyDisplayModeAndLayout();
1615
- }
1616
- return;
1617
- }
1618
- case "dev-mode":
1619
1483
  case "enable-debug-tools":
1620
1484
  case "api-base":
1621
1485
  this._syncForwardedAttributes();
@@ -1625,7 +1489,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1625
1489
  /**
1626
1490
  * Connects shell authentication and starts availability health checks.
1627
1491
  *
1628
- * @param options - Authentication payload for either ID token or ticket auth.
1492
+ * @param options - Authentication payload (`publishableKey` + optional `idToken`).
1629
1493
  * @returns `true` when connect is accepted by the inner element.
1630
1494
  */
1631
1495
  connect(options) {
@@ -1733,15 +1597,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1733
1597
  this._attachForwardedListeners();
1734
1598
  }
1735
1599
  _primeAgentBootConfig(agent) {
1736
- for (const name of [
1737
- "dev-mode",
1738
- "enable-debug-tools",
1739
- "api-base"
1740
- ]) {
1600
+ for (const name of ["enable-debug-tools", "api-base"]) {
1741
1601
  const value = this.getAttribute(name);
1742
1602
  if (value !== null) agent.setAttribute(name, value);
1743
1603
  }
1744
- agent.devMode = this.devMode && typeof this.devMode === "object" ? this.devMode : void 0;
1745
1604
  agent.apiBase = typeof this.apiBase === "string" ? this.apiBase : void 0;
1746
1605
  }
1747
1606
  _attachForwardedListeners() {
@@ -1764,29 +1623,19 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1764
1623
  }
1765
1624
  _readConfigFromAttributes() {
1766
1625
  this._open = parseBooleanAttribute(this.getAttribute("open"));
1767
- this._fullscreenBreakpoint = parsePositiveIntegerAttribute(this.getAttribute("fullscreen-breakpoint"), DEFAULT_FULLSCREEN_BREAKPOINT);
1768
- this._panelWidth = parsePositiveIntegerAttribute(this.getAttribute("panel-width"), DEFAULT_PANEL_WIDTH);
1769
- this._pipWidth = parsePositiveIntegerAttribute(this.getAttribute("pip-width"), DEFAULT_PIP_WIDTH);
1770
- this._pipHeight = parsePositiveIntegerAttribute(this.getAttribute("pip-height"), DEFAULT_PIP_HEIGHT);
1771
- this._pipPosition = normalizePipPosition(this.getAttribute("pip-position"));
1772
1626
  }
1773
1627
  _syncForwardedAttributes() {
1774
1628
  if (!this._agent) return;
1775
- for (const name of [
1776
- "dev-mode",
1777
- "enable-debug-tools",
1778
- "api-base"
1779
- ]) {
1629
+ for (const name of ["enable-debug-tools", "api-base"]) {
1780
1630
  const value = this.getAttribute(name);
1781
1631
  if (value === null) this._agent.removeAttribute(name);
1782
1632
  else this._agent.setAttribute(name, value);
1783
1633
  }
1784
- this._agent.devMode = this.devMode && typeof this.devMode === "object" ? this.devMode : void 0;
1785
1634
  this._agent.apiBase = typeof this.apiBase === "string" ? this.apiBase : void 0;
1786
1635
  }
1787
1636
  _setupViewportObserver() {
1788
1637
  if (typeof window === "undefined") return;
1789
- const query = `(max-width: ${this._fullscreenBreakpoint - 1}px)`;
1638
+ const query = `(max-width: ${FULLSCREEN_BREAKPOINT - 1}px)`;
1790
1639
  if (this._mediaQuery?.media === query) {
1791
1640
  this._isNarrowViewport = this._mediaQuery.matches;
1792
1641
  return;
@@ -1824,38 +1673,558 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1824
1673
  if (displayMode !== "pip") {
1825
1674
  if (this._pipMeasuredHeight !== null) this._pipMeasuredHeight = null;
1826
1675
  }
1676
+ const prevMode = this._lastAppliedDisplayMode;
1677
+ this._lastAppliedDisplayMode = displayMode;
1827
1678
  this._agent.setAttribute("display-mode", displayMode);
1828
1679
  this._applyHostLayout(displayMode);
1829
- this._applyAgentLayout(displayMode);
1680
+ this._applyAgentLayout(displayMode, prevMode);
1681
+ }
1682
+ _isParentFlex() {
1683
+ const parent = this.parentElement;
1684
+ if (!parent) return false;
1685
+ const display = getComputedStyle(parent).display;
1686
+ return display === "flex" || display === "inline-flex";
1687
+ }
1688
+ _isDarkMode() {
1689
+ const themeAttr = document.documentElement.getAttribute("data-theme");
1690
+ if (themeAttr === "dark") return true;
1691
+ if (themeAttr === "light") return false;
1692
+ return document.documentElement.classList.contains("dark") || window.matchMedia("(prefers-color-scheme: dark)").matches;
1693
+ }
1694
+ /**
1695
+ * Read a `--char-color-*` variable from the shell element. If the host
1696
+ * page has set it (directly or via inheritance) use that value; otherwise
1697
+ * fall back to the char design-system default for the current light/dark mode.
1698
+ */
1699
+ _resolveColor(token) {
1700
+ const host = getComputedStyle(this).getPropertyValue({
1701
+ background: "--char-color-background",
1702
+ foreground: "--char-color-foreground",
1703
+ border: "--char-color-border",
1704
+ mutedForeground: "--char-color-muted-foreground",
1705
+ primary: "--char-color-primary"
1706
+ }[token]).trim();
1707
+ if (host) return host;
1708
+ return (this._isDarkMode() ? THEME_DARK : THEME_LIGHT)[token];
1709
+ }
1710
+ _panelBorder() {
1711
+ return `1px solid ${this._resolveColor("border")}`;
1712
+ }
1713
+ _setupThemeObserver() {
1714
+ const onThemeChange = () => {
1715
+ this._applyEdgeTabVisual();
1716
+ this._refreshResizeNubColors();
1717
+ if (this._open && !this._isNarrowViewport) {
1718
+ if (this._isParentFlex()) setStyleValue(this, "border-left", this._panelBorder());
1719
+ else if (this._agent) setStyleValue(this._agent, "border-left", this._panelBorder());
1720
+ }
1721
+ };
1722
+ this._themeObserver = new MutationObserver(onThemeChange);
1723
+ this._themeObserver.observe(document.documentElement, {
1724
+ attributes: true,
1725
+ attributeFilter: [
1726
+ "class",
1727
+ "style",
1728
+ "data-theme"
1729
+ ]
1730
+ });
1731
+ this._themeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
1732
+ this._themeMediaHandler = onThemeChange;
1733
+ this._themeMediaQuery.addEventListener("change", this._themeMediaHandler);
1734
+ }
1735
+ _teardownThemeObserver() {
1736
+ if (this._themeObserver) {
1737
+ this._themeObserver.disconnect();
1738
+ this._themeObserver = null;
1739
+ }
1740
+ if (this._themeMediaQuery && this._themeMediaHandler) {
1741
+ this._themeMediaQuery.removeEventListener("change", this._themeMediaHandler);
1742
+ this._themeMediaQuery = null;
1743
+ this._themeMediaHandler = null;
1744
+ }
1830
1745
  }
1831
1746
  _applyHostLayout(displayMode) {
1747
+ const useFlexLayout = this._isParentFlex();
1832
1748
  if (displayMode === "inline" && !this._isNarrowViewport) {
1833
- setStyleValue(this, "position", "relative");
1834
- setStyleValue(this, "flex", `0 0 ${this._panelWidth}px`);
1835
- setStyleValue(this, "width", `${this._panelWidth}px`);
1836
- setStyleValue(this, "min-width", `${this._panelWidth}px`);
1837
- setStyleValue(this, "height", "100%");
1838
- setStyleValue(this, "z-index", String(SHELL_Z_INDEX));
1839
- setStyleValue(this, "border-left", PANEL_BORDER);
1840
- setStyleValue(this, "transition", SHELL_LAYOUT_TRANSITION);
1749
+ if (useFlexLayout) {
1750
+ setStyleValue(this, "position", "relative");
1751
+ setStyleValue(this, "flex", `0 0 ${this._currentPanelWidth}px`);
1752
+ setStyleValue(this, "width", `${this._currentPanelWidth}px`);
1753
+ setStyleValue(this, "min-width", `${this._currentPanelWidth}px`);
1754
+ setStyleValue(this, "height", "100%");
1755
+ setStyleValue(this, "z-index", String(SHELL_Z_INDEX));
1756
+ setStyleValue(this, "border-left", this._panelBorder());
1757
+ setStyleValue(this, "transition", this._resizeDragging ? "none" : SHELL_LAYOUT_TRANSITION);
1758
+ resetStyleValue(this, "overflow");
1759
+ resetStyleValue(this, "pointer-events");
1760
+ this._removeBodyMargin();
1761
+ } else {
1762
+ setStyleValue(this, "position", "fixed");
1763
+ setStyleValue(this, "width", "0px");
1764
+ setStyleValue(this, "height", "0px");
1765
+ setStyleValue(this, "z-index", String(SHELL_Z_INDEX));
1766
+ resetStyleValue(this, "flex");
1767
+ resetStyleValue(this, "min-width");
1768
+ resetStyleValue(this, "border-left");
1769
+ resetStyleValue(this, "overflow");
1770
+ resetStyleValue(this, "pointer-events");
1771
+ this._applyBodyMargin(this._currentPanelWidth);
1772
+ }
1773
+ this._showResizeNub();
1774
+ this._showEdgeTab();
1775
+ if (useFlexLayout) this._syncEdgeTabWithPanelTransition();
1776
+ } else if (displayMode === "fullscreen") {
1777
+ if (useFlexLayout) {
1778
+ setStyleValue(this, "position", "relative");
1779
+ setStyleValue(this, "flex", "0 0 0px");
1780
+ setStyleValue(this, "width", "0px");
1781
+ setStyleValue(this, "min-width", "0px");
1782
+ setStyleValue(this, "height", "0px");
1783
+ setStyleValue(this, "z-index", String(SHELL_Z_INDEX));
1784
+ resetStyleValue(this, "border-left");
1785
+ setStyleValue(this, "transition", SHELL_LAYOUT_TRANSITION);
1786
+ resetStyleValue(this, "overflow");
1787
+ resetStyleValue(this, "pointer-events");
1788
+ } else {
1789
+ setStyleValue(this, "position", "relative");
1790
+ resetStyleValue(this, "flex");
1791
+ resetStyleValue(this, "min-width");
1792
+ resetStyleValue(this, "border-left");
1793
+ resetStyleValue(this, "overflow");
1794
+ resetStyleValue(this, "pointer-events");
1795
+ }
1796
+ this._removeBodyMargin();
1797
+ this._hideResizeNub();
1798
+ this._hideEdgeTab();
1799
+ } else {
1800
+ if (useFlexLayout) {
1801
+ setStyleValue(this, "position", "relative");
1802
+ setStyleValue(this, "flex", "0 0 0px");
1803
+ setStyleValue(this, "width", "0px");
1804
+ setStyleValue(this, "min-width", "0px");
1805
+ setStyleValue(this, "height", "0px");
1806
+ setStyleValue(this, "z-index", String(SHELL_Z_INDEX));
1807
+ resetStyleValue(this, "border-left");
1808
+ setStyleValue(this, "transition", SHELL_LAYOUT_TRANSITION);
1809
+ resetStyleValue(this, "overflow");
1810
+ resetStyleValue(this, "pointer-events");
1811
+ } else {
1812
+ setStyleValue(this, "position", "relative");
1813
+ resetStyleValue(this, "flex");
1814
+ resetStyleValue(this, "min-width");
1815
+ resetStyleValue(this, "border-left");
1816
+ resetStyleValue(this, "overflow");
1817
+ resetStyleValue(this, "pointer-events");
1818
+ }
1819
+ this._removeBodyMargin();
1820
+ this._hideResizeNub();
1821
+ this._showEdgeTab();
1822
+ }
1823
+ }
1824
+ _applyBodyMargin(width) {
1825
+ const body = document.body;
1826
+ if (this._savedBodyTransition === null) this._savedBodyTransition = body.style.transition || "";
1827
+ if (!this._resizeDragging) body.style.transition = BODY_MARGIN_TRANSITION;
1828
+ body.style.marginRight = `${width}px`;
1829
+ }
1830
+ _removeBodyMargin() {
1831
+ const body = document.body;
1832
+ body.style.marginRight = "";
1833
+ if (this._bodyMarginTimeoutId !== null) {
1834
+ window.clearTimeout(this._bodyMarginTimeoutId);
1835
+ this._bodyMarginTimeoutId = null;
1836
+ }
1837
+ if (this._savedBodyTransition !== null) {
1838
+ const saved = this._savedBodyTransition;
1839
+ this._bodyMarginTimeoutId = window.setTimeout(() => {
1840
+ this._bodyMarginTimeoutId = null;
1841
+ if (body.style.transition === BODY_MARGIN_TRANSITION) body.style.transition = saved || "";
1842
+ }, 220);
1843
+ this._savedBodyTransition = null;
1844
+ }
1845
+ }
1846
+ _ensureResizeNub() {
1847
+ if (this._resizeNubElement) return this._resizeNubElement;
1848
+ const nub = document.createElement("div");
1849
+ nub.setAttribute("role", "separator");
1850
+ nub.setAttribute("aria-orientation", "vertical");
1851
+ nub.setAttribute("aria-label", "Resize panel");
1852
+ nub.style.cssText = [
1853
+ "position:fixed",
1854
+ "top:0",
1855
+ `width:${RESIZE_NUB_WIDTH}px`,
1856
+ "height:100dvh",
1857
+ "cursor:col-resize",
1858
+ `z-index:${SHELL_Z_INDEX + 1}`,
1859
+ "background:transparent",
1860
+ "user-select:none",
1861
+ "-webkit-user-select:none",
1862
+ "touch-action:none"
1863
+ ].join(";");
1864
+ const indicator = document.createElement("div");
1865
+ indicator.style.cssText = [
1866
+ "position:absolute",
1867
+ "top:0",
1868
+ "left:50%",
1869
+ "transform:translateX(-50%)",
1870
+ "width:2px",
1871
+ "height:100%",
1872
+ "border-radius:1px",
1873
+ "transition:background 150ms ease, width 150ms ease",
1874
+ "pointer-events:none"
1875
+ ].join(";");
1876
+ nub.appendChild(indicator);
1877
+ this._applyResizeNubColors(indicator);
1878
+ nub.addEventListener("mouseenter", () => {
1879
+ const dark = this._isDarkMode();
1880
+ indicator.style.background = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.15)";
1881
+ indicator.style.width = "3px";
1882
+ });
1883
+ nub.addEventListener("mouseleave", () => {
1884
+ if (!this._resizeDragging) {
1885
+ this._applyResizeNubColors(indicator);
1886
+ indicator.style.width = "2px";
1887
+ }
1888
+ });
1889
+ nub.addEventListener("mousedown", this._onResizeStart);
1890
+ nub.addEventListener("touchstart", this._onResizeTouchStart, { passive: false });
1891
+ this._resizeNubElement = nub;
1892
+ return nub;
1893
+ }
1894
+ _applyResizeNubColors(indicator) {
1895
+ const el = indicator ?? this._resizeNubElement?.querySelector("div");
1896
+ if (!el) return;
1897
+ const dark = this._isDarkMode();
1898
+ el.style.background = dark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.06)";
1899
+ }
1900
+ _refreshResizeNubColors() {
1901
+ this._applyResizeNubColors();
1902
+ }
1903
+ _showResizeNub() {
1904
+ const nub = this._ensureResizeNub();
1905
+ if (!nub.parentNode) document.body.appendChild(nub);
1906
+ setStyleValue(nub, "right", `${this._currentPanelWidth - Math.floor(RESIZE_NUB_WIDTH / 2)}px`);
1907
+ setStyleValue(nub, "display", "block");
1908
+ }
1909
+ _hideResizeNub() {
1910
+ if (this._resizeNubElement) setStyleValue(this._resizeNubElement, "display", "none");
1911
+ }
1912
+ _ensureEdgeTab() {
1913
+ if (this._edgeTabElement) return this._edgeTabElement;
1914
+ const tab = document.createElement("button");
1915
+ tab.setAttribute("type", "button");
1916
+ tab.setAttribute("aria-label", "Open assistant");
1917
+ tab.style.cssText = [
1918
+ "position:fixed",
1919
+ "top:50%",
1920
+ "right:0px",
1921
+ "transform:translateY(-50%)",
1922
+ `z-index:${SHELL_Z_INDEX + 2}`,
1923
+ `width:${EDGE_TAB_WIDTH}px`,
1924
+ `height:${EDGE_TAB_HEIGHT}px`,
1925
+ "display:flex",
1926
+ "align-items:center",
1927
+ "justify-content:center",
1928
+ "border-right:none",
1929
+ "border-radius:12px 0 0 12px",
1930
+ "padding:0",
1931
+ "outline:none",
1932
+ "transition:transform 200ms ease,background 150ms ease,color 150ms ease,box-shadow 200ms ease,border-color 150ms ease",
1933
+ "user-select:none",
1934
+ "-webkit-user-select:none"
1935
+ ].join(";");
1936
+ const dot = document.createElement("span");
1937
+ dot.style.cssText = [
1938
+ "position:absolute",
1939
+ "top:8px",
1940
+ "right:10px",
1941
+ "width:7px",
1942
+ "height:7px",
1943
+ "border-radius:50%",
1944
+ "transition:opacity 200ms ease"
1945
+ ].join(";");
1946
+ tab.appendChild(dot);
1947
+ const icon = document.createElement("span");
1948
+ icon.style.cssText = "display:flex;align-items:center;justify-content:center;pointer-events:none;";
1949
+ icon.innerHTML = ICON_MESSAGE;
1950
+ tab.appendChild(icon);
1951
+ tab.addEventListener("mouseenter", () => {
1952
+ const dark = this._isDarkMode();
1953
+ if (this._open && !this._isNarrowViewport) {
1954
+ tab.style.background = dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)";
1955
+ tab.style.boxShadow = "none";
1956
+ tab.style.color = this._resolveColor("foreground");
1957
+ tab.style.cursor = "col-resize";
1958
+ } else {
1959
+ tab.style.transform = "translateY(-50%) translateX(-3px)";
1960
+ tab.style.boxShadow = dark ? "-4px 0 16px rgba(0,0,0,0.3)" : "-4px 0 16px rgba(0,0,0,0.12)";
1961
+ tab.style.color = this._resolveColor("foreground");
1962
+ tab.style.cursor = "pointer";
1963
+ }
1964
+ });
1965
+ tab.addEventListener("mouseleave", () => {
1966
+ this._applyEdgeTabVisual();
1967
+ });
1968
+ tab.addEventListener("mousedown", this._onEdgeTabMouseDown);
1969
+ tab.addEventListener("touchstart", this._onEdgeTabTouchStart, { passive: false });
1970
+ this._edgeTabElement = tab;
1971
+ return tab;
1972
+ }
1973
+ /** Apply visual style (colors, shadow) based on open state — no position change. */
1974
+ _applyEdgeTabVisual() {
1975
+ if (!this._edgeTabElement) return;
1976
+ const tab = this._edgeTabElement;
1977
+ const icon = tab.querySelector("span:last-child");
1978
+ const dot = tab.querySelector("span:first-child");
1979
+ const dark = this._isDarkMode();
1980
+ const bg = this._resolveColor("background");
1981
+ const borderColor = this._resolveColor("border");
1982
+ const mutedFg = this._resolveColor("mutedForeground");
1983
+ const primary = this._resolveColor("primary");
1984
+ tab.style.border = `1px solid ${borderColor}`;
1985
+ tab.style.borderRight = "none";
1986
+ if (this._open && !this._isNarrowViewport) {
1987
+ tab.setAttribute("aria-label", "Close assistant");
1988
+ tab.setAttribute("aria-expanded", "true");
1989
+ if (icon) icon.innerHTML = ICON_CHEVRON_RIGHT;
1990
+ if (dot) dot.style.opacity = "0";
1991
+ tab.style.background = bg;
1992
+ tab.style.boxShadow = "none";
1993
+ tab.style.backdropFilter = "none";
1994
+ tab.style.setProperty("-webkit-backdrop-filter", "none");
1995
+ tab.style.color = mutedFg;
1996
+ tab.style.cursor = "col-resize";
1997
+ tab.style.transform = "translateY(-50%)";
1998
+ } else {
1999
+ tab.setAttribute("aria-label", "Open assistant");
2000
+ tab.setAttribute("aria-expanded", "false");
2001
+ if (icon) icon.innerHTML = ICON_MESSAGE;
2002
+ if (dot) {
2003
+ dot.style.opacity = "1";
2004
+ dot.style.background = primary;
2005
+ }
2006
+ tab.style.background = dark ? "color-mix(in oklch, " + bg + ", white 4%)" : "color-mix(in oklch, " + bg + ", transparent 5%)";
2007
+ tab.style.boxShadow = dark ? "-2px 0 12px rgba(0,0,0,0.25)" : "-2px 0 12px rgba(0,0,0,0.08)";
2008
+ tab.style.backdropFilter = "blur(8px)";
2009
+ tab.style.setProperty("-webkit-backdrop-filter", "blur(8px)");
2010
+ tab.style.color = mutedFg;
2011
+ tab.style.cursor = "pointer";
2012
+ tab.style.transform = "translateY(-50%)";
2013
+ }
2014
+ }
2015
+ /** Set the tab's `right` position immediately (no transition). */
2016
+ _positionEdgeTab(rightPx) {
2017
+ if (this._edgeTabElement) this._edgeTabElement.style.right = `${rightPx}px`;
2018
+ }
2019
+ _showEdgeTab() {
2020
+ const tab = this._ensureEdgeTab();
2021
+ if (!tab.parentNode) document.body.appendChild(tab);
2022
+ setStyleValue(tab, "display", "flex");
2023
+ if (this._open && !this._isNarrowViewport) this._positionEdgeTab(this._currentPanelWidth - 1);
2024
+ else this._positionEdgeTab(0);
2025
+ this._applyEdgeTabVisual();
2026
+ }
2027
+ _hideEdgeTab() {
2028
+ if (this._edgeTabElement) setStyleValue(this._edgeTabElement, "display", "none");
2029
+ }
2030
+ _removeEdgeTab() {
2031
+ if (this._edgeTabElement) {
2032
+ this._edgeTabElement.remove();
2033
+ this._edgeTabElement = null;
2034
+ }
2035
+ }
2036
+ _onEdgeTabMouseDown = (e) => {
2037
+ e.preventDefault();
2038
+ this._edgeTabDragStartX = e.clientX;
2039
+ this._edgeTabDragging = false;
2040
+ document.addEventListener("mousemove", this._onEdgeTabMouseMove);
2041
+ document.addEventListener("mouseup", this._onEdgeTabMouseUp);
2042
+ };
2043
+ _onEdgeTabTouchStart = (e) => {
2044
+ if (e.touches.length !== 1) return;
2045
+ e.preventDefault();
2046
+ this._edgeTabDragStartX = e.touches[0].clientX;
2047
+ this._edgeTabDragging = false;
2048
+ document.addEventListener("touchmove", this._onEdgeTabTouchMove, { passive: false });
2049
+ document.addEventListener("touchend", this._onEdgeTabTouchEnd);
2050
+ };
2051
+ _onEdgeTabMouseMove = (e) => {
2052
+ const dx = Math.abs(e.clientX - this._edgeTabDragStartX);
2053
+ if (!this._edgeTabDragging && dx >= EDGE_TAB_DRAG_THRESHOLD_PX) {
2054
+ this._edgeTabDragging = true;
2055
+ this._promoteEdgeTabDragToResize(this._edgeTabDragStartX);
2056
+ }
2057
+ };
2058
+ _onEdgeTabTouchMove = (e) => {
2059
+ if (e.touches.length !== 1) return;
2060
+ e.preventDefault();
2061
+ const dx = Math.abs(e.touches[0].clientX - this._edgeTabDragStartX);
2062
+ if (!this._edgeTabDragging && dx >= EDGE_TAB_DRAG_THRESHOLD_PX) {
2063
+ this._edgeTabDragging = true;
2064
+ this._promoteEdgeTabDragToResize(this._edgeTabDragStartX);
2065
+ }
2066
+ };
2067
+ _onEdgeTabMouseUp = (_e) => {
2068
+ document.removeEventListener("mousemove", this._onEdgeTabMouseMove);
2069
+ document.removeEventListener("mouseup", this._onEdgeTabMouseUp);
2070
+ if (!this._edgeTabDragging) this.toggleOpen();
2071
+ };
2072
+ _onEdgeTabTouchEnd = (_e) => {
2073
+ document.removeEventListener("touchmove", this._onEdgeTabTouchMove);
2074
+ document.removeEventListener("touchend", this._onEdgeTabTouchEnd);
2075
+ if (!this._edgeTabDragging) this.toggleOpen();
2076
+ };
2077
+ /**
2078
+ * Once the edge tab drag crosses the threshold, hand off to the existing
2079
+ * resize machinery so the panel tracks the pointer smoothly.
2080
+ */
2081
+ _promoteEdgeTabDragToResize(startX) {
2082
+ if (!this._open || this._isNarrowViewport) {
2083
+ this._edgeTabDragging = false;
1841
2084
  return;
1842
2085
  }
1843
- setStyleValue(this, "position", "relative");
1844
- setStyleValue(this, "flex", "0 0 0px");
1845
- setStyleValue(this, "width", "0px");
1846
- setStyleValue(this, "min-width", "0px");
1847
- setStyleValue(this, "height", "0px");
1848
- setStyleValue(this, "z-index", String(SHELL_Z_INDEX));
1849
- resetStyleValue(this, "border-left");
1850
- setStyleValue(this, "transition", SHELL_LAYOUT_TRANSITION);
2086
+ this._resizeDragging = true;
2087
+ this._resizeStartX = startX;
2088
+ this._resizeStartWidth = this._currentPanelWidth;
2089
+ this._showResizeDragOverlay();
2090
+ if (this._agent) setStyleValue(this._agent, "transition", "none");
2091
+ setStyleValue(this, "transition", "none");
2092
+ document.body.style.transition = "none";
2093
+ document.body.style.cursor = "col-resize";
2094
+ document.body.style.userSelect = "none";
2095
+ document.addEventListener("mousemove", this._onResizeMove);
2096
+ document.addEventListener("mouseup", this._onResizeEnd);
2097
+ document.addEventListener("touchmove", this._onResizeTouchMove, { passive: false });
2098
+ document.addEventListener("touchend", this._onResizeTouchEnd);
2099
+ }
2100
+ /**
2101
+ * Run a rAF loop that reads the shell element's actual rendered width
2102
+ * and pins the edge tab to it. Used during flex layout transitions so
2103
+ * the tab tracks the animated flex-basis exactly, frame by frame.
2104
+ */
2105
+ _syncEdgeTabWithPanelTransition() {
2106
+ if (!this._edgeTabElement) return;
2107
+ const start = performance.now();
2108
+ const maxDuration = 300;
2109
+ const tick = () => {
2110
+ if (!this._edgeTabElement || !this._open) return;
2111
+ if (this._isParentFlex()) {
2112
+ const width = this.getBoundingClientRect().width;
2113
+ this._positionEdgeTab(width - 1);
2114
+ }
2115
+ if (performance.now() - start < maxDuration) requestAnimationFrame(tick);
2116
+ };
2117
+ requestAnimationFrame(tick);
2118
+ }
2119
+ _showResizeDragOverlay() {
2120
+ if (this._resizeDragOverlay) return;
2121
+ const overlay = document.createElement("div");
2122
+ overlay.style.cssText = [
2123
+ "position:fixed",
2124
+ "top:0",
2125
+ "left:0",
2126
+ "width:100vw",
2127
+ "height:100vh",
2128
+ "cursor:col-resize",
2129
+ `z-index:${SHELL_Z_INDEX + 1}`,
2130
+ "background:transparent",
2131
+ "user-select:none",
2132
+ "-webkit-user-select:none"
2133
+ ].join(";");
2134
+ document.body.appendChild(overlay);
2135
+ this._resizeDragOverlay = overlay;
2136
+ }
2137
+ _removeResizeDragOverlay() {
2138
+ if (this._resizeDragOverlay) {
2139
+ this._resizeDragOverlay.remove();
2140
+ this._resizeDragOverlay = null;
2141
+ }
2142
+ }
2143
+ _onResizeStart = (e) => {
2144
+ e.preventDefault();
2145
+ this._resizeDragging = true;
2146
+ this._resizeStartX = e.clientX;
2147
+ this._resizeStartWidth = this._currentPanelWidth;
2148
+ this._showResizeDragOverlay();
2149
+ if (this._agent) setStyleValue(this._agent, "transition", "none");
2150
+ setStyleValue(this, "transition", "none");
2151
+ document.body.style.transition = "none";
2152
+ document.body.style.cursor = "col-resize";
2153
+ document.body.style.userSelect = "none";
2154
+ document.addEventListener("mousemove", this._onResizeMove);
2155
+ document.addEventListener("mouseup", this._onResizeEnd);
2156
+ };
2157
+ _onResizeTouchStart = (e) => {
2158
+ if (e.touches.length !== 1) return;
2159
+ e.preventDefault();
2160
+ const touch = e.touches[0];
2161
+ this._resizeDragging = true;
2162
+ this._resizeStartX = touch.clientX;
2163
+ this._resizeStartWidth = this._currentPanelWidth;
2164
+ this._showResizeDragOverlay();
2165
+ if (this._agent) setStyleValue(this._agent, "transition", "none");
2166
+ setStyleValue(this, "transition", "none");
2167
+ document.body.style.transition = "none";
2168
+ document.addEventListener("touchmove", this._onResizeTouchMove, { passive: false });
2169
+ document.addEventListener("touchend", this._onResizeTouchEnd);
2170
+ };
2171
+ _onResizeMove = (e) => {
2172
+ if (!this._resizeDragging) return;
2173
+ const delta = this._resizeStartX - e.clientX;
2174
+ this._applyResizeWidth(this._resizeStartWidth + delta);
2175
+ };
2176
+ _onResizeTouchMove = (e) => {
2177
+ if (!this._resizeDragging || e.touches.length !== 1) return;
2178
+ e.preventDefault();
2179
+ const delta = this._resizeStartX - e.touches[0].clientX;
2180
+ this._applyResizeWidth(this._resizeStartWidth + delta);
2181
+ };
2182
+ _applyResizeWidth(rawWidth) {
2183
+ const width = Math.max(PANEL_MIN_WIDTH, Math.min(rawWidth, PANEL_MAX_WIDTH));
2184
+ this._currentPanelWidth = width;
2185
+ if (this._isParentFlex()) {
2186
+ setStyleValue(this, "flex", `0 0 ${width}px`);
2187
+ setStyleValue(this, "width", `${width}px`);
2188
+ setStyleValue(this, "min-width", `${width}px`);
2189
+ } else {
2190
+ if (this._agent) setStyleValue(this._agent, "width", `${width}px`);
2191
+ document.body.style.marginRight = `${width}px`;
2192
+ }
2193
+ if (this._resizeNubElement) {
2194
+ const rightPos = width - Math.floor(RESIZE_NUB_WIDTH / 2);
2195
+ setStyleValue(this._resizeNubElement, "right", `${rightPos}px`);
2196
+ }
2197
+ if (this._edgeTabElement) this._edgeTabElement.style.right = `${width - 1}px`;
2198
+ }
2199
+ _onResizeEnd = (e) => {
2200
+ this._finishResize(e.clientX);
2201
+ document.removeEventListener("mousemove", this._onResizeMove);
2202
+ document.removeEventListener("mouseup", this._onResizeEnd);
2203
+ };
2204
+ _onResizeTouchEnd = (e) => {
2205
+ const x = e.changedTouches[0]?.clientX ?? this._resizeStartX;
2206
+ this._finishResize(x);
2207
+ document.removeEventListener("touchmove", this._onResizeTouchMove);
2208
+ document.removeEventListener("touchend", this._onResizeTouchEnd);
2209
+ };
2210
+ _finishResize(endX) {
2211
+ this._resizeDragging = false;
2212
+ this._removeResizeDragOverlay();
2213
+ document.body.style.cursor = "";
2214
+ document.body.style.userSelect = "";
2215
+ document.body.style.transition = "";
2216
+ const delta = this._resizeStartX - endX;
2217
+ const finalWidth = this._resizeStartWidth + delta;
2218
+ this._currentPanelWidth = Math.max(PANEL_MIN_WIDTH, Math.min(finalWidth, PANEL_MAX_WIDTH));
2219
+ this._applyDisplayModeAndLayout();
1851
2220
  }
1852
2221
  _composePipTransform(opts) {
1853
2222
  const parts = [];
1854
- if (this._pipPosition === "bottom-center") parts.push("translateX(-50%)");
2223
+ if (PIP_POSITION === "bottom-center") parts.push("translateX(-50%)");
1855
2224
  if (opts.scrollHide) parts.push(`translateY(${PIP_SCROLL_HIDE_OFFSET}px)`);
1856
2225
  return parts.length > 0 ? parts.join(" ") : "none";
1857
2226
  }
1858
- _applyAgentLayout(displayMode) {
2227
+ _applyAgentLayout(displayMode, prevMode) {
1859
2228
  if (!this._agent) return;
1860
2229
  if (displayMode === "inline" && !this._isNarrowViewport) {
1861
2230
  this._detachPipInteractionListeners();
@@ -1863,20 +2232,32 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1863
2232
  this._pipExpanded = false;
1864
2233
  this._pipAnimating = false;
1865
2234
  this._hidePipPill();
1866
- setStyleValue(this._agent, "position", "relative");
1867
- resetStyleValue(this._agent, "top");
1868
- resetStyleValue(this._agent, "right");
1869
- resetStyleValue(this._agent, "bottom");
1870
- resetStyleValue(this._agent, "left");
2235
+ if (this._isParentFlex()) {
2236
+ setStyleValue(this._agent, "position", "relative");
2237
+ resetStyleValue(this._agent, "top");
2238
+ resetStyleValue(this._agent, "right");
2239
+ resetStyleValue(this._agent, "bottom");
2240
+ resetStyleValue(this._agent, "left");
2241
+ setStyleValue(this._agent, "z-index", "auto");
2242
+ setStyleValue(this._agent, "width", "100%");
2243
+ setStyleValue(this._agent, "height", "100%");
2244
+ } else {
2245
+ setStyleValue(this._agent, "position", "fixed");
2246
+ setStyleValue(this._agent, "top", "0");
2247
+ setStyleValue(this._agent, "right", "0");
2248
+ resetStyleValue(this._agent, "bottom");
2249
+ resetStyleValue(this._agent, "left");
2250
+ setStyleValue(this._agent, "z-index", String(SHELL_Z_INDEX));
2251
+ setStyleValue(this._agent, "width", `${this._currentPanelWidth}px`);
2252
+ setStyleValue(this._agent, "height", "100dvh");
2253
+ setStyleValue(this._agent, "border-left", this._panelBorder());
2254
+ }
1871
2255
  resetStyleValue(this._agent, "transform");
1872
2256
  resetStyleValue(this._agent, "max-width");
1873
2257
  resetStyleValue(this._agent, "max-height");
1874
- setStyleValue(this._agent, "z-index", "auto");
1875
- setStyleValue(this._agent, "width", "100%");
1876
- setStyleValue(this._agent, "height", "100%");
1877
2258
  setStyleValue(this._agent, "border-radius", "0");
1878
2259
  setStyleValue(this._agent, "overflow", "hidden");
1879
- resetStyleValue(this._agent, "transition");
2260
+ setStyleValue(this._agent, "transition", this._resizeDragging ? "none" : "width 220ms ease");
1880
2261
  resetStyleValue(this._agent, "opacity");
1881
2262
  resetStyleValue(this._agent, "pointer-events");
1882
2263
  return;
@@ -1905,15 +2286,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1905
2286
  resetStyleValue(this._agent, "pointer-events");
1906
2287
  return;
1907
2288
  }
1908
- const pipPositionStyles = getPipPositionStyles(this._pipPosition);
1909
- const expandedWidth = Math.max(PIP_MIN_COLLAPSED_WIDTH, this._pipWidth);
1910
- const collapsedWidth = Math.max(PIP_MIN_COLLAPSED_WIDTH, Math.round(this._pipWidth * PIP_COLLAPSED_WIDTH_RATIO));
2289
+ const pipPositionStyles = getPipPositionStyles(PIP_POSITION);
2290
+ const expandedWidth = Math.max(PIP_MIN_COLLAPSED_WIDTH, PIP_WIDTH);
2291
+ const collapsedWidth = Math.max(PIP_MIN_COLLAPSED_WIDTH, Math.round(PIP_WIDTH * PIP_COLLAPSED_WIDTH_RATIO));
1911
2292
  const targetWidth = this._pipExpanded ? expandedWidth : collapsedWidth;
1912
- const collapsedHeight = Math.max(PIP_MIN_COLLAPSED_HEIGHT, this._pipHeight);
1913
- const hasExplicitPipHeight = this.hasAttribute("pip-height");
2293
+ const collapsedHeight = Math.max(PIP_MIN_COLLAPSED_HEIGHT, PIP_HEIGHT);
1914
2294
  const measuredHeightFloor = typeof this._pipMeasuredHeight === "number" ? this._pipMeasuredHeight : 0;
1915
2295
  const expandedHeight = Math.max(collapsedHeight, measuredHeightFloor);
1916
- const collapsedPipHeight = hasExplicitPipHeight ? collapsedHeight : PIP_PILL_HEIGHT;
2296
+ const collapsedPipHeight = PIP_PILL_HEIGHT;
1917
2297
  const heightDelta = expandedHeight - collapsedPipHeight;
1918
2298
  let targetHeight;
1919
2299
  let targetBottom;
@@ -1924,13 +2304,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1924
2304
  targetHeight = collapsedPipHeight;
1925
2305
  targetBottom = heightDelta > 0 ? `calc(${heightDelta}px + 16px + env(safe-area-inset-bottom, 0px))` : SAFE_BOTTOM;
1926
2306
  }
2307
+ const enteringFromPanel = prevMode != null && prevMode !== "pip" && this._healthState === "healthy";
1927
2308
  const composedTransform = this._composePipTransform({ scrollHide: this._pipHiddenByScroll });
2309
+ if (enteringFromPanel) setStyleValue(this._agent, "transition", "none");
1928
2310
  setStyleValue(this._agent, "position", "fixed");
1929
2311
  resetStyleValue(this._agent, "top");
1930
2312
  setStyleValue(this._agent, "right", pipPositionStyles.right);
1931
2313
  setStyleValue(this._agent, "left", pipPositionStyles.left);
1932
2314
  setStyleValue(this._agent, "bottom", targetBottom);
1933
- setStyleValue(this._agent, "transform", composedTransform);
1934
2315
  setStyleValue(this._agent, "z-index", String(SHELL_Z_INDEX));
1935
2316
  setStyleValue(this._agent, "width", `min(${targetWidth}px, calc(100vw - 24px))`);
1936
2317
  setStyleValue(this._agent, "height", `${targetHeight}px`);
@@ -1938,7 +2319,22 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1938
2319
  setStyleValue(this._agent, "max-width", "100vw");
1939
2320
  setStyleValue(this._agent, "border-radius", "16px");
1940
2321
  setStyleValue(this._agent, "overflow", "hidden");
1941
- setStyleValue(this._agent, "transition", this._healthState === "healthy" ? PIP_MORPH_TRANSITION : "none");
2322
+ resetStyleValue(this._agent, "border-left");
2323
+ if (enteringFromPanel) {
2324
+ setStyleValue(this._agent, "transform", this._composePipTransform({ scrollHide: true }));
2325
+ setStyleValue(this._agent, "opacity", "0");
2326
+ this._agent.offsetHeight;
2327
+ requestAnimationFrame(() => {
2328
+ if (!this._agent) return;
2329
+ setStyleValue(this._agent, "transition", PIP_MORPH_TRANSITION);
2330
+ setStyleValue(this._agent, "transform", composedTransform);
2331
+ setStyleValue(this._agent, "opacity", "1");
2332
+ });
2333
+ } else {
2334
+ setStyleValue(this._agent, "transform", composedTransform);
2335
+ setStyleValue(this._agent, "transition", this._healthState === "healthy" ? PIP_MORPH_TRANSITION : "none");
2336
+ resetStyleValue(this._agent, "opacity");
2337
+ }
1942
2338
  if (this._healthState === "healthy") {
1943
2339
  const pill = this._ensurePipPill();
1944
2340
  if (!pill.parentNode) this.appendChild(pill);
@@ -1947,6 +2343,16 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1947
2343
  this._attachScrollHideListener();
1948
2344
  this._attachSwipeGestureListeners();
1949
2345
  this._attachWheelFallbackListener();
2346
+ if (enteringFromPanel) {
2347
+ pill.style.opacity = "0";
2348
+ pill.style.pointerEvents = "none";
2349
+ pill.style.transition = "none";
2350
+ requestAnimationFrame(() => {
2351
+ pill.style.transition = PIP_MORPH_TRANSITION;
2352
+ pill.style.opacity = "1";
2353
+ pill.style.pointerEvents = "";
2354
+ });
2355
+ }
1950
2356
  this._applyPipVisualState();
1951
2357
  }
1952
2358
  }
@@ -1956,7 +2362,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1956
2362
  pill.setAttribute("role", "button");
1957
2363
  pill.setAttribute("aria-label", "Open assistant");
1958
2364
  pill.setAttribute("tabindex", "0");
1959
- const pipPositionStyles = getPipPositionStyles(this._pipPosition);
2365
+ const pipPositionStyles = getPipPositionStyles(PIP_POSITION);
1960
2366
  pill.style.cssText = [
1961
2367
  "position:fixed",
1962
2368
  `left:${pipPositionStyles.left}`,
@@ -2342,6 +2748,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2342
2748
  this._pipExpanded = false;
2343
2749
  this._pipAnimating = false;
2344
2750
  this._hidePipPill();
2751
+ this._hideEdgeTab();
2345
2752
  if (this._agent) this._agent.removeAttribute("display-mode");
2346
2753
  this._resetHostLayoutStyles();
2347
2754
  this._resetAgentLayoutStyles();
@@ -2387,7 +2794,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2387
2794
  this._revealFrameId = null;
2388
2795
  setStyleValue(this, "opacity", "1");
2389
2796
  window.setTimeout(() => {
2390
- if (this._healthState === "healthy") resetStyleValue(this, "will-change");
2797
+ resetStyleValue(this, "will-change");
2391
2798
  }, SHELL_REVEAL_FADE_MS);
2392
2799
  });
2393
2800
  }
@@ -2439,6 +2846,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2439
2846
  return;
2440
2847
  }
2441
2848
  this._open = nextOpen;
2849
+ if (!nextOpen) this._currentPanelWidth = PANEL_WIDTH;
2442
2850
  if (options.reflect) this._reflectOpenAttribute(nextOpen);
2443
2851
  this._applyDisplayModeAndLayout();
2444
2852
  if (options.emit) this.dispatchEvent(new CustomEvent(SHELL_OPEN_CHANGE_EVENT, {