@qzsy/dsh-bridge 3.4.0 → 3.4.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # 更新日志
2
2
 
3
+ ## [v3.4.2] - 2026-11-02
4
+
5
+ ### 🐛 修复
6
+
7
+ - **侧滑跟手**:水平拖动期间动态注册非 passive `touchmove` 阻止页面横滑/边缘返回,抽屉真正 1:1 跟手;纵向滚动不拦截。
8
+ - **首滑不卡**:手机首屏后在屏幕外预热展开侧栏,第一次侧滑不再在手指下触发宿主重排/挂载完整会话列表。
9
+ - **起手不跳**:拖动起点与 CSS `-102%` 关闭位对齐,修掉第一帧约 6px 跳变。
10
+ - **内容不弹**:拖动期间也强制侧栏内容 `100%` 宽,松手时不再出现 `280px → 300px` 宽度跳变。
11
+ - **松手更轻**:打开距离阈值从 90px 降到约 66px、速度阈值降低;移除松手时重复的展开点击;`box-shadow` 改为过渡。
12
+
13
+ ### 🧪 验证
14
+
15
+ - 真实触摸事件模拟:预热展开、dx=20 首帧 -286、水平移动 `defaultPrevented=true`、纵向不拦截、短滑 110px 吸附打开、阴影过渡,7/7 通过。
16
+
17
+
18
+ ## [v3.4.1] - 2026-11-02
19
+
20
+ ### 🐛 修复
21
+
22
+ - **模型 sheet 识别收紧**:模型菜单被改造成 sheet 后立刻清空触发链,并优先用 `data-model-id` 等模型标记定位;关闭模型面板后紧接着打开账号等其它菜单,不会误套用底部 sheet。
23
+ - **无搜索框宿主兜底**:触发链窗口从 6 秒收紧到 2.5 秒,只对「模型触发后出现、且行数 > 2」的菜单生效。
24
+
25
+ ### 🧪 验证
26
+
27
+ - 实测:第一层「模型 / 推理等级」popover 不改造;二级模型菜单 sheet 化;关闭后再开账号菜单,`data-qzsy-model-sheet` 数量为 0,无残留遮罩。
28
+
29
+
3
30
  ## [v3.4.0] - 2026-11-02
4
31
 
5
32
  ### ✨ 新增
