@qzsy/dsh-bridge 3.4.1 → 3.4.3

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,33 @@
1
1
  # 更新日志
2
2
 
3
+ ## [v3.4.3] - 2026-11-02
4
+
5
+ ### 🐛 修复
6
+
7
+ - **模型 sheet 默认不聚焦搜索框**:宿主打开模型菜单时会自动 focus「搜索模型」,iOS 键盘随即弹起、把 sheet 顶掉一半;现在 sheet 套用后立即以及 80/260ms 内自动 `blur`,键盘不会默认弹出。
8
+ - **保留手动搜索**:用户主动点搜索框(`pointerdown`/`touchstart`)后不再被 blur,需要搜索时仍可正常输入。
9
+ - 模型数量少的场景默认直接看到完整模型列表,不再被键盘遮挡。
10
+
11
+ ### 🧪 验证
12
+
13
+ - 模拟宿主自动 focus:打开后 `activeElement` 从 input 回到 BODY,sheet 正常显示,4/4 通过;用户主动 pointerdown 后 focus 保持输入框。
14
+
15
+
16
+ ## [v3.4.2] - 2026-11-02
17
+
18
+ ### 🐛 修复
19
+
20
+ - **侧滑跟手**:水平拖动期间动态注册非 passive `touchmove` 阻止页面横滑/边缘返回,抽屉真正 1:1 跟手;纵向滚动不拦截。
21
+ - **首滑不卡**:手机首屏后在屏幕外预热展开侧栏,第一次侧滑不再在手指下触发宿主重排/挂载完整会话列表。
22
+ - **起手不跳**:拖动起点与 CSS `-102%` 关闭位对齐,修掉第一帧约 6px 跳变。
23
+ - **内容不弹**:拖动期间也强制侧栏内容 `100%` 宽,松手时不再出现 `280px → 300px` 宽度跳变。
24
+ - **松手更轻**:打开距离阈值从 90px 降到约 66px、速度阈值降低;移除松手时重复的展开点击;`box-shadow` 改为过渡。
25
+
26
+ ### 🧪 验证
27
+
28
+ - 真实触摸事件模拟:预热展开、dx=20 首帧 -286、水平移动 `defaultPrevented=true`、纵向不拦截、短滑 110px 吸附打开、阴影过渡,7/7 通过。
29
+
30
+
3
31
  ## [v3.4.1] - 2026-11-02
4
32
 
5
33
  ### 🐛 修复
package/README.md CHANGED
@@ -48,7 +48,7 @@ dsh plugin --profile <name> add @qzsy/dsh-bridge
48
48
 
49
49
  - 内容区占满屏宽,左上角「菜单」按钮打开会话 / 工作区 / 插件入口;
50
50
  - 支持手势:左缘向右滑打开、抽屉内向左滑关闭,拖动时跟手,松手吸附;
51
- - 模型切换器在手机上改为底部 sheet:全宽、抓手、遮罩,支持下拉/点遮罩关闭;
51
+ - 模型切换器在手机上改为底部 sheet:全宽、抓手、遮罩,支持下拉/点遮罩关闭;默认不聚焦搜索框(不弹键盘),需要搜索时再点;
52
52
  - 全面屏自动避开安全区:刘海 / 状态栏 / 底部 home indicator 不遮挡内容;
53
53
  - 点遮罩、点会话行、按 Esc 或转回桌面宽度都会自动收起;
54
54
  - 桌面端保持 DSH 原生布局,不受影响。
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);
@@ -789,6 +803,33 @@ function setupMobileDrawer() {
789
803
  portal.classList.add("qzsy-model-sheet");
790
804
  attachModelDrag(portal);
791
805
  ensureModelBackdrop();
806
+ const input = portal.querySelector('input[placeholder*="\u641C\u7D22\u6A21\u578B"], input[placeholder*="Search model"], input[type="search"], input');
807
+ if (input) {
808
+ let userIntent = false;
809
+ const markUserIntent = () => {
810
+ userIntent = true;
811
+ };
812
+ input.addEventListener("pointerdown", markUserIntent, { once: true });
813
+ input.addEventListener("touchstart", markUserIntent, { once: true, passive: true });
814
+ const blurIfAutoFocused = () => {
815
+ if (!input.isConnected || userIntent) return;
816
+ if (document.activeElement === input) {
817
+ try {
818
+ input.blur();
819
+ } catch {
820
+ }
821
+ }
822
+ };
823
+ const onFocus = () => {
824
+ if (!userIntent) blurIfAutoFocused();
825
+ };
826
+ input.addEventListener("focus", onFocus);
827
+ blurIfAutoFocused();
828
+ window.requestAnimationFrame(blurIfAutoFocused);
829
+ window.setTimeout(blurIfAutoFocused, 80);
830
+ window.setTimeout(blurIfAutoFocused, 260);
831
+ window.setTimeout(() => input.removeEventListener("focus", onFocus), 900);
832
+ }
792
833
  window.requestAnimationFrame(() => {
793
834
  if (!disposed && modelPortal === portal) document.body.classList.add("qzsy-model-sheet-open");
794
835
  });
@@ -888,6 +929,7 @@ function setupMobileDrawer() {
888
929
  active: false,
889
930
  dx: 0
890
931
  };
932
+ window.addEventListener("touchmove", preventDrawerScroll, { passive: false });
891
933
  return;
892
934
  }
