dsh-remote-panel 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
Binary file
Binary file
Binary file
package/lib/client.js CHANGED
@@ -1075,12 +1075,15 @@ window.__ModuleLoader__.load({
1075
1075
  /* 独立窗口里别再有下拉刷新 / 橡皮筋 */
1076
1076
  body { overscroll-behavior: none; }
1077
1077
 
1078
+ /* 顶部安全区:默认取 env();WebView 里由壳把真实切角高度写进 --dsh-safe-top */
1079
+ :root { --dsh-safe-top: env(safe-area-inset-top, 0px); }
1080
+
1078
1081
  /* 顶栏:52px;刘海屏/独立窗口把状态栏让出来 */
1079
1082
  .wSkVaW_header {
1080
- min-height: calc(52px + env(safe-area-inset-top, 0px)) !important;
1081
- padding: calc(11px + env(safe-area-inset-top, 0px)) 60px 0 10px !important;
1083
+ min-height: calc(52px + var(--dsh-safe-top, 0px)) !important;
1084
+ padding: calc(12px + var(--dsh-safe-top, 0px)) 60px 0 10px !important;
1082
1085
  }
1083
- .wSkVaW_headerSessionless { padding-top: calc(11px + env(safe-area-inset-top, 0px)) !important; }
1086
+ .wSkVaW_headerSessionless { padding-top: calc(12px + var(--dsh-safe-top, 0px)) !important; }
1084
1087
  .wSkVaW_headerCorner { display: none !important; }
1085
1088
  /* 手机的「用文件管理器打开」是桌面动作,收掉 */
1086
1089
  .OMoRSG_split { display: none !important; }
@@ -1167,8 +1170,9 @@ window.__ModuleLoader__.load({
1167
1170
  left: 0;
1168
1171
  right: 0;
1169
1172
  z-index: 60;
1170
- top: env(safe-area-inset-top, 0px);
1171
- height: 52px;
1173
+ /* 与顶栏里那颗汉堡同一个纵向基线:顶栏 padding-top = 12 + safe-top,行高 40 */
1174
+ top: calc(12px + var(--dsh-safe-top, 0px));
1175
+ height: 40px;
1172
1176
  margin: 0;
1173
1177
  padding: 0 64px;
1174
1178
  align-items: center;
@@ -1193,7 +1197,12 @@ window.__ModuleLoader__.load({
1193
1197
  .uV2eYG_row { padding: 6px 10px 10px !important; }
1194
1198
 
1195
1199
  /* 抽屉:工作区列表置顶,面板入口退到列表下面,删掉品牌行与「新会话」行 */
1196
- .hHd-Xa_root { display: flex; flex-direction: column; padding-top: env(safe-area-inset-top, 0px) !important; }
1200
+ /* 顶部按刘海高度留白,但那块就是页面自己的底色,看起来只是「留白」不是「切角」 */
1201
+ .hHd-Xa_root {
1202
+ display: flex;
1203
+ flex-direction: column;
1204
+ padding-top: calc(12px + var(--dsh-safe-top, 0px)) !important;
1205
+ }
1197
1206
  .hHd-Xa_logoRow, .hHd-Xa_newSession { display: none !important; }
1198
1207
  .hHd-Xa_regionArea { order: 1; }
1199
1208
  /* 手机上的 DSH = 对话 + 改动 + 余额:只留「工作区 Git」,其余面板入口全收 */
@@ -1222,14 +1231,44 @@ window.__ModuleLoader__.load({
1222
1231
  html.dsh-search-open .bhn1Oq_searchInput { height: 38px !important; }
1223
1232
 
1224
1233
  /* 点遮罩关抽屉:原生窄屏没有这层,补一个(抽屉之下、正文之上) */
1225
- html.dsh-drawer-open .dsh-mobile-scrim {
1226
- display: block;
1234
+ /* 遮罩常驻,用透明度过渡:display 切换会瞬变,看着很硬 */
1235
+ .dsh-mobile-scrim {
1227
1236
  position: fixed;
1228
1237
  inset: 0;
1229
1238
  z-index: 899;
1230
- background: rgba(0, 0, 0, .35);
1239
+ display: block;
1240
+ opacity: 0;
1241
+ pointer-events: none;
1242
+ /* 纯黑底上「压暗」看不出层级,这里反过来提亮:抽屉保持纯黑,其余区域变灰 */
1243
+ background: rgba(255, 255, 255, .16);
1244
+ transition: opacity 160ms cubic-bezier(.2, .8, .2, 1);
1245
+ }
1246
+ html.dsh-drawer-open .dsh-mobile-scrim {
1247
+ opacity: 1;
1248
+ pointer-events: auto;
1249
+ /* 只在真正要动画时才提成合成层,别让它一直挂着 */
1250
+ will-change: opacity;
1251
+ }
1252
+
1253
+ /* 微交互:按压/悬停给 120ms 过渡,别让反馈是瞬变 */
1254
+ .dsh-mobile-hamburger, .dsh-mobile-newchat, .dsh-mobile-clear-workspace,
1255
+ .dsh-code-copy, .dsh-mobile-offline {
1256
+ transition: background-color 120ms ease-out, color 120ms ease-out,
1257
+ opacity 120ms ease-out, transform 120ms ease-out;
1258
+ -webkit-tap-highlight-color: transparent;
1259
+ }
1260
+ .dsh-mobile-hamburger:active, .dsh-mobile-newchat:active,
1261
+ .dsh-mobile-clear-workspace:active, .dsh-code-copy:active {
1262
+ transform: scale(.94);
1263
+ }
1264
+ @keyframes dsh-pill-in {
1265
+ from { opacity: 0; transform: translate(-50%, -4px); }
1266
+ to { opacity: 1; transform: translate(-50%, 0); }
1267
+ }
1268
+ .dsh-mobile-offline { animation: dsh-pill-in 160ms cubic-bezier(.2, .8, .2, 1); }
1269
+ @media (prefers-reduced-motion: reduce) {
1270
+ .dsh-mobile-scrim, .dsh-mobile-offline { transition: none; animation: none; }
1231
1271
  }
1232
- .dsh-mobile-scrim { display: none; }
1233
1272
 
1234
1273
  /* 会话内容:代码块 / 表格 / 图片 / 附件在手机上不撑破页面 */
1235
1274
  .md-code-block { max-width: 100%; }
@@ -1270,7 +1309,7 @@ window.__ModuleLoader__.load({
1270
1309
  .dsh-code-copy[data-done="true"] { opacity: .6; }
1271
1310
  .dsh-code-copy--float {
1272
1311
  position: absolute;
1273
- top: 6px;
1312
+ top: calc(6px + var(--dsh-safe-top, 0px));
1274
1313
  right: 6px;
1275
1314
  z-index: 2;
1276
1315
  background: var(--dsw-alias-bg-base, rgba(0, 0, 0, .4));
@@ -1295,22 +1334,31 @@ window.__ModuleLoader__.load({
1295
1334
 
1296
1335
  /* 手机外壳:深色配色(浅色主题保持原生配色,只换骨架) */
1297
1336
  @media (max-width: 1023px) {
1298
- body[data-ds-dark-theme] { background: #000 !important; }
1337
+ /* 纯黑底:连 DSH 自己的深色面(.wSkVaW_root / emptyTabHost / 抽屉)一起刷,
1338
+ 否则空白页黑、会话页 #151517,开关抽屉会看到底色跳变。
1339
+ 抽屉保持纯黑,靠遮罩提亮来区分层级。 */
1340
+ body[data-ds-dark-theme],
1341
+ body[data-ds-dark-theme] .wSkVaW_root,
1342
+ body[data-ds-dark-theme] [class*="_emptyTabHost"],
1299
1343
  body[data-ds-dark-theme] [class*="_frame"],
1300
- body[data-ds-dark-theme] [class*="_centerCol"] { background: #000 !important; }
1301
- body[data-ds-dark-theme] [class*="_sidebarCol"] {
1344
+ body[data-ds-dark-theme] [class*="_centerCol"],
1345
+ body[data-ds-dark-theme] [class*="_scrollBody"],
1346
+ body[data-ds-dark-theme] [class*="_sidebarCol"],
1347
+ body[data-ds-dark-theme] .hHd-Xa_root,
1348
+ body[data-ds-dark-theme] .hHd-Xa_regionArea,
1349
+ body[data-ds-dark-theme] .hHd-Xa_panelList {
1302
1350
  background: #000 !important;
1351
+ }
1352
+ body[data-ds-dark-theme] [class*="_sidebarCol"] {
1303
1353
  border: 0 !important;
1304
- box-shadow: 0 0 32px rgba(0, 0, 0, .45);
1354
+ box-shadow: none;
1305
1355
  }
1306
- body[data-ds-dark-theme] .hHd-Xa_root { background: #000 !important; }
1307
1356
  body[data-ds-dark-theme] .wSkVaW_header { border-bottom: 0 !important; background: transparent !important; }
1308
1357
  body[data-ds-dark-theme] .uV2eYG_card {
1309
- background: #141416 !important;
1310
1358
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .06) !important;
1311
1359
  }
1312
1360
  body[data-ds-dark-theme] .pXSMma_workspace { background: rgba(255, 255, 255, .07) !important; }
1313
- /* 列表底部的滚动渐隐原本渐到 #1b1b1c,在纯黑抽屉上会显出一条灰条 */
1361
+ /* 列表底部的滚动渐隐原本渐到 #1b1b1c,跟统一底色对齐 */
1314
1362
  body[data-ds-dark-theme] .bhn1Oq_fade {
1315
1363
  background-image: linear-gradient(rgba(0, 0, 0, 0), #000) !important;
1316
1364
  }
@@ -1322,7 +1370,7 @@ window.__ModuleLoader__.load({
1322
1370
  /* 断线胶囊:连续两次探活失败才出现,恢复即消失(任何宽度都显示) */
1323
1371
  .dsh-mobile-offline {
1324
1372
  position: fixed;
1325
- top: calc(env(safe-area-inset-top, 0px) + 6px);
1373
+ top: calc(6px + var(--dsh-safe-top, 0px));
1326
1374
  left: 50%;
1327
1375
  transform: translateX(-50%);
1328
1376
  z-index: 80;
@@ -1391,18 +1439,18 @@ window.__ModuleLoader__.load({
1391
1439
  padding-bottom: env(safe-area-inset-bottom, 0px) !important;
1392
1440
  }
1393
1441
 
1394
- /* 长会话滚动:离屏的 markdown 块不参与布局/绘制。
1395
- DSH 不虚拟化消息,几百轮之后节点全在文档里;content-visibility 让浏览器跳过离屏块,
1396
- 滚动时只画视口附近的内容。只在窄屏生效,桌面保持原样。 */
1442
+ /* 长会话滚动:只在窄屏做滚动容器层面的优化,桌面保持原样 */
1397
1443
  @media (max-width: 1023px) {
1398
- [class*="_markdown"] {
1399
- content-visibility: auto;
1400
- contain-intrinsic-size: auto 320px;
1401
- }
1444
+ /* 曾经给 markdown 块加 content-visibility:auto 想省渲染,
1445
+ 但 4x CPU 节流实测滚动会卡出 155ms 的长帧(块进入视口时要重新布局),
1446
+ 手机上的「卡顿」就是它。这里去掉,只保留滚动容器本身的优化。 */
1402
1447
  [class*="_scrollBody"] {
1403
1448
  overscroll-behavior: contain;
1404
1449
  -webkit-overflow-scrolling: touch;
1405
1450
  }
1451
+ /* 点按不要留系统高亮块(各 ROM 的 WebView 默认会给一层灰/蓝色块) */
1452
+ * { -webkit-tap-highlight-color: transparent; }
1453
+ *:focus:not(:focus-visible) { outline: none; }
1406
1454
  }
1407
1455
 
1408
1456
  `
@@ -1444,11 +1492,22 @@ window.__ModuleLoader__.load({
1444
1492
  clear.dataset.none = String(isEmpty())
1445
1493
  row.appendChild(clear)
1446
1494
  }
1447
- const observer = new MutationObserver(ensure)
1495
+ // 流式回答时 DOM 每秒要变很多次,ensure 里是一次全量 querySelector,
1496
+ // 必须合并到每帧最多一次,否则主线程全被它占了(手机上就是掉帧)
1497
+ let frame = 0
1498
+ const schedule = () => {
1499
+ if (frame !== 0) return
1500
+ frame = requestAnimationFrame(() => {
1501
+ frame = 0
1502
+ ensure()
1503
+ })
1504
+ }
1505
+ const observer = new MutationObserver(schedule)
1448
1506
  observer.observe(document.body, { subtree: true, childList: true })
1449
1507
  ensure()
1450
1508
  return () => {
1451
1509
  observer.disconnect()
1510
+ if (frame !== 0) cancelAnimationFrame(frame)
1452
1511
  document.querySelector('.dsh-mobile-clear-workspace')?.remove()
1453
1512
  }
1454
1513
  }
@@ -1465,7 +1524,15 @@ window.__ModuleLoader__.load({
1465
1524
  const onPop = () => {
1466
1525
  if (isOpen()) layout?.toggleSidebar?.()
1467
1526
  }
1527
+ let frame = 0
1468
1528
  const observer = new MutationObserver(() => {
1529
+ if (frame !== 0) return
1530
+ frame = requestAnimationFrame(() => {
1531
+ frame = 0
1532
+ check()
1533
+ })
1534
+ })
1535
+ const check = () => {
1469
1536
  if (query && !query.matches) return
1470
1537
  const open = isOpen()
1471
1538
  if (open && !pushed) {
@@ -1479,7 +1546,7 @@ window.__ModuleLoader__.load({
1479
1546
  pushed = false
1480
1547
  if (history.state !== null && history.state.dshDrawer === true) history.back()
1481
1548
  }
1482
- })
1549
+ }
1483
1550
  observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
1484
1551
  window.addEventListener('popstate', onPop)
1485
1552
  return () => {
@@ -1488,6 +1555,129 @@ window.__ModuleLoader__.load({
1488
1555
  }
1489
1556
  }
1490
1557
 
1558
+ /**
1559
+ * 真机性能探针:URL 带 `?dsh-fps=1` 时在左下角显示帧率 / 最慢帧 / 长任务数。
1560
+ *
1561
+ * 只是为了在手机上定位「哪一步卡」,平时不加载任何逻辑(默认直接 return)。
1562
+ */
1563
+ function installFpsMeter() {
1564
+ if (typeof window === 'undefined' || typeof document === 'undefined') return () => {}
1565
+ let enabled = /[?&]dsh-fps=1(&|$)/.test(window.location.search)
1566
+ if (!enabled) {
1567
+ try {
1568
+ enabled = window.sessionStorage.getItem('dsh-fps') === '1'
1569
+ } catch {
1570
+ /* 隐私模式禁 storage,忽略 */
1571
+ }
1572
+ }
1573
+ if (!enabled) return () => {}
1574
+
1575
+ const box = document.createElement('div')
1576
+ box.style.cssText =
1577
+ 'position:fixed;left:8px;bottom:8px;z-index:99999;padding:4px 8px;border-radius:8px;' +
1578
+ 'background:rgba(0,0,0,.72);color:#7ef29b;font:11px/1.5 ui-monospace,monospace;' +
1579
+ 'pointer-events:none;white-space:pre;'
1580
+ document.body.appendChild(box)
1581
+
1582
+ let frames = 0
1583
+ let since = performance.now()
1584
+ let worst = 0
1585
+ let longTasks = 0
1586
+ let fps = 0
1587
+ let observer = null
1588
+ try {
1589
+ observer = new PerformanceObserver((list) => {
1590
+ for (const entry of list.getEntries()) if (entry.duration > 50) longTasks += 1
1591
+ })
1592
+ observer.observe({ entryTypes: ['longtask'] })
1593
+ } catch {
1594
+ /* 老内核没有 longtask,忽略 */
1595
+ }
1596
+
1597
+ const paint = () => {
1598
+ box.textContent = `${fps}fps worst ${Math.round(worst)}ms long ${longTasks}`
1599
+ worst = 0
1600
+ }
1601
+ let raf = 0
1602
+ const tick = (now) => {
1603
+ frames += 1
1604
+ worst = Math.max(worst, now - since)
1605
+ const elapsed = now - since
1606
+ if (elapsed >= 500) {
1607
+ fps = Math.round((frames * 1000) / elapsed)
1608
+ frames = 0
1609
+ since = now
1610
+ paint()
1611
+ }
1612
+ raf = requestAnimationFrame(tick)
1613
+ }
1614
+ raf = requestAnimationFrame(tick)
1615
+ window.setInterval(paint, 1000)
1616
+ return () => {
1617
+ cancelAnimationFrame(raf)
1618
+ observer?.disconnect()
1619
+ box.remove()
1620
+ }
1621
+ }
1622
+
1623
+ /**
1624
+ * 切换工作区后别自动聚焦输入框。
1625
+ *
1626
+ * DSH 打开工作区选择器时会先把焦点交给搜索框;关掉后焦点会回到输入条,
1627
+ * 手机上就是键盘自己弹出来。这里在选择器关闭、或 chip 文案变化后,
1628
+ * 把焦点从输入条上撤走(只撤输入条里的可编辑区,不动选择器自己的搜索框)。
1629
+ */
1630
+ function installComposerFocusGuard() {
1631
+ if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return () => {}
1632
+ let labelObserver = null
1633
+ let observedLabel = null
1634
+ let lastLabel = ''
1635
+
1636
+ const isComposer = (el) =>
1637
+ el !== null &&
1638
+ el !== document.body &&
1639
+ (el.isContentEditable === true || el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') &&
1640
+ (el.closest('.uV2eYG_root') !== null || el.closest('[class*="_composer"]') !== null)
1641
+
1642
+ const release = () => {
1643
+ const el = document.activeElement
1644
+ if (isComposer(el)) el.blur()
1645
+ }
1646
+ // DSH 可能在我们之后又把焦点塞回去,所以分几拍各撤一次
1647
+ const scheduleRelease = () => {
1648
+ for (const delay of [0, 80, 200, 400]) window.setTimeout(release, delay)
1649
+ }
1650
+
1651
+ const watchLabel = () => {
1652
+ const label = document.querySelector('[class*="_workspaceLabel"]')
1653
+ if (label === null || label === observedLabel) return
1654
+ labelObserver?.disconnect()
1655
+ observedLabel = label
1656
+ lastLabel = label.textContent ?? ''
1657
+ labelObserver = new MutationObserver(() => {
1658
+ const now = label.textContent ?? ''
1659
+ if (now === lastLabel) return
1660
+ lastLabel = now
1661
+ scheduleRelease()
1662
+ })
1663
+ labelObserver.observe(label, { childList: true, characterData: true, subtree: true })
1664
+ }
1665
+
1666
+ // 选择器关闭 = 里面那个搜索框失焦。用事件判断,别在每次 DOM 变动时全量 querySelector。
1667
+ const onFocusOut = (event) => {
1668
+ const target = event.target
1669
+ if (target instanceof Element && target.closest('[class*="wsc-"]') !== null) scheduleRelease()
1670
+ }
1671
+ document.addEventListener('focusout', onFocusOut, true)
1672
+ const ensureLabel = window.setInterval(watchLabel, 800)
1673
+ watchLabel()
1674
+ return () => {
1675
+ document.removeEventListener('focusout', onFocusOut, true)
1676
+ window.clearInterval(ensureLabel)
1677
+ labelObserver?.disconnect()
1678
+ }
1679
+ }
1680
+
1491
1681
  /**
1492
1682
  * 本机反馈:任务跑完 / 等你确认时,如果页面在后台,闪一下标题 + 震动。
1493
1683
  * 不依赖推送服务(v1 不做推送),也不需要服务端配合。
@@ -1719,8 +1909,23 @@ window.__ModuleLoader__.load({
1719
1909
  enhanceJobsBadge()
1720
1910
  enhanceImages()
1721
1911
  }
1722
- const observer = new MutationObserver(() => {
1912
+ const RELEVANT = 'pre, img, table, [class*="job"], [class*="Job"], [class*="attach"]'
1913
+ const isRelevant = (records) => {
1914
+ for (const record of records) {
1915
+ const target = record.target
1916
+ if (target instanceof Element && target.closest(RELEVANT) !== null) return true
1917
+ for (const node of record.addedNodes) {
1918
+ if (node.nodeType !== 1) continue
1919
+ if (node.matches?.(RELEVANT) === true || node.querySelector?.(RELEVANT) !== null) return true
1920
+ }
1921
+ }
1922
+ return false
1923
+ }
1924
+ const observer = new MutationObserver((records) => {
1723
1925
  if (scheduled !== 0) return
1926
+ // 流式回答每帧都往消息里塞文本节点,全量扫描会把帧吃光;只有出现
1927
+ // 代码块/图片/表格/任务卡/附件这类节点时才真的需要补按钮
1928
+ if (!isRelevant(records)) return
1724
1929
  scheduled = requestAnimationFrame(() => {
1725
1930
  scheduled = 0
1726
1931
  enhance()
@@ -1749,6 +1954,40 @@ window.__ModuleLoader__.load({
1749
1954
  * 断线重连提示:DSH 前端没有「连接断开」的可见反馈,网络一抖页面只是不动了。
1750
1955
  * 每 10s 打一次面板自己的 /status,连续两次失败才显示,恢复后自动消失。
1751
1956
  */
1957
+ /**
1958
+ * 点输入条上的「+」(添加文件或调用指令)时,别让输入框自动聚焦。
1959
+ * 手机上一聚焦就弹键盘,把刚打开的面板顶走。
1960
+ *
1961
+ * 做法:在 pointerdown 阶段记一个短窗口(900ms),窗口内如果输入框收到 focus 就立刻 blur 回去;
1962
+ * 点别的地方会立刻清掉窗口,不会影响正常输入。
1963
+ */
1964
+ function installComposerFocusGuard() {
1965
+ if (typeof document === 'undefined') return () => {}
1966
+ let until = 0
1967
+ const isComposerInput = (el) => el instanceof Element && el.closest?.('.uV2eYG_input') !== null
1968
+ const onPointerDown = (event) => {
1969
+ const target = event.target
1970
+ until = target instanceof Element && target.closest('.uV2eYG_add') !== null ? Date.now() + 900 : 0
1971
+ }
1972
+ const onFocusIn = (event) => {
1973
+ if (Date.now() > until) return
1974
+ const el = event.target
1975
+ if (!isComposerInput(el)) return
1976
+ const drop = () => {
1977
+ if (Date.now() <= until && document.activeElement === el) el.blur?.()
1978
+ }
1979
+ drop()
1980
+ // 有的实现会在下一帧再 focus 一次,补一刀
1981
+ requestAnimationFrame(drop)
1982
+ }
1983
+ document.addEventListener('pointerdown', onPointerDown, true)
1984
+ document.addEventListener('focusin', onFocusIn, true)
1985
+ return () => {
1986
+ document.removeEventListener('pointerdown', onPointerDown, true)
1987
+ document.removeEventListener('focusin', onFocusIn, true)
1988
+ }
1989
+ }
1990
+
1752
1991
  function installConnectivity() {
1753
1992
  if (typeof window === 'undefined' || typeof fetch !== 'function') return () => {}
1754
1993
  let failures = 0
@@ -1984,11 +2223,15 @@ window.__ModuleLoader__.load({
1984
2223
  const cleanupAttention = installAttention()
1985
2224
  const cleanupContent = installContentEnhancements()
1986
2225
  const cleanupNet = installConnectivity()
2226
+ const cleanupFocus = installComposerFocusGuard()
2227
+ const cleanupFps = installFpsMeter()
1987
2228
  if (typeof ctx.effect === 'function') ctx.effect(() => cleanupClear, 'remote-panel: workspace clear')
1988
2229
  if (typeof ctx.effect === 'function') ctx.effect(() => cleanupBack, 'remote-panel: back gesture')
1989
2230
  if (typeof ctx.effect === 'function') ctx.effect(() => cleanupAttention, 'remote-panel: attention')
1990
2231
  if (typeof ctx.effect === 'function') ctx.effect(() => cleanupContent, 'remote-panel: content enhancements')
1991
2232
  if (typeof ctx.effect === 'function') ctx.effect(() => cleanupNet, 'remote-panel: connectivity')
2233
+ if (typeof ctx.effect === 'function') ctx.effect(() => cleanupFocus, 'remote-panel: composer focus guard')
2234
+ if (typeof ctx.effect === 'function') ctx.effect(() => cleanupFps, 'remote-panel: fps meter')
1992
2235
  if (typeof ctx.effect === 'function') ctx.effect(() => cleanup, 'remote-panel: mobile shell css')
1993
2236
 
1994
2237
  ctx.slots.inject('shell.overlay', () =>
package/lib/index.js CHANGED
@@ -141,7 +141,9 @@ export function writeGoPage(dir, token, accessKey) {
141
141
  `<title>DSH</title><meta http-equiv="refresh" content="0;url=${target}">` +
142
142
  '<style>html,body{height:100%;margin:0;background:#000;color:#888;font:14px system-ui;display:flex;align-items:center;justify-content:center}</style>' +
143
143
  '<p>正在进入 DSH…</p>' +
144
- `<script>location.replace(${JSON.stringify(target)})</script>`
144
+ // ?fps=1 时把开关记进 sessionStorage,跳转后由客户端插件读取(用于真机性能排查)
145
+ '<script>try{if(/[?&]fps=1/.test(location.search))sessionStorage.setItem("dsh-fps","1")}catch(e){}' +
146
+ `location.replace(${JSON.stringify(target)})</script>`
145
147
  writeFileSync(join(dir, 'go.html'), html)
146
148
  return target
147
149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-panel",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "DSH 远程访问控制面:部署向导、状态面板与一键配对(EasyTier + Caddy),数据面保持进程外。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",