package/client/client.js CHANGED
@@ -300,17 +300,19 @@ var MOBILE_DRAWER_CSS = `
300
300
  transform: translateX(-102%) !important;
301
301
  overflow: hidden !important;
302
302
  overscroll-behavior: contain;
303
+ touch-action: pan-y;
303
304
  will-change: transform;
304
305
  }
305
306
  /* \u9996\u5E27\u5148\u5C31\u4F4D\u3001\u4E0D\u64AD\u653E\u5165\u573A\u52A8\u753B\uFF0C\u907F\u514D\u9875\u9762\u52A0\u8F7D\u65F6\u4FA7\u680F\u4ECE 0 \u6ED1\u51FA */
306
307
  body.qzsy-drawer-ready div[class*="_sidebarCol"] {
307
- transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1) !important;
308
+ transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1), box-shadow 280ms ease !important;
308
309
  }
309
310
  body.qzsy-drawer-open div[class*="_sidebarCol"] {
310
311
  transform: translateX(0) !important;
311
312
  box-shadow: 8px 0 32px rgba(0, 0, 0, 0.18) !important;
312
313
  }
313
- body.qzsy-drawer-open div[class*="_sidebarCol"] > div > div[class*="_root"] {
314
+ body.qzsy-drawer-open div[class*="_sidebarCol"] > div > div[class*="_root"],
315
+ body.qzsy-drawer-dragging div[class*="_sidebarCol"] > div > div[class*="_root"] {
314
316
  width: 100% !important;
315
317
  }
316
318
 
@@ -514,6 +516,17 @@ function setupMobileDrawer() {
514
516
  let modelChainUntil = 0;
515
517
  let modelDrag = null;
516
518
  let modelDragHandlers = null;
519
+ const preventDrawerScroll = (event) => {
520
+ if (!gesture || !event.cancelable) return;
521
+ const touch = event.touches?.[0];
522
+ if (!touch) return;
523
+ const dx = touch.clientX - gesture.startX;
524
+ const dy = touch.clientY - gesture.startY;
525
+ if (Math.abs(dx) > 4 && Math.abs(dx) > Math.abs(dy) * 1.2) event.preventDefault();
526
+ };
527
+ const removeDrawerPrevent = () => {
528
+ window.removeEventListener("touchmove", preventDrawerScroll);
529
+ };
517
530
  const ensureElements = () => {
518
531
  if (disposed || !document.body) return;
519
532
  injectStyles();
@@ -561,8 +574,9 @@ function setupMobileDrawer() {
561
574
  }
562
575
  };
563
576
  const setDragProgress = (x, width) => {
577
+ const span = Math.max(1, width * 1.02);
564
578
  document.body.style.setProperty("--qzsy-drawer-x", `${x}px`);
565
- document.body.style.setProperty("--qzsy-drawer-progress", String(Math.max(0, Math.min(1, 1 - Math.abs(x) / width))));
579
+ document.body.style.setProperty("--qzsy-drawer-progress", String(Math.max(0, Math.min(1, 1 - Math.abs(x) / span))));
566
580
  };
567
581
  const clearDrag = () => {
568
582
  document.body.classList.remove(DRAGGING_CLASS);
@@ -624,7 +638,6 @@ function setupMobileDrawer() {
624
638
  if ((s.position === "fixed" || s.position === "absolute") && portal.getBoundingClientRect().width > 120) return portal;
625
639
  }
626
640
  }
627
- if (Date.now() > modelChainUntil) return null;
628
641
  const candidates = [];
629
642
  for (const el of document.body.children) {
630
643
  if (!(el instanceof HTMLElement)) continue;
@@ -637,13 +650,15 @@ function setupMobileDrawer() {
637
650
  const text = menu.innerText || "";
638
651
  const rows = menu.querySelectorAll('[role="menuitem"], [role="option"], button, a, [tabindex]').length;
639
652
  const hasSearch = Boolean(el.querySelector('input[placeholder*="\u641C\u7D22\u6A21\u578B"], input[placeholder*="Search model"]'));
653
+ const hasModelMarker = Boolean(el.querySelector("[data-model-id], [data-provider-id], [data-model], [data-model-name]"));
640
654
  if (/推理等级|Reasoning effort|推理强度/i.test(text) && rows <= 2) continue;
641
655
  const rect = el.getBoundingClientRect();
642
656
  const alreadySheet = rect.width >= window.innerWidth - 2 && Math.abs(rect.bottom - window.innerHeight) < 2 && rect.height > 120;
643
657
  if (alreadySheet && !hasSearch) continue;
644
- if (hasSearch || rows > 2) candidates.push({ el, rows, hasSearch });
658
+ const chained = Date.now() < modelChainUntil && rows > 2;
659
+ if (hasSearch || hasModelMarker || chained) candidates.push({ el, rows, hasSearch, hasModelMarker });
645
660
  }
646
- candidates.sort((a, b) => Number(b.hasSearch) - Number(a.hasSearch) || b.rows - a.rows);
661
+ candidates.sort((a, b) => Number(b.hasSearch) - Number(a.hasSearch) || Number(b.hasModelMarker) - Number(a.hasModelMarker) || b.rows - a.rows);
647
662
  return candidates[0]?.el || null;
648
663
  };
649
664
  const hostBackdropPresent = () => {
@@ -783,6 +798,7 @@ function setupMobileDrawer() {
783
798
  if (modelPortal === portal) return;
784
799
  clearModelSheet();
785
800
  modelPortal = portal;
801
+ modelChainUntil = 0;
786
802
  portal.setAttribute("data-qzsy-model-sheet", "");
787
803
  portal.classList.add("qzsy-model-sheet");
788
804
  attachModelDrag(portal);
@@ -821,7 +837,7 @@ function setupMobileDrawer() {
821
837
  return;
822
838
  }
823
839
  if (isModelTrigger(target)) {
824
- modelChainUntil = Date.now() + 6e3;
840
+ modelChainUntil = Date.now() + 2500;
825
841
  window.setTimeout(syncModelSheet, 30);
826
842
  window.setTimeout(syncModelSheet, 180);
827
843
  }
@@ -886,6 +902,7 @@ function setupMobileDrawer() {
886
902
  active: false,
887
903
  dx: 0
888
904
  };
905
+ window.addEventListener("touchmove", preventDrawerScroll, { passive: false });
889
906
  return;
890
907
  }
891
908
  if (!sidebar.contains(event.target)) return;
@@ -899,6 +916,7 @@ function setupMobileDrawer() {
899
916
  active: false,
900
917
  dx: 0
901
918
  };
919
+ window.addEventListener("touchmove", preventDrawerScroll, { passive: false });
902
920
  };
903
921
  const onTouchMove = (event) => {
904
922
  if (modelPortal || !gesture || disposed) return;
@@ -910,14 +928,17 @@ function setupMobileDrawer() {
910
928
  if (Math.abs(dx) < DRAG_THRESHOLD) return;
911
929
  if (Math.abs(dx) < Math.abs(dy) * 1.2) {
912
930
  gesture = null;
931
+ removeDrawerPrevent();
913
932
  return;
914
933
  }
915
934
  if (gesture.mode === "open" && dx <= 0) {
916
935
  gesture = null;
936
+ removeDrawerPrevent();
917
937
  return;
918
938
  }
919
939
  if (gesture.mode === "close" && dx >= 0) {
920
940
  gesture = null;
941
+ removeDrawerPrevent();
921
942
  return;
922
943
  }
923
944
  gesture.active = true;
@@ -926,8 +947,12 @@ function setupMobileDrawer() {
926
947
  }
927
948
  const width = Math.max(1, gesture.width);
928
949
  let x;
929
- if (gesture.mode === "open") x = Math.max(-width, Math.min(0, -width + dx));
930
- else x = Math.max(-width, Math.min(0, dx));
950
+ if (gesture.mode === "open") {
951
+ const startOffset = -width * 1.02;
952
+ x = Math.max(startOffset, Math.min(0, startOffset + dx));
953
+ } else {
954
+ x = Math.max(-width, Math.min(0, dx));
955
+ }
931
956
  gesture.dx = dx;
932
957
  setDragProgress(x, width);
933
958
  };
@@ -935,15 +960,15 @@ function setupMobileDrawer() {
935
960
  if (!gesture) return;
936
961
  const current = gesture;
937
962
  gesture = null;
963
+ removeDrawerPrevent();
938
964
  if (!current.active) return;
939
965
  const endX = touch ? touch.clientX : current.startX + current.dx;
940
966
  const dx = endX - current.startX;
941
967
  const elapsed = Math.max(1, Date.now() - current.startT);
942
968
  const velocity = dx / elapsed;
943
- const threshold = Math.min(90, Math.max(60, current.width * 0.3));
944
- const openNow = current.mode === "open" ? dx > threshold || velocity > 0.45 : !(dx < -Math.max(50, threshold * 0.7) || velocity < -0.35);
969
+ const threshold = Math.min(80, Math.max(50, current.width * 0.22));
970
+ const openNow = current.mode === "open" ? dx > threshold || velocity > 0.35 : !(dx < -Math.max(40, threshold * 0.6) || velocity < -0.3);
945
971
  document.body.classList.toggle(OPEN_CLASS, openNow);
946
- if (openNow) ensureSidebarExpanded();
947
972
  syncAria();
948
973
  suppressClickUntil = Date.now() + 250;
949
974
  window.requestAnimationFrame(clearDrag);
@@ -969,6 +994,12 @@ function setupMobileDrawer() {
969
994
  window.requestAnimationFrame(() => {
970
995
  if (!disposed && document.body) document.body.classList.add("qzsy-drawer-ready");
971
996
  });
997
+ const preExpandSidebar = () => {
998
+ if (disposed || !isMobileViewport()) return;
999
+ ensureSidebarExpanded();
1000
+ };
1001
+ window.setTimeout(preExpandSidebar, 600);
1002
+ window.setTimeout(preExpandSidebar, 1600);
972
1003
  if (typeof MutationObserver === "function" && document.body) {
973
1004
  observer = new MutationObserver(() => {
974
1005
  if (!document.querySelector(`.${HEADER_CLASS}`) || !document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
@@ -995,6 +1026,7 @@ function setupMobileDrawer() {
995
1026
  window.removeEventListener("touchmove", onTouchMove);
996
1027
  window.removeEventListener("touchend", onTouchEnd);
997
1028
  window.removeEventListener("touchcancel", onTouchCancel);
1029
+ removeDrawerPrevent();
998
1030
  clearModelSheet();
999
1031
  clearDrag();
1000
1032
  document.body?.classList.remove(OPEN_CLASS);
package/client/mobile.js CHANGED
@@ -97,17 +97,19 @@ export const MOBILE_DRAWER_CSS = `
97
97
  transform: translateX(-102%) !important;