893
935
  if (!sidebar.contains(event.target)) return;
@@ -901,6 +943,7 @@ function setupMobileDrawer() {
901
943
  active: false,
902
944
  dx: 0
903
945
  };
946
+ window.addEventListener("touchmove", preventDrawerScroll, { passive: false });
904
947
  };
905
948
  const onTouchMove = (event) => {
906
949
  if (modelPortal || !gesture || disposed) return;
@@ -912,14 +955,17 @@ function setupMobileDrawer() {
912
955
  if (Math.abs(dx) < DRAG_THRESHOLD) return;
913
956
  if (Math.abs(dx) < Math.abs(dy) * 1.2) {
914
957
  gesture = null;
958
+ removeDrawerPrevent();
915
959
  return;
916
960
  }
917
961
  if (gesture.mode === "open" && dx <= 0) {
918
962
  gesture = null;
963
+ removeDrawerPrevent();
919
964
  return;
920
965
  }
921
966
  if (gesture.mode === "close" && dx >= 0) {
922
967
  gesture = null;
968
+ removeDrawerPrevent();
923
969
  return;
924
970
  }
925
971
  gesture.active = true;
@@ -928,8 +974,12 @@ function setupMobileDrawer() {
928
974
  }
929
975
  const width = Math.max(1, gesture.width);
930
976
  let x;
931
- if (gesture.mode === "open") x = Math.max(-width, Math.min(0, -width + dx));
932
- else x = Math.max(-width, Math.min(0, dx));
977
+ if (gesture.mode === "open") {
978
+ const startOffset = -width * 1.02;
979
+ x = Math.max(startOffset, Math.min(0, startOffset + dx));
980
+ } else {
981
+ x = Math.max(-width, Math.min(0, dx));
982
+ }
933
983
  gesture.dx = dx;
934
984
  setDragProgress(x, width);
935
985
  };
@@ -937,15 +987,15 @@ function setupMobileDrawer() {
937
987
  if (!gesture) return;
938
988
  const current = gesture;
939
989
  gesture = null;
990
+ removeDrawerPrevent();
940
991
  if (!current.active) return;
941
992
  const endX = touch ? touch.clientX : current.startX + current.dx;
942
993
  const dx = endX - current.startX;
943
994
  const elapsed = Math.max(1, Date.now() - current.startT);
944
995
  const velocity = dx / elapsed;
945
- const threshold = Math.min(90, Math.max(60, current.width * 0.3));
946
- const openNow = current.mode === "open" ? dx > threshold || velocity > 0.45 : !(dx < -Math.max(50, threshold * 0.7) || velocity < -0.35);
996
+ const threshold = Math.min(80, Math.max(50, current.width * 0.22));
997
+ const openNow = current.mode === "open" ? dx > threshold || velocity > 0.35 : !(dx < -Math.max(40, threshold * 0.6) || velocity < -0.3);
947
998
  document.body.classList.toggle(OPEN_CLASS, openNow);
948
- if (openNow) ensureSidebarExpanded();
949
999
  syncAria();
950
1000
  suppressClickUntil = Date.now() + 250;
951
1001
  window.requestAnimationFrame(clearDrag);
@@ -971,6 +1021,12 @@ function setupMobileDrawer() {
971
1021
  window.requestAnimationFrame(() => {
972
1022
  if (!disposed && document.body) document.body.classList.add("qzsy-drawer-ready");
973
1023
  });
1024
+ const preExpandSidebar = () => {
1025
+ if (disposed || !isMobileViewport()) return;
1026
+ ensureSidebarExpanded();
1027
+ };
1028
+ window.setTimeout(preExpandSidebar, 600);
1029
+ window.setTimeout(preExpandSidebar, 1600);
974
1030
  if (typeof MutationObserver === "function" && document.body) {
975
1031
  observer = new MutationObserver(() => {
976
1032
  if (!document.querySelector(`.${HEADER_CLASS}`) || !document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
@@ -997,6 +1053,7 @@ function setupMobileDrawer() {
997
1053
  window.removeEventListener("touchmove", onTouchMove);
998
1054
  window.removeEventListener("touchend", onTouchEnd);
999
1055
  window.removeEventListener("touchcancel", onTouchCancel);
1056
+ removeDrawerPrevent();
1000
1057
  clearModelSheet();
1001
1058
  clearDrag();
1002
1059
  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 = () => {
@@ -610,6 +626,30 @@ export function setupMobileDrawer() {
610
626
  portal.classList.add('qzsy-model-sheet');
611
627
  attachModelDrag(portal);
612
628
  ensureModelBackdrop();
629
+
630
+ // 宿主菜单默认会聚焦「搜索模型」,手机上会直接弹起键盘把 sheet 顶掉一半。
631
+ // 移动端模型数量通常很少,默认不聚焦;用户主动点搜索框时再允许聚焦。
632
+ const input = portal.querySelector('input[placeholder*="搜索模型"], input[placeholder*="Search model"], input[type="search"], input');
633
+ if (input) {
634
+ let userIntent = false;
635
+ const markUserIntent = () => { userIntent = true; };
636
+ input.addEventListener('pointerdown', markUserIntent, { once: true });
637
+ input.addEventListener('touchstart', markUserIntent, { once: true, passive: true });
638
+ const blurIfAutoFocused = () => {
639
+ if (!input.isConnected || userIntent) return;
640
+ if (document.activeElement === input) {
641
+ try { input.blur(); } catch { /* 忽略 */ }
642
+ }
643
+ };
644
+ const onFocus = () => { if (!userIntent) blurIfAutoFocused(); };
645
+ input.addEventListener('focus', onFocus);
646
+ blurIfAutoFocused();
647
+ window.requestAnimationFrame(blurIfAutoFocused);
648
+ window.setTimeout(blurIfAutoFocused, 80);
649
+ window.setTimeout(blurIfAutoFocused, 260);
650
+ window.setTimeout(() => input.removeEventListener('focus', onFocus), 900);
651
+ }
652
+
613
653
  window.requestAnimationFrame(() => {
614
654
  if (!disposed && modelPortal === portal) document.body.classList.add('qzsy-model-sheet-open');
615
655
  });
@@ -729,6 +769,7 @@ export function setupMobileDrawer() {
729
769
  active: false,
730
770
  dx: 0,
731
771
  };
772
+ window.addEventListener('touchmove', preventDrawerScroll, { passive: false });
732
773
  return;
733
774
  }
734
775
 
@@ -743,6 +784,7 @@ export function setupMobileDrawer() {
743
784
  active: false,
744
785
  dx: 0,
745
786
  };
787
+ window.addEventListener('touchmove', preventDrawerScroll, { passive: false });
746
788
  };
747
789
 
748
790
  const onTouchMove = (event) => {
@@ -755,9 +797,9 @@ export function setupMobileDrawer() {
755
797
  if (!gesture.active) {
756
798
  if (Math.abs(dx) < DRAG_THRESHOLD) return;
757
799
  // 纵向为主:交还给宿主滚动,本次不拦截
758
- if (Math.abs(dx) < Math.abs(dy) * 1.2) { gesture = null; return; }
759
- if (gesture.mode === 'open' && dx <= 0) { gesture = null; return; }
760
- if (gesture.mode === 'close' && dx >= 0) { gesture = null; return; }
800
+ if (Math.abs(dx) < Math.abs(dy) * 1.2) { gesture = null; removeDrawerPrevent(); return; }
801
+ if (gesture.mode === 'open' && dx <= 0) { gesture = null; removeDrawerPrevent(); return; }
802
+ if (gesture.mode === 'close' && dx >= 0) { gesture = null; removeDrawerPrevent(); return; }
761
803
  gesture.active = true;
762
804
  suppressClickUntil = Date.now() + 450;
763
805
  document.body.classList.add(DRAGGING_CLASS);
@@ -765,8 +807,13 @@ export function setupMobileDrawer() {
765
807
 
766
808
  const width = Math.max(1, gesture.width);
767
809
  let x;
768
- if (gesture.mode === 'open') x = Math.max(-width, Math.min(0, -width + dx));
769
- else x = Math.max(-width, Math.min(0, dx));
810
+ if (gesture.mode === 'open') {
811
+ // 与 CSS 的 -102% 起点对齐,避免起手第一帧跳 6px
812
+ const startOffset = -width * 1.02;
813
+ x = Math.max(startOffset, Math.min(0, startOffset + dx));
814
+ } else {
815
+ x = Math.max(-width, Math.min(0, dx));
816
+ }
770
817
  gesture.dx = dx;
771
818
  setDragProgress(x, width);
772
819
  };
@@ -775,19 +822,19 @@ export function setupMobileDrawer() {
775
822
  if (!gesture) return;
776
823
  const current = gesture;
777
824
  gesture = null;
825
+ removeDrawerPrevent();
778
826
  if (!current.active) return;
779
827
 
780
828
  const endX = touch ? touch.clientX : current.startX + current.dx;
781
829
  const dx = endX - current.startX;
782
830
  const elapsed = Math.max(1, Date.now() - current.startT);
783
831
  const velocity = dx / elapsed;
784
- const threshold = Math.min(90, Math.max(60, current.width * 0.3));
832
+ const threshold = Math.min(80, Math.max(50, current.width * 0.22));
785
833
  const openNow = current.mode === 'open'
786
- ? dx > threshold || velocity > 0.45
787
- : !(dx < -Math.max(50, threshold * 0.7) || velocity < -0.35);
834
+ ? dx > threshold || velocity > 0.35
835
+ : !(dx < -Math.max(40, threshold * 0.6) || velocity < -0.3);
788
836
 
789
837
  document.body.classList.toggle(OPEN_CLASS, openNow);
790
- if (openNow) ensureSidebarExpanded();
791
838
  syncAria();
792
839
  suppressClickUntil = Date.now() + 250;
793
840
  // 等一帧让浏览器接住当前拖动位置,再撤掉拖动样式做吸附动画
@@ -820,6 +867,15 @@ export function setupMobileDrawer() {
820
867
  if (!disposed && document.body) document.body.classList.add('qzsy-drawer-ready');
821
868
  });
822
869
 
870
+ // 预热:手机端首屏后把宿主侧栏展开好(它在屏幕外),这样第一次侧滑不会在
871
+ // 手指下触发宿主重排/挂载完整会话列表,起手更稳。
872
+ const preExpandSidebar = () => {
873
+ if (disposed || !isMobileViewport()) return;
874
+ ensureSidebarExpanded();
875
+ };
876
+ window.setTimeout(preExpandSidebar, 600);
877
+ window.setTimeout(preExpandSidebar, 1600);
878
+
823
879
  if (typeof MutationObserver === 'function' && document.body) {
824
880
  observer = new MutationObserver(() => {
825
881
  if (!document.querySelector(`.${HEADER_CLASS}`) || !document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
@@ -845,6 +901,7 @@ export function setupMobileDrawer() {
845
901
  window.removeEventListener('touchmove', onTouchMove);
846
902
  window.removeEventListener('touchend', onTouchEnd);
847
903
  window.removeEventListener('touchcancel', onTouchCancel);
904
+ removeDrawerPrevent();
848
905
  clearModelSheet();
849
906
  clearDrag();
850
907
  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.1",
3
+ "version": "3.4.3",
4
4
  "description": "DSH 远程隧道云官方客户端插件:一条命令把本机 DeepSeek Harness 接入云端隧道(设备码绑定、短期令牌、本地门禁代理、HTTP/SSE/WebSocket 反向隧道)。",
5
- "releaseNotes": "【v3.4.1】模型 sheet 识别收紧\\n• 模型菜单改造后立即清空触发链,关闭后再开账号菜单不会误套底部 sheet\\n• 优先按 data-model-id 等模型标记定位;无搜索框宿主的兜底窗口收紧到 2.5 秒\\n• 第一层「模型 / 推理等级」popover 保持不变",
5
+ "releaseNotes": "【v3.4.3】模型 sheet 默认不弹键盘\\n• 宿主打开模型菜单会自动聚焦搜索框,iOS 键盘把 sheet 顶掉一半;现在自动 blur\\n• 模型数量少时默认直接看到完整列表;需要搜索时主动点搜索框仍可输入\\n• 立即 / 80ms / 260ms 三次兜底 blur,不误伤用户主动点击",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
8
8
  "exports": {