dsh-remote-mobile 1.6.0 → 1.7.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.7.0] - 2026-09-15
9
+
10
+ ### Fixed & Compatibility (解决 Issue #6 · 适配官方新版三栏布局与右侧边栏)
11
+ - **解决 Issue #6:修复官方右侧边栏引入导致移动端触摸/滑动/点击被全屏拦截**:
12
+ - **Grid 轨道显式锚定**:官方新版(0.1.5-rc.1 及以上)引入右侧边栏(`_rightbarCol` / `@deepseek-ai/dsh-client-ui-sidebar-right`),整体外层 `_frame` 采用三栏 CSS Grid 布局。在移动端抽屉样式覆写(`preset-sidebar`)将左侧栏设为 `position: fixed` 后脱离了 Grid 自动放置流,导致后续列整体前移:中间正文 `_centerCol` 被移到第 1 轨(0px),而右侧边栏 `_rightbarCol` 被自动排入第 2 轨(1fr 全宽)并全屏覆盖在正文上方;其透明容器拦截了全部点击、滑动与触摸手势。现显式指定三列 Grid 轨道(`_sidebarCol` 绑定第 1 轨、`_centerCol` 绑定第 2 轨、`_rightbarCol` 绑定第 3 轨),彻底解决自动放置错位问题。
13
+ - **右侧栏折叠态穿透防御**:在窄屏下为折叠状态的右侧栏(`[data-rightbar-collapsed]` 或未处于展开状态)设置宽度清零与 `pointer-events: none !important;`,展开时恢复 `pointer-events: auto`,杜绝任何透明占位层或边缘手柄拦截触摸手势。
14
+ - **抽屉遮罩折叠态常驻兜底**:增强 `preset-sidebar` 中的全屏遮罩规则,当侧边栏内部含有 `_collapsed` 折叠类时,强制禁止渲染 `::after` 遮罩,防止页面异步加载或属性未就绪时遮罩常驻阻挡操作。
15
+ - **正文点击拦截双重守卫**:在网关层注入的移动端点击监听脚本(`MOBILE_DISMISS_SNIPPET`)中引入对侧边栏内部 `_collapsed` 类的状态检查,仅在侧边栏真正处于展开状态时才拦截正文点击以收起抽屉;在侧边栏已折叠时绝不误拦截任何主工作区操作。
16
+
17
+ ---
18
+
8
19
  ## [1.6.0] - 2026-09-06
9
20
 
10
21
  ### Mobile UX (解决 Issue #5 · 移动端体验全面重构与极窄屏排版加固)
package/README.md CHANGED
@@ -37,7 +37,7 @@
37
37
 
38
38
  DSH 核心服务出于安全考虑默认仅监听本地回环地址(`127.0.0.1`),手机、平板或其他电脑无法从外部访问 Web 控制台。本项目通过**访问控制中间件**与**请求隔离技术**,安全地开放了 **Tailscale 虚拟私网** 以及 **本地局域网 (Wi-Fi/LAN)** 访问能力,并提供传输加密、扫码配对、长期密码认证与防暴力破解审计体系。
39
39
 
40
- 手机端访问时,插件还针对 DSH 界面做了**移动端样式适配**:侧边栏抽屉折叠与可拖拽展开把手、设置弹窗居中微缩、对话正文高密度排版等,让手机上的使用体验更贴近桌面端。
40
+ 手机端访问时,插件还针对 DSH 界面做了**移动端样式适配**:侧边栏抽屉折叠与可拖拽展开把手、新版官方三栏 Grid 布局与右侧边栏无缝兼容、设置弹窗居中微缩、对话正文高密度排版等,让手机上的使用体验更贴近桌面端。
41
41
 
42
42
  ---
43
43
 
package/README_EN.md CHANGED
@@ -37,7 +37,7 @@ English Documentation · [简体中文](./README.md)
37
37
 
38
38
  By default, DSH strictly listens only to the local loopback address (`127.0.0.1`), preventing mobile devices, tablets, or external computers from accessing the Web console. This plugin uses **access control middleware** and **request isolation** to securely open up **Tailscale Private Network** and **Local Area Network (LAN / Wi-Fi)** access, complete with RSA transmission encryption, QR code pairing, persistent password auth, and automated brute-force defense.