98
98
  overflow: hidden !important;
99
99
  overscroll-behavior: contain;
100
+ touch-action: pan-y;
100
101
  will-change: transform;
101
102
  }
102
103
  /* 首帧先就位、不播放入场动画,避免页面加载时侧栏从 0 滑出 */
103
104
  body.qzsy-drawer-ready div[class*="_sidebarCol"] {
104
- transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1) !important;
105
+ transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1), box-shadow 280ms ease !important;
105
106
  }
106
107
  body.qzsy-drawer-open div[class*="_sidebarCol"] {
107
108
  transform: translateX(0) !important;
108
109
  box-shadow: 8px 0 32px rgba(0, 0, 0, 0.18) !important;
109
110
  }
110
- body.qzsy-drawer-open div[class*="_sidebarCol"] > div > div[class*="_root"] {
111
+ body.qzsy-drawer-open div[class*="_sidebarCol"] > div > div[class*="_root"],
112
+ body.qzsy-drawer-dragging div[class*="_sidebarCol"] > div > div[class*="_root"] {
111
113
  width: 100% !important;
112
114
  }
113
115
 
@@ -321,6 +323,19 @@ export function setupMobileDrawer() {
321
323
  let modelDrag = null;
322
324
  let modelDragHandlers = null;
323
325
 
326
+ const preventDrawerScroll = (event) => {
327
+ if (!gesture || !event.cancelable) return;
328
+ const touch = event.touches?.[0];
329
+ if (!touch) return;
330
+ const dx = touch.clientX - gesture.startX;
331
+ const dy = touch.clientY - gesture.startY;
332
+ if (Math.abs(dx) > 4 && Math.abs(dx) > Math.abs(dy) * 1.2) event.preventDefault();
333
+ };
334
+
335
+ const removeDrawerPrevent = () => {
336
+ window.removeEventListener('touchmove', preventDrawerScroll);
337
+ };
338
+
324
339
  const ensureElements = () => {
325
340
  if (disposed || !document.body) return;
326
341
  injectStyles();
@@ -371,8 +386,9 @@ export function setupMobileDrawer() {
371
386
  };
372
387
 
373
388
  const setDragProgress = (x, width) => {
389
+ const span = Math.max(1, width * 1.02);
374
390
  document.body.style.setProperty('--qzsy-drawer-x', `${x}px`);
375
- document.body.style.setProperty('--qzsy-drawer-progress', String(Math.max(0, Math.min(1, 1 - Math.abs(x) / width))));
391
+ document.body.style.setProperty('--qzsy-drawer-progress', String(Math.max(0, Math.min(1, 1 - Math.abs(x) / span))));
376
392
  };
377
393
 
378
394
  const clearDrag = () => {
@@ -440,8 +456,7 @@ export function setupMobileDrawer() {
440
456
  if ((s.position === 'fixed' || s.position === 'absolute') && portal.getBoundingClientRect().width > 120) return portal;
441
457
  }
442
458
  }
443
- // 无搜索框的宿主(如移动端版本)用「模型触发链 + 菜单行数」兜底
444
- if (Date.now() > modelChainUntil) return null;
459
+ // 无搜索框的宿主(如移动端版本)用「模型标记 / 模型触发链 + 菜单行数」兜底
445
460
  const candidates = [];
446
461
  for (const el of document.body.children) {
447
462
  if (!(el instanceof HTMLElement)) continue;
@@ -454,15 +469,19 @@ export function setupMobileDrawer() {
454
469
  const text = menu.innerText || '';
455
470
  const rows = menu.querySelectorAll('[role="menuitem"], [role="option"], button, a, [tabindex]').length;
456
471
  const hasSearch = Boolean(el.querySelector('input[placeholder*="搜索模型"], input[placeholder*="Search model"]'));
472
+ const hasModelMarker = Boolean(el.querySelector('[data-model-id], [data-provider-id], [data-model], [data-model-name]'));
457
473
  // 排除「模型 / 推理等级」两行的第一层 popover
458
474
  if (/推理等级|Reasoning effort|推理强度/i.test(text) && rows <= 2) continue;
459
475
  // 已经是全宽底部 sheet 的宿主菜单不再重复改造
460
476
  const rect = el.getBoundingClientRect();
461
477
  const alreadySheet = rect.width >= window.innerWidth - 2 && Math.abs(rect.bottom - window.innerHeight) < 2 && rect.height > 120;
462
478
  if (alreadySheet && !hasSearch) continue;
463
- if (hasSearch || rows > 2) candidates.push({ el, rows, hasSearch });
479
+ const chained = Date.now() < modelChainUntil && rows > 2;
480
+ if (hasSearch || hasModelMarker || chained) candidates.push({ el, rows, hasSearch, hasModelMarker });
464
481
  }
465
- candidates.sort((a, b) => Number(b.hasSearch) - Number(a.hasSearch) || b.rows - a.rows);
482
+ candidates.sort((a, b) => Number(b.hasSearch) - Number(a.hasSearch)
483
+ || Number(b.hasModelMarker) - Number(a.hasModelMarker)
484
+ || b.rows - a.rows);
466
485
  return candidates[0]?.el || null;
467
486
  };
468
487
 
@@ -602,6 +621,7 @@ export function setupMobileDrawer() {
602
621
  if (modelPortal === portal) return;
603
622
  clearModelSheet();
604
623
  modelPortal = portal;
624
+ modelChainUntil = 0;
605
625
  portal.setAttribute('data-qzsy-model-sheet', '');
606
626
  portal.classList.add('qzsy-model-sheet');
607
627
  attachModelDrag(portal);
@@ -645,7 +665,7 @@ export function setupMobileDrawer() {
645
665
 
646
666
  // 模型切换器:记录触发链,等宿主把二级菜单 portal 挂到 body 后再改造成 sheet
647
667
  if (isModelTrigger(target)) {
648
- modelChainUntil = Date.now() + 6000;
668
+ modelChainUntil = Date.now() + 2500;
649
669
  window.setTimeout(syncModelSheet, 30);
650
670
  window.setTimeout(syncModelSheet, 180);
651
671
  }
@@ -725,6 +745,7 @@ export function setupMobileDrawer() {
725
745
  active: false,
726
746
  dx: 0,
727
747
  };
748
+ window.addEventListener('touchmove', preventDrawerScroll, { passive: false });
728
749
  return;
729
750
  }
730
751
 
@@ -739,6 +760,7 @@ export function setupMobileDrawer() {
739
760
  active: false,
740
761
  dx: 0,
741
762
  };
763
+ window.addEventListener('touchmove', preventDrawerScroll, { passive: false });
742
764
  };
743
765
 
744
766
  const onTouchMove = (event) => {
@@ -751,9 +773,9 @@ export function setupMobileDrawer() {
751
773
  if (!gesture.active) {
752
774
  if (Math.abs(dx) < DRAG_THRESHOLD) return;
753
775
  // 纵向为主:交还给宿主滚动,本次不拦截
754
- if (Math.abs(dx) < Math.abs(dy) * 1.2) { gesture = null; return; }
755
- if (gesture.mode === 'open' && dx <= 0) { gesture = null; return; }
756
- if (gesture.mode === 'close' && dx >= 0) { gesture = null; return; }
776
+ if (Math.abs(dx) < Math.abs(dy) * 1.2) { gesture = null; removeDrawerPrevent(); return; }
777
+ if (gesture.mode === 'open' && dx <= 0) { gesture = null; removeDrawerPrevent(); return; }
778
+ if (gesture.mode === 'close' && dx >= 0) { gesture = null; removeDrawerPrevent(); return; }
757
779
  gesture.active = true;
758
780
  suppressClickUntil = Date.now() + 450;
759
781
  document.body.classList.add(DRAGGING_CLASS);
@@ -761,8 +783,13 @@ export function setupMobileDrawer() {
761
783
 
762
784
  const width = Math.max(1, gesture.width);
763
785
  let x;
764
- if (gesture.mode === 'open') x = Math.max(-width, Math.min(0, -width + dx));
765
- else x = Math.max(-width, Math.min(0, dx));
786
+ if (gesture.mode === 'open') {
787
+ // 与 CSS 的 -102% 起点对齐,避免起手第一帧跳 6px
788
+ const startOffset = -width * 1.02;
789
+ x = Math.max(startOffset, Math.min(0, startOffset + dx));
790
+ } else {
791
+ x = Math.max(-width, Math.min(0, dx));
792
+ }
766
793
  gesture.dx = dx;
767
794
  setDragProgress(x, width);
768
795
  };
@@ -771,19 +798,19 @@ export function setupMobileDrawer() {
771
798
  if (!gesture) return;
772
799
  const current = gesture;
773
800
  gesture = null;
801
+ removeDrawerPrevent();
774
802
  if (!current.active) return;
775
803
 
776
804
  const endX = touch ? touch.clientX : current.startX + current.dx;
777
805
  const dx = endX - current.startX;
778
806
  const elapsed = Math.max(1, Date.now() - current.startT);
779
807
  const velocity = dx / elapsed;
780
- const threshold = Math.min(90, Math.max(60, current.width * 0.3));
808
+ const threshold = Math.min(80, Math.max(50, current.width * 0.22));
781
809
  const openNow = current.mode === 'open'
782
- ? dx > threshold || velocity > 0.45
783
- : !(dx < -Math.max(50, threshold * 0.7) || velocity < -0.35);
810
+ ? dx > threshold || velocity > 0.35
811
+ : !(dx < -Math.max(40, threshold * 0.6) || velocity < -0.3);
784
812
 
785
813
  document.body.classList.toggle(OPEN_CLASS, openNow);
786
- if (openNow) ensureSidebarExpanded();
787
814
  syncAria();
788
815
  suppressClickUntil = Date.now() + 250;
789
816
  // 等一帧让浏览器接住当前拖动位置,再撤掉拖动样式做吸附动画
@@ -816,6 +843,15 @@ export function setupMobileDrawer() {
816
843
  if (!disposed && document.body) document.body.classList.add('qzsy-drawer-ready');
817
844
  });
818
845
 
846
+ // 预热:手机端首屏后把宿主侧栏展开好(它在屏幕外),这样第一次侧滑不会在
847
+ // 手指下触发宿主重排/挂载完整会话列表,起手更稳。
848
+ const preExpandSidebar = () => {
849
+ if (disposed || !isMobileViewport()) return;
850
+ ensureSidebarExpanded();
851
+ };
852
+ window.setTimeout(preExpandSidebar, 600);
853
+ window.setTimeout(preExpandSidebar, 1600);
854
+
819
855
  if (typeof MutationObserver === 'function' && document.body) {
820
856
  observer = new MutationObserver(() => {
821
857
  if (!document.querySelector(`.${HEADER_CLASS}`) || !document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
@@ -841,6 +877,7 @@ export function setupMobileDrawer() {
841
877
  window.removeEventListener('touchmove', onTouchMove);
842
878
  window.removeEventListener('touchend', onTouchEnd);
843
879
  window.removeEventListener('touchcancel', onTouchCancel);
880
+ removeDrawerPrevent();
844
881
  clearModelSheet();
845
882
  clearDrag();
846
883
  document.body?.classList.remove(OPEN_CLASS);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@qzsy/dsh-bridge",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "description": "DSH 远程隧道云官方客户端插件:一条命令把本机 DeepSeek Harness 接入云端隧道(设备码绑定、短期令牌、本地门禁代理、HTTP/SSE/WebSocket 反向隧道)。",
5
- "releaseNotes": "【v3.4.0】移动端模型切换器改为底部 sheet\\n• 模型二级菜单全宽贴底:圆角、抓手、遮罩、滚动区、safe-area 底部留白\\n• 支持点遮罩关闭、顶部下拉关闭(跟手 + 松手吸附)\\n• 兼容有/无「搜索模型」输入框的宿主版本,不依赖宿主哈希\\n• 桌面端模型 popover 完全不变",
5
+ "releaseNotes": "【v3.4.2】移动端侧滑跟手优化\\n• 水平拖动阻止页面横滑/边缘返回,抽屉 1:1 跟手;纵向滚动不受影响\\n• 首屏后在屏幕外预热展开侧栏,第一次侧滑不再卡顿\\n• 起手不再跳 6px,松手时内容宽度不再 280→300 跳变\\n• 打开阈值降到约 66px,速度阈值降低,松手吸附更轻快",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
8
8
  "exports": {