@artenx/dshb 0.1.26 → 0.1.28

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
  /* 模型切换菜单:名称完整展示,长名称在菜单项内换行。 */
@@ -1156,19 +1158,8 @@ window.__ModuleLoader__.load({
1156
1158
  overflow: visible !important;
1157
1159
  }
1158
1160
 
1159
- /* 模型选择器弹窗:JS(installModelMenuPosition)在窄屏下改为 position:fixed
1160
- 视口级浮层(fixed 不受祖先 overflow 裁剪),这里仅做静态兜底(JS 未生效时
1161
- 仍从触发按钮左缘向右展开、宽度收进视口)。 */
1162
- [class*="7KE1Ra_menu"],
1163
- [class*="_menu"]:has([class*="modelName"]) {
1164
- left: 0 !important;
1165
- right: auto !important;
1166
- width: calc(100vw - 32px) !important;
1167
- min-width: 0 !important;
1168
- max-width: calc(100vw - 32px) !important;
1169
- overflow-y: auto !important;
1170
- max-height: calc(100vh - 120px) !important;
1171
- }
1161
+ /* 菜单定位完全交还上游(0.2.1-rc.2 起菜单 position:fixed 且自带视口钳制;
1162
+ 此前插件的 left/width !important 兜底会覆盖上游 place() 的计算结果)。 */
1172
1163
 
1173
1164
  /* 工具调用执行结果/代码/JSON:窄屏可横向滑动。details 抽屉宽 min(92%,420px)
1174
1165
  较窄,命令行输出/JSON 长行溢出被外层 overflow:hidden 裁剪看不到。命令行输出
@@ -1183,7 +1174,7 @@ window.__ModuleLoader__.load({
1183
1174
  overflow-x: auto !important;
1184
1175
  -webkit-overflow-scrolling: touch;
1185
1176
  scrollbar-width: none;
1186
- touch-action: pan-x pan-y !important;
1177
+ touch-action: pan-x pan-y pinch-zoom !important;
1187
1178
  }
1188
1179
  [data-phase] [class*="resultBlockText"]::-webkit-scrollbar,
1189
1180
  [data-phase] [class*="payload"]::-webkit-scrollbar,
@@ -1201,7 +1192,7 @@ window.__ModuleLoader__.load({
1201
1192
  overflow-x: auto !important;
1202
1193
  -webkit-overflow-scrolling: touch;
1203
1194
  scrollbar-width: none;
1204
- touch-action: pan-x pan-y !important;
1195
+ touch-action: pan-x pan-y pinch-zoom !important;
1205
1196
  }
1206
1197
  [data-phase] [class*="jsonPayload"]::-webkit-scrollbar,
1207
1198
  [data-phase] [class*="jsonPreview"]::-webkit-scrollbar,
@@ -1224,7 +1215,7 @@ window.__ModuleLoader__.load({
1224
1215
  overflow-x: auto !important;
1225
1216
  -webkit-overflow-scrolling: touch;
1226
1217
  scrollbar-width: none;
1227
- touch-action: pan-x pan-y !important;
1218
+ touch-action: pan-x pan-y pinch-zoom !important;
1228
1219
  }
1229
1220
  /* xterm uses a transparent helper textarea for all terminal input. Keep it
1230
1221
  focusable and non-zero-sized so mobile browsers can present their keyboard. */
@@ -1251,7 +1242,7 @@ window.__ModuleLoader__.load({
1251
1242
  max-width: 100% !important;
1252
1243
  overflow-x: auto !important;
1253
1244
  overscroll-behavior-x: contain;
1254
- touch-action: pan-x pan-y !important;
1245
+ touch-action: pan-x pan-y pinch-zoom !important;
1255
1246
  }
1256
1247
  [data-phase] [class*="_prompt_1gdtu_"],
1257
1248
  [data-phase] [class*="_promptLine_1gdtu_"],
@@ -1273,7 +1264,7 @@ window.__ModuleLoader__.load({
1273
1264
  overflow-x: auto !important;
1274
1265
  -webkit-overflow-scrolling: touch;
1275
1266
  scrollbar-width: none;
1276
- touch-action: pan-x pan-y !important;
1267
+ touch-action: pan-x pan-y pinch-zoom !important;
1277
1268
  }
1278
1269
  [data-phase] [class*="_output_1gdtu_"]::-webkit-scrollbar { display: none !important; }
1279
1270
  [data-phase] [class*="_line_1gdtu_"] {
@@ -1286,7 +1277,7 @@ window.__ModuleLoader__.load({
1286
1277
  [data-phase] [class*="_copyButton_"] { touch-action: manipulation !important; }
1287
1278
  [data-phase] [class*="_copyAnchor_"],
1288
1279
  [data-phase] [class*="_body_1ye18_"],
1289
- [data-phase] [class*="_root_4qrvp_"] { touch-action: pan-x pan-y !important; }
1280
+ [data-phase] [class*="_root_4qrvp_"] { touch-action: pan-x pan-y pinch-zoom !important; }
1290
1281
 
1291
1282
  /* 上下文用量弹窗(ContextMeter .JObwrW_panel):上游 dsh-client-ui-trajectory 的
1292
1283
  [class*="panel"] 全局规则给它加了 max-width:100%!important,命中本弹窗,使宽度
@@ -1340,10 +1331,19 @@ window.__ModuleLoader__.load({
1340
1331
  z-index: 2 !important;
1341
1332
  display: inline-flex !important;
1342
1333
  align-items: center; justify-content: center;
1343
- width: 28px; height: 28px;
1334
+ width: 32px; height: 32px;
1344
1335
  padding: 0; border: none; background: none; cursor: pointer; color: inherit;
1345
1336
  -webkit-tap-highlight-color: transparent;
1346
1337
  }
1338
+ /* 触控命中区扩展:视觉尺寸不变,可点区域扩到 ~44px(Apple HIG 建议值) */
1339
+ [${FRAME_ATTR}="toggle"]::after,
1340
+ [${FRAME_ATTR}="fab"]::after,
1341
+ [class*="_iconButton"]::after {
1342
+ content: "";
1343
+ position: absolute;
1344
+ inset: -7px;
1345
+ }
1346
+ [class*="_iconButton"] { position: relative !important; }
1347
1347
 
1348
1348
  /* 首页/hero(无活跃会话,session header 不存在)的抽屉切换 FAB:
1349
1349
  浮在顶部相机带下方,避免与 hero 头部内容重叠 */
@@ -1354,7 +1354,7 @@ window.__ModuleLoader__.load({
1354
1354
  z-index: 21 !important;
1355
1355
  display: inline-flex !important;
1356
1356
  align-items: center; justify-content: center;
1357
- width: 38px; height: 38px;
1357
+ width: 44px; height: 44px;
1358
1358
  padding: 0;
1359
1359
  border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;
1360
1360
  border-radius: 50% !important;
@@ -1364,6 +1364,30 @@ window.__ModuleLoader__.load({
1364
1364
  box-shadow: 0 2px 12px rgba(0, 0, 0, .18) !important;
1365
1365
  -webkit-tap-highlight-color: transparent;
1366
1366
  }
1367
+
1368
+ /* iOS 对 computed font-size <16px 的可编辑元素聚焦时自动放大视口;
1369
+ 表单与 composer 提到 16px(消息正文不受影响),聚焦后页面不再错位。 */
1370
+ input, textarea, select, [contenteditable="true"] {
1371
+ font-size: max(16px, 1em) !important;
1372
+ }
1373
+
1374
+ /* 底部安全区(iPhone home 条):viewport-fit=cover 后浏览器 UI 退出底部,
1375
+ home 指示条叠在 composer / 抽屉 / 设置 sheet 上,遮挡发送按钮与会话列表末项。 */
1376
+ [class*="composerSeat"], [data-composer-seat] {
1377
+ padding-bottom: max(env(safe-area-inset-bottom, 0px), 4px) !important;
1378
+ }
1379
+ [${FRAME_ATTR}="frame"] > :first-child {
1380
+ padding-bottom: env(safe-area-inset-bottom, 0px) !important;
1381
+ }
1382
+ [aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])):not(:has([class*="ZuhsRW"])) {
1383
+ max-height: calc(100dvh - 24px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)) !important;
1384
+ }
1385
+ [aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])):not(:has([class*="ZuhsRW"])) > :last-child > :last-child {
1386
+ padding-bottom: max(env(safe-area-inset-bottom, 0px), 24px) !important;
1387
+ }
1388
+
1389
+ /* 会话行触控高度 */
1390
+ [class*="sessionRow"] { min-height: 44px !important; }
1367
1391
  }
1368
1392
 
1369
1393
  /* 宽屏 / 鼠标指针:隐藏切换按钮 */
@@ -1385,7 +1409,7 @@ window.__ModuleLoader__.load({
1385
1409
  if (viewport === null) return () => {};
1386
1410
  const original = viewport.content;
1387
1411
  const sync = () => {
1388
- viewport.content = narrow.matches ? "width=device-width, initial-scale=1, viewport-fit=cover" : original;
1412
+ viewport.content = narrow.matches ? "width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" : original;
1389
1413
  };
1390
1414
  sync();
1391
1415
  narrow.addEventListener("change", sync);
@@ -1560,69 +1584,11 @@ window.__ModuleLoader__.load({
1560
1584
  };
1561
1585
  }, "dshb-mobile: stats line");
1562
1586
  }
1563
- /** 浮层 fixed 定位需管理的内联样式键,清理时逐一移除以交还上游 CSS。 */
1564
- const SHEET_PROPS = [
1565
- "position",
1566
- "width",
1567
- "left",
1568
- "right",
1569
- "top",
1570
- "bottom",
1571
- "min-width",
1572
- "max-width",
1573
- "max-height",
1574
- "overflow-y",
1575
- "box-sizing"
1576
- ];
1577
1587
  /** 幂等写入 important 内联样式:值未变化时跳过,避免 MutationObserver(attributes) 自触发循环。 */
1578
1588
  function setImportantIfChanged(el, prop, value) {
1579
1589
  if (el.style.getPropertyValue(prop) === value && el.style.getPropertyPriority(prop) === "important") return;
1580
1590
  el.style.setProperty(prop, value, "important");
1581
1591
  }
1582
- /** 当前视口下的可视区域(手机键盘弹出时 visualViewport 与布局视口错位)。 */
1583
- function visibleViewport() {
1584
- const vv = window.visualViewport;
1585
- if (vv) return {
1586
- vw: vv.width,
1587
- vh: vv.offsetTop + vv.height,
1588
- top: vv.offsetTop
1589
- };
1590
- return {
1591
- vw: window.innerWidth,
1592
- vh: window.innerHeight,
1593
- top: 0
1594
- };
1595
- }
1596
- /** Keep the model menu anchored to its trigger while fitting it in the viewport. */
1597
- function positionAnchoredMenu(el, triggerRect, maxWidth, maxHeight) {
1598
- const margin = 8;
1599
- const { vw, vh } = visibleViewport();
1600
- setImportantIfChanged(el, "position", "fixed");
1601
- setImportantIfChanged(el, "box-sizing", "border-box");
1602
- setImportantIfChanged(el, "left", "0");
1603
- setImportantIfChanged(el, "right", "auto");
1604
- setImportantIfChanged(el, "top", "0");
1605
- setImportantIfChanged(el, "bottom", "auto");
1606
- setImportantIfChanged(el, "min-width", "0");
1607
- setImportantIfChanged(el, "max-width", `${Math.max(0, Math.min(maxWidth, vw - 16))}px`);
1608
- setImportantIfChanged(el, "max-height", `${Math.max(0, Math.min(maxHeight, vh - 16))}px`);
1609
- setImportantIfChanged(el, "overflow-y", "auto");
1610
- let width = el.offsetWidth;
1611
- let height = el.offsetHeight;
1612
- const { top: viewportTop } = visibleViewport();
1613
- const spaceAbove = Math.max(0, triggerRect.top - viewportTop - 16);
1614
- const spaceBelow = Math.max(0, vh - triggerRect.bottom - 16);
1615
- const expandUp = height <= spaceAbove || spaceAbove >= spaceBelow;
1616
- const available = expandUp ? spaceAbove : spaceBelow;
1617
- if (height > available) {
1618
- setImportantIfChanged(el, "max-height", `${Math.max(0, available - margin)}px`);
1619
- height = el.offsetHeight;
1620
- width = el.offsetWidth;
1621
- }
1622
- const top = expandUp ? triggerRect.top - margin - height : triggerRect.bottom + margin;
1623
- setImportantIfChanged(el, "left", `${Math.max(margin, Math.min(triggerRect.right - width, vw - width - margin))}px`);
1624
- setImportantIfChanged(el, "top", `${Math.max(viewportTop + margin, Math.min(top, vh - height - margin))}px`);
1625
- }
1626
1592
  /**
1627
1593
  * 模型名多为纯连字符词(DeepSeek-V4-Flash-Vision-Exp);white-space:normal 下
1628
1594
  * 每个 "-" 都是软断点,名字一旦超列宽就在第一个连字符处断行("换行太早")。
@@ -1674,18 +1640,13 @@ window.__ModuleLoader__.load({
1674
1640
  n.style.removeProperty("font-size");
1675
1641
  }
1676
1642
  }
1677
- /** 清除 anchored menu 写入的内联样式,交还上游 CSS。 */
1678
- function clearFixedSheet(el) {
1679
- if (el === null || el === void 0) return;
1680
- for (const key of SHEET_PROPS) el.style.removeProperty(key);
1681
- }
1682
1643
  /**
1683
- * 模型选择弹窗(dsh-client-ui-model-selection)是两级 pane 的同一个 `_7KE1Ra_menu`
1684
- * 元素:一级 Model/Effort 单元格列表(cellLabel),点击后切到模型列表(modelName)
1685
- * 或档位列表。上游默认 `position:absolute;right:0` 贴触发按钮右缘,触发按钮在
1686
- * 可横向滚动的 stats 行内时菜单会整体出屏。两级 pane 都必须接管(不能只按
1687
- * modelName 匹配,否则一级 pane 漏掉)。所有视口都把菜单锚定到触发按钮,
1688
- * 在上方/下方可用空间中选择更合适的一侧,避免上游空间不足时把菜单钳到屏幕顶端。
1644
+ * 模型选择弹窗(dsh-client-ui-model-selection):上游 0.2.1-rc.2 起菜单已是
1645
+ * `position:fixed` 并自带 place() 视口钳制定位(portal 到 body,ResizeObserver
1646
+ * 跟随尺寸变化)。插件不再接管定位——此前写入的 fixed/left/top 与上游 menuPos
1647
+ * 内联样式互相拉扯;且对自动聚焦搜索框的 blur 拦截会触发上游 onBlur 的
1648
+ * close()(焦点移出菜单即关闭),手机上表现为菜单刚打开就被关掉("弹不出来")。
1649
+ * 这里只保留窄屏下的视觉适配:模型名不换行连字符 + 按最宽名字缩放字号。
1689
1650
  */
1690
1651
  function installModelMenuPosition(ctx) {
1691
1652
  ctx.effect(() => {
@@ -1699,45 +1660,27 @@ window.__ModuleLoader__.load({
1699
1660
  fallbackLast = now;
1700
1661
  return Array.from(document.querySelectorAll("[class*=\"_menu\"]")).filter((el) => el.querySelector("[class*=\"cellLabel\"], [class*=\"modelName\"]") !== null);
1701
1662
  };
1702
- const findTrigger = (menu) => {
1703
- if (menu.id) {
1704
- const controlled = Array.from(document.querySelectorAll("[aria-controls]")).find((el) => el.getAttribute("aria-controls") === menu.id);
1705
- if (controlled) return controlled;
1706
- }
1707
- return document.querySelector("[class*=\"7KE1Ra_trigger\"][aria-expanded=\"true\"]");
1708
- };
1709
1663
  let fittedVw = 0;
1710
1664
  let fittedSig = -1;
1711
1665
  let fittedMenus = [];
1712
- let positionedMenus = [];
1713
1666
  const sync = () => {
1714
- const menus = findMenus();
1715
1667
  let vw = 0;
1716
1668
  let sig = 0;
1717
1669
  const modelMenus = [];
1718
- const visibleMenus = [];
1719
- for (const menu of menus) {
1720
- const trigger = findTrigger(menu);
1721
- if (trigger === null || trigger.getAttribute("aria-expanded") !== "true") continue;
1722
- const rect = menu.getBoundingClientRect();
1723
- if (rect.width === 0 && rect.height === 0) continue;
1724
- positionAnchoredMenu(menu, trigger.getBoundingClientRect(), Math.min(420, window.innerWidth - 16), 360);
1725
- visibleMenus.push(menu);
1726
- if (menu.querySelector("[class*=\"modelName\"]")) {
1727
- modelMenus.push(menu);
1728
- if (narrow.matches) {
1729
- applyNonBreakingHyphens(menu);
1730
- vw = window.innerWidth;
1731
- for (const n of menu.querySelectorAll("[class*=\"modelName\"]")) sig += n.textContent?.length ?? 0;
1732
- }
1670
+ for (const menu of findMenus()) {
1671
+ if (!menu.querySelector("[class*=\"modelName\"]")) continue;
1672
+ if (menu.getBoundingClientRect().width === 0) continue;
1673
+ modelMenus.push(menu);
1674
+ if (narrow.matches) {
1675
+ applyNonBreakingHyphens(menu);
1676
+ vw = window.innerWidth;
1677
+ for (const n of menu.querySelectorAll("[class*=\"modelName\"]")) sig += n.textContent?.length ?? 0;
1733
1678
  }
1734
1679
  }
1735
- for (const previous of positionedMenus) {
1736
- if (visibleMenus.includes(previous)) continue;
1737
- clearFixedSheet(previous);
1680
+ for (const previous of fittedMenus) {
1681
+ if (modelMenus.includes(previous)) continue;
1738
1682
  clearModelNameFit(previous);
1739
1683
  }
1740
- positionedMenus = visibleMenus;
1741
1684
  if (!narrow.matches) {
1742
1685
  for (const menu of modelMenus) clearModelNameFit(menu);
1743
1686
  fittedVw = 0;
@@ -1757,42 +1700,14 @@ window.__ModuleLoader__.load({
1757
1700
  const mo = new MutationObserver(() => requestAnimationFrame(sync));
1758
1701
  mo.observe(document.documentElement, {
1759
1702
  childList: true,
1760
- subtree: true,
1761
- attributes: true,
1762
- attributeFilter: ["style"]
1703
+ subtree: true
1763
1704
  });
1764
1705
  narrow.addEventListener("change", sync);
1765
1706
  window.addEventListener("resize", sync);
1766
- window.addEventListener("scroll", sync, true);
1767
- window.visualViewport?.addEventListener("resize", sync);
1768
- window.visualViewport?.addEventListener("scroll", sync);
1769
- let lastPointerTarget = null;
1770
- const keyboardLikelyOpen = () => {
1771
- const vv = window.visualViewport;
1772
- if (!vv) return false;
1773
- return window.innerHeight - vv.height > 150;
1774
- };
1775
- const onPointerDown = (event) => {
1776
- lastPointerTarget = event.target instanceof Element ? event.target : null;
1777
- };
1778
- const onFocusIn = (event) => {
1779
- if (!narrow.matches || !(event.target instanceof Element) || keyboardLikelyOpen()) return;
1780
- const input = event.target.closest("input");
1781
- if (!input || input.closest("[class*=\"7KE1Ra_menu\"], [class*=\"_menu\"]") === null) return;
1782
- if (lastPointerTarget !== null && (lastPointerTarget === input || input.contains(lastPointerTarget))) return;
1783
- input.blur();
1784
- };
1785
- document.addEventListener("pointerdown", onPointerDown, true);
1786
- document.addEventListener("focusin", onFocusIn, true);
1787
1707
  return () => {
1788
1708
  mo.disconnect();
1789
1709
  narrow.removeEventListener("change", sync);
1790
1710
  window.removeEventListener("resize", sync);
1791
- window.removeEventListener("scroll", sync, true);
1792
- window.visualViewport?.removeEventListener("resize", sync);
1793
- window.visualViewport?.removeEventListener("scroll", sync);
1794
- document.removeEventListener("pointerdown", onPointerDown, true);
1795
- document.removeEventListener("focusin", onFocusIn, true);
1796
1711
  };
1797
1712
  }, "dshb-mobile: model menu position");
1798
1713
  }
@@ -1858,6 +1773,47 @@ window.__ModuleLoader__.load({
1858
1773
  return () => document.removeEventListener("pointerdown", focusInput, true);
1859
1774
  }, "dshb-mobile: terminal input focus");
1860
1775
  }
1776
+ /**
1777
+ * iOS 键盘只缩小 visualViewport,布局视口不动,sticky 在滚动容器底部的
1778
+ * composer 会整个留在键盘后面(发送按钮不可达、最新消息被遮)。监听
1779
+ * visualViewport,键盘开启时把 composer seat 上移键盘高度并在下一帧把
1780
+ * 会话滚动补到最底;Android 由 meta 的 interactive-widget=resizes-content
1781
+ * 让布局视口直接收缩,无需此补偿。
1782
+ */
1783
+ function installComposerKeyboardLift(ctx) {
1784
+ ctx.effect(() => {
1785
+ const mobile = window.matchMedia(MOBILE_QUERY);
1786
+ const vv = window.visualViewport;
1787
+ if (!vv) return () => {};
1788
+ let raf = 0;
1789
+ const seat = () => document.querySelector("[class*=\"composerSeat\"]") ?? document.querySelector("[data-composer-seat]");
1790
+ const sync = () => {
1791
+ if (raf !== 0) return;
1792
+ raf = requestAnimationFrame(() => {
1793
+ raf = 0;
1794
+ if (!mobile.matches) return;
1795
+ const el = seat();
1796
+ if (el === null) return;
1797
+ const occluded = window.innerHeight - vv.height - vv.offsetTop;
1798
+ if (occluded > 150) {
1799
+ el.style.transform = `translateY(${-occluded}px)`;
1800
+ const body = document.querySelector("[class*=\"scrollBody\"]");
1801
+ body?.scrollTo({ top: body.scrollHeight });
1802
+ } else if (el.style.transform !== "") el.style.transform = "";
1803
+ });
1804
+ };
1805
+ sync();
1806
+ vv.addEventListener("resize", sync);
1807
+ vv.addEventListener("scroll", sync);
1808
+ return () => {
1809
+ if (raf !== 0) cancelAnimationFrame(raf);
1810
+ const el = seat();
1811
+ if (el !== null) el.style.transform = "";
1812
+ vv.removeEventListener("resize", sync);
1813
+ vv.removeEventListener("scroll", sync);
1814
+ };
1815
+ }, "dshb-mobile: composer keyboard lift");
1816
+ }
1861
1817
  /** 目录抽屉切换按钮(仅切换,不含文件浏览/日志导出等功能按钮)。 */
1862
1818
  function DrawerToggle({ toggleSidebar }) {
1863
1819
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -1948,6 +1904,7 @@ window.__ModuleLoader__.load({
1948
1904
  function installMobile(ctx) {
1949
1905
  installStyles(ctx);
1950
1906
  installMobileTerminalInputFocus(ctx);
1907
+ installComposerKeyboardLift(ctx);
1951
1908
  installViewport(ctx);
1952
1909
  installFrameMarker(ctx);
1953
1910
  installStatsLine(ctx);
@@ -1988,7 +1945,7 @@ window.__ModuleLoader__.load({
1988
1945
  const dialog = document.createElement("div");
1989
1946
  dialog.setAttribute("role", "dialog");
1990
1947
  dialog.setAttribute("aria-label", `修改 ${workspaceTitle} 的项目指令`);
1991
- 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)";
1948
+ 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)";
1992
1949
  const title = document.createElement("h2");
1993
1950
  title.textContent = `项目指令 - ${workspaceTitle}`;
1994
1951
  title.style.cssText = "margin:0 0 10px;font-size:16px";
@@ -3226,9 +3183,9 @@ window.__ModuleLoader__.load({
3226
3183
  onClick: () => copyText(archive.originSessionId),
3227
3184
  style: {
3228
3185
  ...secondaryBtn,
3229
- height: 22,
3230
- padding: "0 6px",
3231
- fontSize: 10
3186
+ height: 30,
3187
+ padding: "0 10px",
3188
+ fontSize: 11
3232
3189
  },
3233
3190
  children: "复制 ID"
3234
3191
  })]
@@ -4643,7 +4600,10 @@ window.__ModuleLoader__.load({
4643
4600
  依赖祖先 overflow:visible 才能展开到输入框之外,禁止给任何中间祖先加裁剪。 */
4644
4601
  html, body { overflow-x: hidden !important; max-width: 100vw !important }
4645
4602
  * { min-width: 0 !important; }
4646
- table { display: block !important; overflow-x: auto !important; }
4603
+ /* 表格不设 display:block:上游 markdown 表格已自带滚动容器(tableScroll),
4604
+ display:block 会破坏 caption/sticky 语义且与上游 width:max-content 冲突;
4605
+ 这里仅保留溢出约束,宽表由上游 wrapper 横向滚动。 */
4606
+ table { max-width: 100% !important; overflow-x: auto !important; }
4647
4607
  img, video, canvas, svg { max-width: 100% !important; height: auto !important }
4648
4608
  pre, code { white-space: pre-wrap !important; word-break: break-all !important }
4649
4609
  input, select, textarea { max-width: 100% !important; }