@artenx/dshb 0.1.25 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -510,7 +510,8 @@ window.__ModuleLoader__.load({
510
510
  const dialogStyle = {
511
511
  width: "min(92vw, 560px)",
512
512
  maxWidth: "100%",
513
- maxHeight: "80vh",
513
+ maxHeight: "80dvh",
514
+ paddingBottom: "max(env(safe-area-inset-bottom), 12px)",
514
515
  overflow: "auto",
515
516
  overflowX: "hidden",
516
517
  background: "var(--dsw-alias-bg-layer-1)",
@@ -952,7 +953,8 @@ window.__ModuleLoader__.load({
952
953
  style: {
953
954
  width: "min(92vw, 600px)",
954
955
  maxWidth: "100%",
955
- maxHeight: "85vh",
956
+ maxHeight: "85dvh",
957
+ paddingBottom: "max(env(safe-area-inset-bottom), 12px)",
956
958
  overflow: "auto",
957
959
  overflowX: "hidden",
958
960
  background: "var(--dsw-alias-bg-layer-1)",
@@ -1129,7 +1131,7 @@ window.__ModuleLoader__.load({
1129
1131
  /* footer 容器各项间距 16px 偏松 */
1130
1132
  [data-phase] [class*="_root"][class*="osXY9a"] { gap: 8px !important; }
1131
1133
  /* footer 统计行窄屏可横向滑动看全,不溢出页面 */
1132
- [data-phase] [class*="_actions"] { max-width: 100% !important; overflow-x: auto !important; scrollbar-width: none; touch-action: pan-x pan-y !important; }
1134
+ [data-phase] [class*="_actions"] { max-width: 100% !important; overflow-x: auto !important; scrollbar-width: none; touch-action: pan-x pan-y pinch-zoom !important; }
1133
1135
  [data-phase] [class*="_actions"]::-webkit-scrollbar { display: none; }
1134
1136
 
1135
1137
  /* 模型切换菜单:名称完整展示,长名称在菜单项内换行。 */
@@ -1183,7 +1185,7 @@ window.__ModuleLoader__.load({
1183
1185
  overflow-x: auto !important;
1184
1186
  -webkit-overflow-scrolling: touch;
1185
1187
  scrollbar-width: none;
1186
- touch-action: pan-x pan-y !important;
1188
+ touch-action: pan-x pan-y pinch-zoom !important;
1187
1189
  }
1188
1190
  [data-phase] [class*="resultBlockText"]::-webkit-scrollbar,
1189
1191
  [data-phase] [class*="payload"]::-webkit-scrollbar,
@@ -1201,7 +1203,7 @@ window.__ModuleLoader__.load({
1201
1203
  overflow-x: auto !important;
1202
1204
  -webkit-overflow-scrolling: touch;
1203
1205
  scrollbar-width: none;
1204
- touch-action: pan-x pan-y !important;
1206
+ touch-action: pan-x pan-y pinch-zoom !important;
1205
1207
  }
1206
1208
  [data-phase] [class*="jsonPayload"]::-webkit-scrollbar,
1207
1209
  [data-phase] [class*="jsonPreview"]::-webkit-scrollbar,
@@ -1224,7 +1226,7 @@ window.__ModuleLoader__.load({
1224
1226
  overflow-x: auto !important;
1225
1227
  -webkit-overflow-scrolling: touch;
1226
1228
  scrollbar-width: none;
1227
- touch-action: pan-x pan-y !important;
1229
+ touch-action: pan-x pan-y pinch-zoom !important;
1228
1230
  }
1229
1231
  /* xterm uses a transparent helper textarea for all terminal input. Keep it
1230
1232
  focusable and non-zero-sized so mobile browsers can present their keyboard. */
@@ -1251,7 +1253,7 @@ window.__ModuleLoader__.load({
1251
1253
  max-width: 100% !important;
1252
1254
  overflow-x: auto !important;
1253
1255
  overscroll-behavior-x: contain;
1254
- touch-action: pan-x pan-y !important;
1256
+ touch-action: pan-x pan-y pinch-zoom !important;
1255
1257
  }
1256
1258
  [data-phase] [class*="_prompt_1gdtu_"],
1257
1259
  [data-phase] [class*="_promptLine_1gdtu_"],
@@ -1273,7 +1275,7 @@ window.__ModuleLoader__.load({
1273
1275
  overflow-x: auto !important;
1274
1276
  -webkit-overflow-scrolling: touch;
1275
1277
  scrollbar-width: none;
1276
- touch-action: pan-x pan-y !important;
1278
+ touch-action: pan-x pan-y pinch-zoom !important;
1277
1279
  }
1278
1280
  [data-phase] [class*="_output_1gdtu_"]::-webkit-scrollbar { display: none !important; }
1279
1281
  [data-phase] [class*="_line_1gdtu_"] {
@@ -1286,7 +1288,7 @@ window.__ModuleLoader__.load({
1286
1288
  [data-phase] [class*="_copyButton_"] { touch-action: manipulation !important; }
1287
1289
  [data-phase] [class*="_copyAnchor_"],
1288
1290
  [data-phase] [class*="_body_1ye18_"],
1289
- [data-phase] [class*="_root_4qrvp_"] { touch-action: pan-x pan-y !important; }
1291
+ [data-phase] [class*="_root_4qrvp_"] { touch-action: pan-x pan-y pinch-zoom !important; }
1290
1292
 
1291
1293
  /* 上下文用量弹窗(ContextMeter .JObwrW_panel):上游 dsh-client-ui-trajectory 的
1292
1294
  [class*="panel"] 全局规则给它加了 max-width:100%!important,命中本弹窗,使宽度
@@ -1340,10 +1342,19 @@ window.__ModuleLoader__.load({
1340
1342
  z-index: 2 !important;
1341
1343
  display: inline-flex !important;
1342
1344
  align-items: center; justify-content: center;
1343
- width: 28px; height: 28px;
1345
+ width: 32px; height: 32px;
1344
1346
  padding: 0; border: none; background: none; cursor: pointer; color: inherit;
1345
1347
  -webkit-tap-highlight-color: transparent;
1346
1348
  }
1349
+ /* 触控命中区扩展:视觉尺寸不变,可点区域扩到 ~44px(Apple HIG 建议值) */
1350
+ [${FRAME_ATTR}="toggle"]::after,
1351
+ [${FRAME_ATTR}="fab"]::after,
1352
+ [class*="_iconButton"]::after {
1353
+ content: "";
1354
+ position: absolute;
1355
+ inset: -7px;
1356
+ }
1357
+ [class*="_iconButton"] { position: relative !important; }
1347
1358
 
1348
1359
  /* 首页/hero(无活跃会话,session header 不存在)的抽屉切换 FAB:
1349
1360
  浮在顶部相机带下方,避免与 hero 头部内容重叠 */
@@ -1354,7 +1365,7 @@ window.__ModuleLoader__.load({
1354
1365
  z-index: 21 !important;
1355
1366
  display: inline-flex !important;
1356
1367
  align-items: center; justify-content: center;
1357
- width: 38px; height: 38px;
1368
+ width: 44px; height: 44px;
1358
1369
  padding: 0;
1359
1370
  border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;
1360
1371
  border-radius: 50% !important;
@@ -1364,6 +1375,30 @@ window.__ModuleLoader__.load({
1364
1375
  box-shadow: 0 2px 12px rgba(0, 0, 0, .18) !important;
1365
1376
  -webkit-tap-highlight-color: transparent;
1366
1377
  }
1378
+
1379
+ /* iOS 对 computed font-size <16px 的可编辑元素聚焦时自动放大视口;
1380
+ 表单与 composer 提到 16px(消息正文不受影响),聚焦后页面不再错位。 */
1381
+ input, textarea, select, [contenteditable="true"] {
1382
+ font-size: max(16px, 1em) !important;
1383
+ }
1384
+
1385
+ /* 底部安全区(iPhone home 条):viewport-fit=cover 后浏览器 UI 退出底部,
1386
+ home 指示条叠在 composer / 抽屉 / 设置 sheet 上,遮挡发送按钮与会话列表末项。 */
1387
+ [class*="composerSeat"], [data-composer-seat] {
1388
+ padding-bottom: max(env(safe-area-inset-bottom, 0px), 4px) !important;
1389
+ }
1390
+ [${FRAME_ATTR}="frame"] > :first-child {
1391
+ padding-bottom: env(safe-area-inset-bottom, 0px) !important;
1392
+ }
1393
+ [aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])):not(:has([class*="ZuhsRW"])) {
1394
+ max-height: calc(100dvh - 24px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)) !important;
1395
+ }
1396
+ [aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])):not(:has([class*="ZuhsRW"])) > :last-child > :last-child {
1397
+ padding-bottom: max(env(safe-area-inset-bottom, 0px), 24px) !important;
1398
+ }
1399
+
1400
+ /* 会话行触控高度 */
1401
+ [class*="sessionRow"] { min-height: 44px !important; }
1367
1402
  }
1368
1403
 
1369
1404
  /* 宽屏 / 鼠标指针:隐藏切换按钮 */
@@ -1385,7 +1420,7 @@ window.__ModuleLoader__.load({
1385
1420
  if (viewport === null) return () => {};
1386
1421
  const original = viewport.content;
1387
1422
  const sync = () => {
1388
- viewport.content = narrow.matches ? "width=device-width, initial-scale=1, viewport-fit=cover" : original;
1423
+ viewport.content = narrow.matches ? "width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" : original;
1389
1424
  };
1390
1425
  sync();
1391
1426
  narrow.addEventListener("change", sync);
@@ -1516,7 +1551,8 @@ window.__ModuleLoader__.load({
1516
1551
  document.querySelectorAll(`[${FRAME_ATTR}="stats"]`).forEach((el) => {
1517
1552
  if (!el.isConnected || !STATS_LINE_RE.test((el.textContent ?? "").trim())) el.removeAttribute(FRAME_ATTR);
1518
1553
  });
1519
- const cands = Array.from(document.querySelectorAll("span, div")).filter((el) => {
1554
+ const scope = document.querySelector("[class*=\"composerStack\"]") ?? document.querySelector("[class*=\"composer\"]") ?? document;
1555
+ const cands = Array.from(scope.querySelectorAll("span, div")).filter((el) => {
1520
1556
  if (!isStatsText(el)) return false;
1521
1557
  if (getComputedStyle(el).display === "contents") return false;
1522
1558
  return true;
@@ -1538,15 +1574,22 @@ window.__ModuleLoader__.load({
1538
1574
  }
1539
1575
  };
1540
1576
  mark();
1541
- const mo = new MutationObserver(() => {
1542
- if (narrow.matches) mark();
1543
- });
1577
+ let raf = 0;
1578
+ const scheduleMark = () => {
1579
+ if (raf !== 0 || !narrow.matches) return;
1580
+ raf = requestAnimationFrame(() => {
1581
+ raf = 0;
1582
+ mark();
1583
+ });
1584
+ };
1585
+ const mo = new MutationObserver(scheduleMark);
1544
1586
  mo.observe(document.documentElement, {
1545
1587
  childList: true,
1546
1588
  subtree: true
1547
1589
  });
1548
1590
  narrow.addEventListener("change", mark);
1549
1591
  return () => {
1592
+ if (raf !== 0) cancelAnimationFrame(raf);
1550
1593
  mo.disconnect();
1551
1594
  narrow.removeEventListener("change", mark);
1552
1595
  };
@@ -1566,36 +1609,54 @@ window.__ModuleLoader__.load({
1566
1609
  "overflow-y",
1567
1610
  "box-sizing"
1568
1611
  ];
1612
+ /** 幂等写入 important 内联样式:值未变化时跳过,避免 MutationObserver(attributes) 自触发循环。 */
1613
+ function setImportantIfChanged(el, prop, value) {
1614
+ if (el.style.getPropertyValue(prop) === value && el.style.getPropertyPriority(prop) === "important") return;
1615
+ el.style.setProperty(prop, value, "important");
1616
+ }
1617
+ /** 当前视口下的可视区域(手机键盘弹出时 visualViewport 与布局视口错位)。 */
1618
+ function visibleViewport() {
1619
+ const vv = window.visualViewport;
1620
+ if (vv) return {
1621
+ vw: vv.width,
1622
+ vh: vv.offsetTop + vv.height,
1623
+ top: vv.offsetTop
1624
+ };
1625
+ return {
1626
+ vw: window.innerWidth,
1627
+ vh: window.innerHeight,
1628
+ top: 0
1629
+ };
1630
+ }
1569
1631
  /** Keep the model menu anchored to its trigger while fitting it in the viewport. */
1570
1632
  function positionAnchoredMenu(el, triggerRect, maxWidth, maxHeight) {
1571
1633
  const margin = 8;
1572
- const vw = window.innerWidth;
1573
- const vh = window.innerHeight;
1574
- el.style.setProperty("position", "fixed", "important");
1575
- el.style.setProperty("box-sizing", "border-box", "important");
1576
- el.style.setProperty("left", "0", "important");
1577
- el.style.setProperty("right", "auto", "important");
1578
- el.style.setProperty("top", "0", "important");
1579
- el.style.setProperty("bottom", "auto", "important");
1580
- el.style.setProperty("min-width", "0", "important");
1581
- el.style.setProperty("max-width", `${Math.max(0, Math.min(maxWidth, vw - 16))}px`, "important");
1582
- el.style.setProperty("max-height", `${Math.max(0, Math.min(maxHeight, vh - 16))}px`, "important");
1583
- el.style.setProperty("overflow-y", "auto", "important");
1634
+ const { vw, vh } = visibleViewport();
1635
+ setImportantIfChanged(el, "position", "fixed");
1636
+ setImportantIfChanged(el, "box-sizing", "border-box");
1637
+ setImportantIfChanged(el, "left", "0");
1638
+ setImportantIfChanged(el, "right", "auto");
1639
+ setImportantIfChanged(el, "top", "0");
1640
+ setImportantIfChanged(el, "bottom", "auto");
1641
+ setImportantIfChanged(el, "min-width", "0");
1642
+ setImportantIfChanged(el, "max-width", `${Math.max(0, Math.min(maxWidth, vw - 16))}px`);
1643
+ setImportantIfChanged(el, "max-height", `${Math.max(0, Math.min(maxHeight, vh - 16))}px`);
1644
+ setImportantIfChanged(el, "overflow-y", "auto");
1584
1645
  let width = el.offsetWidth;
1585
1646
  let height = el.offsetHeight;
1586
- const spaceAbove = Math.max(0, triggerRect.top - 16);
1647
+ const { top: viewportTop } = visibleViewport();
1648
+ const spaceAbove = Math.max(0, triggerRect.top - viewportTop - 16);
1587
1649
  const spaceBelow = Math.max(0, vh - triggerRect.bottom - 16);
1588
1650
  const expandUp = height <= spaceAbove || spaceAbove >= spaceBelow;
1589
1651
  const available = expandUp ? spaceAbove : spaceBelow;
1590
1652
  if (height > available) {
1591
- el.style.setProperty("max-height", `${Math.max(0, available - margin)}px`, "important");
1653
+ setImportantIfChanged(el, "max-height", `${Math.max(0, available - margin)}px`);
1592
1654
  height = el.offsetHeight;
1593
1655
  width = el.offsetWidth;
1594
1656
  }
1595
1657
  const top = expandUp ? triggerRect.top - margin - height : triggerRect.bottom + margin;
1596
- const left = Math.max(margin, Math.min(triggerRect.right - width, vw - width - margin));
1597
- el.style.setProperty("left", `${left}px`, "important");
1598
- el.style.setProperty("top", `${Math.max(margin, Math.min(top, vh - height - margin))}px`, "important");
1658
+ setImportantIfChanged(el, "left", `${Math.max(margin, Math.min(triggerRect.right - width, vw - width - margin))}px`);
1659
+ setImportantIfChanged(el, "top", `${Math.max(viewportTop + margin, Math.min(top, vh - height - margin))}px`);
1599
1660
  }
1600
1661
  /**
1601
1662
  * 模型名多为纯连字符词(DeepSeek-V4-Flash-Vision-Exp);white-space:normal 下
@@ -1664,9 +1725,13 @@ window.__ModuleLoader__.load({
1664
1725
  function installModelMenuPosition(ctx) {
1665
1726
  ctx.effect(() => {
1666
1727
  const narrow = window.matchMedia(NARROW_QUERY);
1728
+ let fallbackLast = 0;
1667
1729
  const findMenus = () => {
1668
1730
  const byHash = Array.from(document.querySelectorAll("[class*=\"7KE1Ra_menu\"]"));
1669
1731
  if (byHash.length > 0) return byHash;
1732
+ const now = Date.now();
1733
+ if (now - fallbackLast < 500) return [];
1734
+ fallbackLast = now;
1670
1735
  return Array.from(document.querySelectorAll("[class*=\"_menu\"]")).filter((el) => el.querySelector("[class*=\"cellLabel\"], [class*=\"modelName\"]") !== null);
1671
1736
  };
1672
1737
  const findTrigger = (menu) => {
@@ -1727,16 +1792,42 @@ window.__ModuleLoader__.load({
1727
1792
  const mo = new MutationObserver(() => requestAnimationFrame(sync));
1728
1793
  mo.observe(document.documentElement, {
1729
1794
  childList: true,
1730
- subtree: true
1795
+ subtree: true,
1796
+ attributes: true,
1797
+ attributeFilter: ["style"]
1731
1798
  });
1732
1799
  narrow.addEventListener("change", sync);
1733
1800
  window.addEventListener("resize", sync);
1734
1801
  window.addEventListener("scroll", sync, true);
1802
+ window.visualViewport?.addEventListener("resize", sync);
1803
+ window.visualViewport?.addEventListener("scroll", sync);
1804
+ let lastPointerTarget = null;
1805
+ const keyboardLikelyOpen = () => {
1806
+ const vv = window.visualViewport;
1807
+ if (!vv) return false;
1808
+ return window.innerHeight - vv.height > 150;
1809
+ };
1810
+ const onPointerDown = (event) => {
1811
+ lastPointerTarget = event.target instanceof Element ? event.target : null;
1812
+ };
1813
+ const onFocusIn = (event) => {
1814
+ if (!narrow.matches || !(event.target instanceof Element) || keyboardLikelyOpen()) return;
1815
+ const input = event.target.closest("input");
1816
+ if (!input || input.closest("[class*=\"7KE1Ra_menu\"], [class*=\"_menu\"]") === null) return;
1817
+ if (lastPointerTarget !== null && (lastPointerTarget === input || input.contains(lastPointerTarget))) return;
1818
+ input.blur();
1819
+ };
1820
+ document.addEventListener("pointerdown", onPointerDown, true);
1821
+ document.addEventListener("focusin", onFocusIn, true);
1735
1822
  return () => {
1736
1823
  mo.disconnect();
1737
1824
  narrow.removeEventListener("change", sync);
1738
1825
  window.removeEventListener("resize", sync);
1739
1826
  window.removeEventListener("scroll", sync, true);
1827
+ window.visualViewport?.removeEventListener("resize", sync);
1828
+ window.visualViewport?.removeEventListener("scroll", sync);
1829
+ document.removeEventListener("pointerdown", onPointerDown, true);
1830
+ document.removeEventListener("focusin", onFocusIn, true);
1740
1831
  };
1741
1832
  }, "dshb-mobile: model menu position");
1742
1833
  }
@@ -1757,20 +1848,22 @@ window.__ModuleLoader__.load({
1757
1848
  const rootRect = root.getBoundingClientRect();
1758
1849
  if (rootRect.width === 0 && rootRect.height === 0) continue;
1759
1850
  const gap = 8;
1760
- panel.style.setProperty("position", "fixed", "important");
1761
- panel.style.setProperty("top", "auto", "important");
1762
- panel.style.setProperty("bottom", `${window.innerHeight - rootRect.top + gap}px`, "important");
1763
- panel.style.setProperty("left", "auto", "important");
1764
- panel.style.setProperty("right", `${Math.max(gap, window.innerWidth - rootRect.right)}px`, "important");
1765
- panel.style.setProperty("max-height", `${Math.max(160, rootRect.top - 16)}px`, "important");
1766
- panel.style.setProperty("overflow-y", "auto", "important");
1851
+ setImportantIfChanged(panel, "position", "fixed");
1852
+ setImportantIfChanged(panel, "top", "auto");
1853
+ setImportantIfChanged(panel, "bottom", `${window.innerHeight - rootRect.top + gap}px`);
1854
+ setImportantIfChanged(panel, "left", "auto");
1855
+ setImportantIfChanged(panel, "right", `${Math.max(gap, window.innerWidth - rootRect.right)}px`);
1856
+ setImportantIfChanged(panel, "max-height", `${Math.max(160, rootRect.top - 16)}px`);
1857
+ setImportantIfChanged(panel, "overflow-y", "auto");
1767
1858
  }
1768
1859
  };
1769
1860
  sync();
1770
1861
  const mo = new MutationObserver(() => requestAnimationFrame(sync));
1771
1862
  mo.observe(document.documentElement, {
1772
1863
  childList: true,
1773
- subtree: true
1864
+ subtree: true,
1865
+ attributes: true,
1866
+ attributeFilter: ["style"]
1774
1867
  });
1775
1868
  narrow.addEventListener("change", sync);
1776
1869
  window.addEventListener("resize", sync);
@@ -1800,6 +1893,47 @@ window.__ModuleLoader__.load({
1800
1893
  return () => document.removeEventListener("pointerdown", focusInput, true);
1801
1894
  }, "dshb-mobile: terminal input focus");
1802
1895
  }
1896
+ /**
1897
+ * iOS 键盘只缩小 visualViewport,布局视口不动,sticky 在滚动容器底部的
1898
+ * composer 会整个留在键盘后面(发送按钮不可达、最新消息被遮)。监听
1899
+ * visualViewport,键盘开启时把 composer seat 上移键盘高度并在下一帧把
1900
+ * 会话滚动补到最底;Android 由 meta 的 interactive-widget=resizes-content
1901
+ * 让布局视口直接收缩,无需此补偿。
1902
+ */
1903
+ function installComposerKeyboardLift(ctx) {
1904
+ ctx.effect(() => {
1905
+ const mobile = window.matchMedia(MOBILE_QUERY);
1906
+ const vv = window.visualViewport;
1907
+ if (!vv) return () => {};
1908
+ let raf = 0;
1909
+ const seat = () => document.querySelector("[class*=\"composerSeat\"]") ?? document.querySelector("[data-composer-seat]");
1910
+ const sync = () => {
1911
+ if (raf !== 0) return;
1912
+ raf = requestAnimationFrame(() => {
1913
+ raf = 0;
1914
+ if (!mobile.matches) return;
1915
+ const el = seat();
1916
+ if (el === null) return;
1917
+ const occluded = window.innerHeight - vv.height - vv.offsetTop;
1918
+ if (occluded > 150) {
1919
+ el.style.transform = `translateY(${-occluded}px)`;
1920
+ const body = document.querySelector("[class*=\"scrollBody\"]");
1921
+ body?.scrollTo({ top: body.scrollHeight });
1922
+ } else if (el.style.transform !== "") el.style.transform = "";
1923
+ });
1924
+ };
1925
+ sync();
1926
+ vv.addEventListener("resize", sync);
1927
+ vv.addEventListener("scroll", sync);
1928
+ return () => {
1929
+ if (raf !== 0) cancelAnimationFrame(raf);
1930
+ const el = seat();
1931
+ if (el !== null) el.style.transform = "";
1932
+ vv.removeEventListener("resize", sync);
1933
+ vv.removeEventListener("scroll", sync);
1934
+ };
1935
+ }, "dshb-mobile: composer keyboard lift");
1936
+ }
1803
1937
  /** 目录抽屉切换按钮(仅切换,不含文件浏览/日志导出等功能按钮)。 */
1804
1938
  function DrawerToggle({ toggleSidebar }) {
1805
1939
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -1890,6 +2024,7 @@ window.__ModuleLoader__.load({
1890
2024
  function installMobile(ctx) {
1891
2025
  installStyles(ctx);
1892
2026
  installMobileTerminalInputFocus(ctx);
2027
+ installComposerKeyboardLift(ctx);
1893
2028
  installViewport(ctx);
1894
2029
  installFrameMarker(ctx);
1895
2030
  installStatsLine(ctx);
@@ -1930,7 +2065,7 @@ window.__ModuleLoader__.load({
1930
2065
  const dialog = document.createElement("div");
1931
2066
  dialog.setAttribute("role", "dialog");
1932
2067
  dialog.setAttribute("aria-label", `修改 ${workspaceTitle} 的项目指令`);
1933
- dialog.style.cssText = "box-sizing:border-box;width:min(92vw,600px);max-height:85vh;overflow:auto;padding:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)";
2068
+ dialog.style.cssText = "box-sizing:border-box;width:min(92vw,600px);max-height:85dvh;overflow:auto;padding:20px;padding-bottom:max(env(safe-area-inset-bottom),20px);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)";
1934
2069
  const title = document.createElement("h2");
1935
2070
  title.textContent = `项目指令 - ${workspaceTitle}`;
1936
2071
  title.style.cssText = "margin:0 0 10px;font-size:16px";
@@ -2112,7 +2247,14 @@ window.__ModuleLoader__.load({
2112
2247
  }
2113
2248
  }
2114
2249
  };
2115
- new MutationObserver(scan).observe(document.body, {
2250
+ let raf = 0;
2251
+ new MutationObserver(() => {
2252
+ if (raf !== 0) return;
2253
+ raf = requestAnimationFrame(() => {
2254
+ raf = 0;
2255
+ scan();
2256
+ });
2257
+ }).observe(document.body, {
2116
2258
  childList: true,
2117
2259
  subtree: true
2118
2260
  });
@@ -3161,9 +3303,9 @@ window.__ModuleLoader__.load({
3161
3303
  onClick: () => copyText(archive.originSessionId),
3162
3304
  style: {
3163
3305
  ...secondaryBtn,
3164
- height: 22,
3165
- padding: "0 6px",
3166
- fontSize: 10
3306
+ height: 30,
3307
+ padding: "0 10px",
3308
+ fontSize: 11
3167
3309
  },
3168
3310
  children: "复制 ID"
3169
3311
  })]
@@ -4578,7 +4720,10 @@ window.__ModuleLoader__.load({
4578
4720
  依赖祖先 overflow:visible 才能展开到输入框之外,禁止给任何中间祖先加裁剪。 */
4579
4721
  html, body { overflow-x: hidden !important; max-width: 100vw !important }
4580
4722
  * { min-width: 0 !important; }
4581
- table { display: block !important; overflow-x: auto !important; }
4723
+ /* 表格不设 display:block:上游 markdown 表格已自带滚动容器(tableScroll),
4724
+ display:block 会破坏 caption/sticky 语义且与上游 width:max-content 冲突;
4725
+ 这里仅保留溢出约束,宽表由上游 wrapper 横向滚动。 */
4726
+ table { max-width: 100% !important; overflow-x: auto !important; }
4582
4727
  img, video, canvas, svg { max-width: 100% !important; height: auto !important }
4583
4728
  pre, code { white-space: pre-wrap !important; word-break: break-all !important }
4584
4729
  input, select, textarea { max-width: 100% !important; }
@@ -4651,20 +4796,25 @@ window.__ModuleLoader__.load({
4651
4796
  "认证": "auth",
4652
4797
  "工作节点": "dshb-nodes"
4653
4798
  };
4799
+ let raf = 0;
4654
4800
  new MutationObserver(() => {
4655
- const cells = document.querySelectorAll("button[class*=\"navCell\"]");
4656
- for (const cell of Array.from(cells)) {
4657
- const label = cell.querySelector("span[class*=\"navLabel\"]");
4658
- if (!label) continue;
4659
- const text = label.textContent ?? "";
4660
- const iconKey = ICON_LABELS[text];
4661
- if (!iconKey) continue;
4662
- const iconSlot = cell.querySelector("[class*=\"navIcon\"]");
4663
- if (!iconSlot) continue;
4664
- if (iconSlot.getAttribute("data-dshb-icon") === iconKey) continue;
4665
- iconSlot.innerHTML = ICON_SVG[iconKey] ?? "";
4666
- iconSlot.setAttribute("data-dshb-icon", iconKey);
4667
- }
4801
+ if (raf !== 0) return;
4802
+ raf = requestAnimationFrame(() => {
4803
+ raf = 0;
4804
+ const cells = document.querySelectorAll("button[class*=\"navCell\"]");
4805
+ for (const cell of Array.from(cells)) {
4806
+ const label = cell.querySelector("span[class*=\"navLabel\"]");
4807
+ if (!label) continue;
4808
+ const text = label.textContent ?? "";
4809
+ const iconKey = ICON_LABELS[text];
4810
+ if (!iconKey) continue;
4811
+ const iconSlot = cell.querySelector("[class*=\"navIcon\"]");
4812
+ if (!iconSlot) continue;
4813
+ if (iconSlot.getAttribute("data-dshb-icon") === iconKey) continue;
4814
+ iconSlot.innerHTML = ICON_SVG[iconKey] ?? "";
4815
+ iconSlot.setAttribute("data-dshb-icon", iconKey);
4816
+ }
4817
+ });
4668
4818
  }).observe(document.body, {
4669
4819
  childList: true,
4670
4820
  subtree: true