39
39
 
40
- When accessed from a phone, the plugin also adapts the DSH interface for **mobile styles**: a collapsible sidebar drawer with a draggable floating toggle, a centered/scaled settings dialog, and dense conversation typography — so the mobile experience feels closer to the desktop.
40
+ When accessed from a phone, the plugin also adapts the DSH interface for **mobile styles**: a collapsible sidebar drawer with a draggable floating toggle, seamless compatibility with the official 3-column Grid layout & right sidebar, a centered/scaled settings dialog, and dense conversation typography — so the mobile experience feels closer to the desktop.
41
41
 
42
42
  ---
43
43
 
@@ -69,7 +69,7 @@ export declare const DRAGGABLE_NAV_SNIPPET = "\n(function() {\n if (typeof wind
69
69
  * 后缀稳定),不依赖 web-ui 注入的 data-pane / data-dsh-frame 属性,因此装/不装 web-ui
70
70
  * 两种环境行为一致。折叠状态沿用官方写入的 data-sidebar-collapsed 属性。
71
71
  */
72
- export declare const MOBILE_DISMISS_SNIPPET = "\n(function() {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") return;\n\n var frameSelector = '[class*=\"_frame\"]';\n var sidebarSelector = '[class*=\"_sidebarCol\"]';\n\n function webUiOwnsDrawer() {\n return !!document.querySelector('style[data-dsh-compat=\"responsive\"]');\n }\n\n function findToggle(frame) {\n return frame.querySelector('[class*=\"_toggle\"]') ||\n Array.prototype.filter.call(frame.querySelectorAll('button'), function (b) {\n return /\u6536\u8D77\u4FA7\u8FB9\u680F|Collapse sidebar|Open sidebar/i.test(b.getAttribute('aria-label') || '');\n })[0] || null;\n }\n\n function install(frame) {\n if (frame.__dshRmDismissInstalled__) return;\n frame.__dshRmDismissInstalled__ = true;\n var raf = 0;\n\n frame.addEventListener('click', function (event) {\n try {\n // web-ui \u53EF\u80FD\u8FD0\u884C\u4E2D\u624D\u6CE8\u5165 responsive style\uFF1A\u6BCF\u6B21\u4E8B\u4EF6\u90FD\u590D\u67E5\uFF0C\u52A8\u6001\u8BA9\u4F4D\n if (webUiOwnsDrawer()) return;\n var target = event.target;\n if (!target || !(target instanceof Element)) return;\n if (typeof window.matchMedia !== 'function' || !window.matchMedia('(max-width: 900px)').matches) return;\n var sidebar = frame.querySelector(sidebarSelector);\n var toggle = findToggle(frame);\n if (!sidebar || !toggle) return;\n\n // \u62BD\u5C49\u5C55\u5F00\u65F6\u70B9\u51FB\u6B63\u6587\uFF08sidebar \u5916\uFF09\u2192 \u89E6\u53D1\u5B98\u65B9\u6536\u8D77\u6309\u94AE\uFF0C\u5E76\u62E6\u622A\u88AB\u541E\u6389\u7684\u70B9\u51FB\uFF1B\n // \u627E\u4E0D\u5230\u6536\u8D77\u6309\u94AE\u65F6\u4E0A\u9762\u5DF2 return\uFF0C\u7EDD\u4E0D\u8BEF\u62E6\u6B63\u6587\u4EA4\u4E92\n if (!frame.hasAttribute('data-sidebar-collapsed') && !sidebar.contains(target)) {\n event.preventDefault();\n event.stopPropagation();\n toggle.click();\n return;\n }\n // \u70B9\u51FB\u4FA7\u8FB9\u680F\u5185\u6B21\u7EA7\u64CD\u4F5C\u56FE\u6807\uFF08\u66F4\u591A\u64CD\u4F5C \u22EF / \u5C55\u5F00\u7BAD\u5934\u7B49\uFF09\u3001\u5DE5\u4F5C\u533A\u884C\uFF08_projectRow\uFF09\u6216\u539F\u751F\u5207\u6362\u6309\u94AE \u2192 \u4E0D\u6536\u8D77\u62BD\u5C49\n if (target.closest('[class*=\"_toggle\"], [class*=\"_rowActions\"], [class*=\"_projectRow\"], [class*=\"_chevron\"], [class*=\"_arrow\"], button, [role=\"button\"]')) return;\n // \u70B9\u51FB\u4F1A\u8BDD\u6761\u76EE\uFF08_sessionRow\uFF09\u4E3B\u4F53\uFF08\u9009\u4E2D\u4F1A\u8BDD\u540E\u56DE\u4E3B\u533A\uFF09\u2192 \u4E0B\u4E00\u5E27\u7A0B\u5E8F\u5316\u6536\u8D77\n if (!target.closest('[class*=\"_sessionRow\"], [role=\"treeitem\"], [data-dsh-part=\"sidebar-entry\"]')) return;\n if (raf !== 0) cancelAnimationFrame(raf);\n raf = requestAnimationFrame(function () {\n raf = 0;\n if (!frame.hasAttribute('data-sidebar-collapsed')) toggle.click();\n });\n } catch (err) {}\n }, true);\n }\n\n function ensure() {\n if (webUiOwnsDrawer()) return;\n var frame = document.querySelector(frameSelector);\n if (frame) install(frame);\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', ensure);\n } else {\n ensure();\n }\n setInterval(ensure, 800);\n})();\n";
72
+ export declare const MOBILE_DISMISS_SNIPPET = "\n(function() {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") return;\n\n var frameSelector = '[class*=\"_frame\"]';\n var sidebarSelector = '[class*=\"_sidebarCol\"]';\n\n function webUiOwnsDrawer() {\n return !!document.querySelector('style[data-dsh-compat=\"responsive\"]');\n }\n\n function findToggle(frame) {\n return frame.querySelector('[class*=\"_toggle\"]') ||\n Array.prototype.filter.call(frame.querySelectorAll('button'), function (b) {\n return /\u6536\u8D77\u4FA7\u8FB9\u680F|Collapse sidebar|Open sidebar/i.test(b.getAttribute('aria-label') || '');\n })[0] || null;\n }\n\n function install(frame) {\n if (frame.__dshRmDismissInstalled__) return;\n frame.__dshRmDismissInstalled__ = true;\n var raf = 0;\n\n frame.addEventListener('click', function (event) {\n try {\n // web-ui \u53EF\u80FD\u8FD0\u884C\u4E2D\u624D\u6CE8\u5165 responsive style\uFF1A\u6BCF\u6B21\u4E8B\u4EF6\u90FD\u590D\u67E5\uFF0C\u52A8\u6001\u8BA9\u4F4D\n if (webUiOwnsDrawer()) return;\n var target = event.target;\n if (!target || !(target instanceof Element)) return;\n if (typeof window.matchMedia !== 'function' || !window.matchMedia('(max-width: 900px)').matches) return;\n var sidebar = frame.querySelector(sidebarSelector);\n var toggle = findToggle(frame);\n if (!sidebar || !toggle) return;\n\n // \u62BD\u5C49\u5C55\u5F00\u65F6\u70B9\u51FB\u6B63\u6587\uFF08sidebar \u5916\uFF09\u2192 \u89E6\u53D1\u5B98\u65B9\u6536\u8D77\u6309\u94AE\uFF0C\u5E76\u62E6\u622A\u88AB\u541E\u6389\u7684\u70B9\u51FB\uFF1B\n // \u627E\u4E0D\u5230\u6536\u8D77\u6309\u94AE\u65F6\u4E0A\u9762\u5DF2 return\uFF0C\u7EDD\u4E0D\u8BEF\u62E6\u6B63\u6587\u4EA4\u4E92\uFF1B\n // \u589E\u52A0\u5BF9 sidebar \u5185\u90E8 _collapsed \u7684\u72B6\u6001\u6821\u9A8C\uFF0C\u53CC\u91CD\u4FDD\u8BC1\u4EC5\u5728\u4FA7\u8FB9\u680F\u771F\u6B63\u5C55\u5F00\u65F6\u624D\u62E6\u622A\u6B63\u6587\u70B9\u51FB\n var isSidebarOpen = !frame.hasAttribute('data-sidebar-collapsed') && !sidebar.querySelector('[class*=\"_collapsed\"]');\n if (isSidebarOpen && !sidebar.contains(target)) {\n event.preventDefault();\n event.stopPropagation();\n toggle.click();\n return;\n }\n // \u70B9\u51FB\u4FA7\u8FB9\u680F\u5185\u6B21\u7EA7\u64CD\u4F5C\u56FE\u6807\uFF08\u66F4\u591A\u64CD\u4F5C \u22EF / \u5C55\u5F00\u7BAD\u5934\u7B49\uFF09\u3001\u5DE5\u4F5C\u533A\u884C\uFF08_projectRow\uFF09\u6216\u539F\u751F\u5207\u6362\u6309\u94AE \u2192 \u4E0D\u6536\u8D77\u62BD\u5C49\n if (target.closest('[class*=\"_toggle\"], [class*=\"_rowActions\"], [class*=\"_projectRow\"], [class*=\"_chevron\"], [class*=\"_arrow\"], button, [role=\"button\"]')) return;\n // \u70B9\u51FB\u4F1A\u8BDD\u6761\u76EE\uFF08_sessionRow\uFF09\u4E3B\u4F53\uFF08\u9009\u4E2D\u4F1A\u8BDD\u540E\u56DE\u4E3B\u533A\uFF09\u2192 \u4E0B\u4E00\u5E27\u7A0B\u5E8F\u5316\u6536\u8D77\n if (!target.closest('[class*=\"_sessionRow\"], [role=\"treeitem\"], [data-dsh-part=\"sidebar-entry\"]')) return;\n if (raf !== 0) cancelAnimationFrame(raf);\n raf = requestAnimationFrame(function () {\n raf = 0;\n if (!frame.hasAttribute('data-sidebar-collapsed')) toggle.click();\n });\n } catch (err) {}\n }, true);\n }\n\n function ensure() {\n if (webUiOwnsDrawer()) return;\n var frame = document.querySelector(frameSelector);\n if (frame) install(frame);\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', ensure);\n } else {\n ensure();\n }\n setInterval(ensure, 800);\n})();\n";
73
73
  /**
74
74
  * 挂载底层 http.Server 门禁劫持与回环上下文虚拟化代理
75
75
  *
@@ -295,8 +295,10 @@ export const MOBILE_DISMISS_SNIPPET = `
295
295
  if (!sidebar || !toggle) return;
296
296
 
297
297
  // 抽屉展开时点击正文(sidebar 外)→ 触发官方收起按钮,并拦截被吞掉的点击;
298
- // 找不到收起按钮时上面已 return,绝不误拦正文交互
299
- if (!frame.hasAttribute('data-sidebar-collapsed') && !sidebar.contains(target)) {
298
+ // 找不到收起按钮时上面已 return,绝不误拦正文交互;
299
+ // 增加对 sidebar 内部 _collapsed 的状态校验,双重保证仅在侧边栏真正展开时才拦截正文点击
300
+ var isSidebarOpen = !frame.hasAttribute('data-sidebar-collapsed') && !sidebar.querySelector('[class*="_collapsed"]');
301
+ if (isSidebarOpen && !sidebar.contains(target)) {
300
302
  event.preventDefault();
301
303
  event.stopPropagation();
302
304
  toggle.click();
package/lib/client.js CHANGED
@@ -3526,8 +3526,8 @@ function StylesCard({ lang, showToast }) {
3526
3526
  }
3527
3527
 
3528
3528
  // src/client/version.js
3529
- var PLUGIN_VERSION = "v1.6.0";
3530
- var BUILD_TIME = "2026-09-06 01:42:18";
3529
+ var PLUGIN_VERSION = "v1.7.0";
3530
+ var BUILD_TIME = "2026-09-15 23:45:02";
3531
3531
 
3532
3532
  // src/client/components/MainSection.jsx
3533
3533
  var import_jsx_runtime9 = require("react/jsx-runtime");
@@ -62,11 +62,39 @@ const PRESET_SIDEBAR_CSS = `
62
62
  position: relative !important;
63
63
  }
64
64
 
65
- [class*="_frame"]:has([class*="_sidebarCol"]) [class*="_centerCol"] {
65
+ /* 核心修复:显式将三列锁定到各自的 Grid 轨道!
66
+ 官方 AppFrame 为三栏 CSS Grid 布局(sidebarCol + centerCol + rightbarCol)。
67
+ 当 sidebarCol 设为 position: fixed 后脱离 Grid 自动流,会导致后续两列前移:
68
+ centerCol 移入第 1 列(0px),rightbarCol 移入第 2 列(1fr 全宽) 并全屏覆盖在
69
+ 对话区上方,其透明容器拦截全部点击与触摸手势(Issue #6)。
70
+ 通过显式指定 grid-column,确保每列始终位于设计轨道,杜绝错位覆盖。 */
71
+ [class*="_frame"]:has([class*="_sidebarCol"]) > [class*="_sidebarCol"] {
72
+ grid-column: 1 !important;
73
+ }
74
+
75
+ [class*="_frame"]:has([class*="_sidebarCol"]) [class*="_centerCol"],
76
+ [class*="_frame"]:has([class*="_sidebarCol"]) > [class*="_centerCol"] {
77
+ grid-column: 2 !important;
66
78
  width: 100vw !important;
67
79
  max-width: 100vw !important;
68
80
  }
69
81
 
82
+ [class*="_frame"]:has([class*="_sidebarCol"]) > [class*="_rightbarCol"] {
83
+ grid-column: 3 !important;
84
+ }
85
+
86
+ /* 右侧栏折叠态穿透防御:折叠时宽度清零并禁用指针事件,杜绝透明层与边缘手柄拦截触摸 */
87
+ [class*="_frame"][data-rightbar-collapsed] [class*="_rightbarCol"],
88
+ [class*="_rightbarCol"]:not(:has([data-sidebar-right-open])) {
89
+ width: 0px !important;
90
+ min-width: 0px !important;
91
+ pointer-events: none !important;
92
+ }
93
+ [class*="_rightbarCol"]:has([data-sidebar-right-open]),
94
+ [class*="_frame"]:not([data-rightbar-collapsed]) [class*="_rightbarCol"] {
95
+ pointer-events: auto !important;
96
+ }
97
+
70
98
  /* 2. 侧边栏最外层容器:作为整体抽屉浮层(阴影仅加在最外层,严禁污染内部列表) */
71
99
  [class*="_sidebarCol"] {
72
100
  position: fixed !important;
@@ -116,6 +144,14 @@ const PRESET_SIDEBAR_CSS = `
116
144
  background: rgb(0 0 0 / 24%);
117
145
  }
118
146
 
147
+ /* 兜底防御:当左侧栏实际处于折叠状态(内部含 _collapsed 类,如官方仅收起内部但 frame
148
+ 未及时打上 data-sidebar-collapsed)时,严禁渲染遮罩,彻底杜绝黑纱常驻与点击拦截 */
149
+ [class*="_frame"]:has([class*="_sidebarCol"]:has([class*="_collapsed"]))::after,
150
+ [class*="_frame"][data-sidebar-collapsed]::after {
151
+ content: none !important;
152
+ display: none !important;
153
+ }
154
+
119
155
  /* 展开态自适应宽度:支持 360px 窄屏与大屏手机灵活伸缩,覆盖官方内联 280px */
120
156
  [class*="_sidebarCol"]:not(:has([class*="_collapsed"])) {
121
157
  width: min(85vw, 320px) !important;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-mobile",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "DeepSeek Harness 远程与移动端安全网关插件:零修改 DSH 底层代码安全开放局域网与 Tailscale 连接 | DeepSeek Harness (DSH) Remote & Mobile Security Guard: safely opens Tailscale/LAN with zero core modifications, QR scan auth, RSA encryption & brute-force defense.",
5
5
  "keywords": [
6
6
  "dsh",