dsh-bridge-gateway 0.1.6 → 0.1.7

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
@@ -2,6 +2,17 @@
2
2
 
3
3
  本项目 `dsh-bridge-gateway` 是基于 [dsh-bridge](https://github.com/wenbin-wb/dsh-bridge) 的移植增强分支,在保留原版能力之外,重点新增「公网直连网关」。
4
4
 
5
+ ## [0.1.7] - 2026-09-12
6
+
7
+ ### 变更(移动端第三批:标题行密度)
8
+ - 对话/轨迹/上下文 改为标题行内的「对话 ▾」下拉按钮(弃用胶囊方案):插在标准模式与访达图标之间,原生样式观感,点开下拉切换视图,文案动态读取、语言无关。
9
+ - 修复 tabs 胶囊与标题行折成两行的问题:删除级联中残留的 titleRow wrap 规则,标题行强制单行。
10
+ - 原生 corner 按钮(打开右侧边栏)负边距归零,单行布局下不再被裁切。
11
+
12
+ ### 修复
13
+ - workbuddy-connect 的 Reasoning levels 按钮:移动端删除文字节点只留图标(此前裁切露字),悬停宽度锁定不再引起工具栏抖动。
14
+ - 修复标签文案同步无条件写入 textContent 触发 MutationObserver 死循环导致页面卡死的问题。
15
+
5
16
  ## [0.1.6] - 2026-09-12
6
17
 
7
18
  ### 修复(移动端第二批:布局密度与面板)
package/client/client.js CHANGED
@@ -3829,62 +3829,97 @@ function injectMobileStyles() {
3829
3829
  display: none !important;
3830
3830
  }
3831
3831
 
3832
- /* ---- \u5BF9\u8BDD/\u8F68\u8FF9/\u4E0A\u4E0B\u6587 \u6807\u7B7E\u884C\u6298\u53E0\u8FDB\u6807\u9898\u884C ----
3833
- \u539F\u672C tabs \u72EC\u5360\u4E00\u884C\uFF0825px + \u95F4\u8DDD\uFF09\uFF0C\u79FB\u52A8\u7AEF\u4E2D\u95F4\u7A7A\u95F4\u592A\u5C0F\u3002
3834
- \u6539\u4E3A\u7EDD\u5BF9\u5B9A\u4F4D\u7684\u60AC\u6D6E\u80F6\u56CA\uFF1A\u9489\u5728\u6807\u9898\u884C\u4E0B\u7F18\u53F3\u4FA7\uFF0C\u4E0D\u518D\u5360\u636E\u5E03\u5C40\u9AD8\u5EA6\uFF0C
3835
- \u4E2D\u95F4\u5185\u5BB9\u533A\u51C0\u589E\u7EA6 33px\uFF1B\u80CC\u666F\u7528\u539F\u751F tooltip \u7070\u4E0E\u5185\u5BB9\u5206\u5C42\u3002 */
3836
- header[class*="wSkVaW_header"] {
3837
- position: relative !important;
3838
- }
3832
+ /* ---- \u5BF9\u8BDD/\u8F68\u8FF9/\u4E0A\u4E0B\u6587 \u6807\u7B7E\u6298\u53E0\u8FDB\u6807\u9898\u884C\uFF08\u540C\u4E00\u884C\uFF09 ----
3833
+ \u539F\u751F tablist \u6574\u884C\u9690\u85CF\uFF1B\u7531 setupMobileExperience \u6CE8\u5165\u4E00\u4E2A\u539F\u751F\u6837\u5F0F\u7684
3834
+ \u300C\u5BF9\u8BDD\u300D\u6309\u94AE\uFF08\u63D2\u5728\u6807\u51C6\u6A21\u5F0F\u4E0E\u8BBF\u8FBE\u56FE\u6807\u4E4B\u95F4\uFF09\uFF0C\u70B9\u51FB\u5F39\u4E0B\u62C9\u5207\u6362\u4E09\u4E2A\u89C6\u56FE\u3002
3835
+ \u6309\u94AE\u6587\u6848\u4E0E\u9009\u9879\u6587\u6848\u90FD\u4ECE\u539F\u751F tab \u52A8\u6001\u8BFB\u53D6\uFF0C\u8BED\u8A00\u65E0\u5173\u3002 */
3839
3836
  div[class*="wSkVaW_tabs"] {
3840
- position: absolute !important;
3841
- top: 38px !important;
3842
- right: 6px !important;
3843
- left: auto !important;
3844
- width: auto !important;
3845
- max-width: 62vw !important;
3846
- height: 30px !important;
3847
- display: inline-flex !important;
3848
- align-items: center !important;
3849
- gap: 2px !important;
3850
- padding: 2px !important;
3851
- border-radius: 999px !important;
3852
- background: var(--dsw-alias-tooltip-bg, #43454a) !important;
3853
- box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3) !important;
3854
- overflow-x: auto !important;
3855
- scrollbar-width: none !important;
3856
- z-index: 30 !important;
3857
- }
3858
- div[class*="wSkVaW_tabs"]::-webkit-scrollbar {
3859
3837
  display: none !important;
3860
3838
  }
3861
- div[class*="wSkVaW_tabs"] [role="tab"] {
3862
- height: 24px !important;
3839
+ .dsh-mobile-tab-btn {
3840
+ display: inline-flex !important;
3841
+ align-items: center !important;
3842
+ gap: 5px !important;
3843
+ position: relative !important;
3844
+ height: 28px !important;
3845
+ min-height: 28px !important;
3863
3846
  padding: 0 10px !important;
3864
3847
  border-radius: 999px !important;
3865
- font-size: 12px !important;
3848
+ background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, 0.12)) !important;
3849
+ color: var(--dsw-alias-label-primary, inherit) !important;
3850
+ font-size: 12.5px !important;
3851
+ line-height: 28px !important;
3866
3852
  white-space: nowrap !important;
3853
+ cursor: pointer !important;
3854
+ }
3855
+ .dsh-mobile-tab-btn svg {
3867
3856
  flex: 0 0 auto !important;
3868
3857
  }
3858
+ .dsh-mobile-tab-dropdown {
3859
+ position: absolute !important;
3860
+ top: calc(100% + 6px) !important;
3861
+ left: 0 !important;
3862
+ min-width: 132px !important;
3863
+ z-index: 60 !important;
3864
+ padding: 4px !important;
3865
+ border-radius: 12px !important;
3866
+ background: var(--dsw-alias-bg-layer-2, #2c2c2e) !important;
3867
+ border: 1px solid var(--dsw-alias-border-l2, transparent) !important;
3868
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35) !important;
3869
+ }
3870
+ .dsh-mobile-tab-dropdown .dsh-mobile-tab-option {
3871
+ display: flex !important;
3872
+ align-items: center !important;
3873
+ justify-content: space-between !important;
3874
+ gap: 8px !important;
3875
+ width: 100% !important;
3876
+ padding: 7px 10px !important;
3877
+ border: 0 !important;
3878
+ border-radius: 8px !important;
3879
+ background: transparent !important;
3880
+ color: var(--dsw-alias-label-primary, inherit) !important;
3881
+ font-size: 13px !important;
3882
+ line-height: 18px !important;
3883
+ cursor: pointer !important;
3884
+ text-align: left !important;
3885
+ }
3886
+ .dsh-mobile-tab-dropdown .dsh-mobile-tab-option:hover {
3887
+ background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.15)) !important;
3888
+ }
3889
+ .dsh-mobile-tab-dropdown .dsh-mobile-tab-option[data-active='true'] {
3890
+ color: var(--dsw-alias-brand-text, var(--dsw-alias-label-primary, inherit)) !important;
3891
+ font-weight: 600 !important;
3892
+ }
3869
3893
 
3870
3894
  /* ---- \u9876\u680F\u300C\u6807\u51C6\u6A21\u5F0F\u300D\u4E0E\u300C\u5BF9\u8BDD\u7BA1\u7406\u300D\u91CD\u53E0\u4FEE\u590D ----
3871
3895
  \u5B9E\u6D4B\uFF08390px\uFF09\uFF1A\u6807\u51C6\u6A21\u5F0F(\u5728 titleCluster \u5185) \u53F3\u8FB9\u754C 102\uFF0C
3872
3896
  \u5BF9\u8BDD\u7BA1\u7406(\u5728 headerUtilities \u5185) \u5DE6\u8FB9\u754C 91 \u2192 \u6C34\u5E73\u91CD\u53E0 11px\u3002
3873
- \u6839\u56E0\uFF1AtitleCluster \u53EA\u6709 46px \u5BBD\uFF08[33,79]\uFF09\uFF0C\u4F46\u5185\u90E8\u7684\u300C\u6807\u51C6\u6A21\u5F0F\u300D\u80F6\u56CA\u56FA\u5B9A 69px\uFF0C
3874
- \u6491\u7834\u7236\u5BB9\u5668\u540E\u4E0E\u53F3\u4FA7 headerUtilities \u76F8\u649E\u3002
3875
- \u4FEE\u590D\uFF1A\u5141\u8BB8 titleRow \u6362\u884C + \u8BA9\u4E24\u4FA7\u5404\u81EA\u53EF\u6536\u7F29\u5E76\u9650\u5BBD\u3002 */
3897
+ \u4FEE\u590D\uFF1AtitleRow \u5F3A\u5236\u5355\u884C\uFF08tabs \u5DF2\u5E76\u5165\u540C\u4E00\u884C\uFF09\uFF0C\u8D85\u5BBD\u65F6 titleCluster
3898
+ \u5185\u90E8\u622A\u65AD\u800C\u4E0D\u662F\u6362\u884C\u2014\u2014\u6362\u884C\u4F1A\u8BA9 header \u53D8\u4E24\u884C\u3001\u6324\u538B\u4E2D\u95F4\u7A7A\u95F4\u3002 */
3876
3899
  div[class*="wSkVaW_titleRow"],
3877
3900
  div[class*="_titleRow"] {
3878
- flex-wrap: wrap !important;
3879
- row-gap: 6px !important;
3901
+ display: flex !important;
3902
+ flex-direction: row !important;
3903
+ flex-wrap: nowrap !important;
3904
+ align-items: center !important;
3905
+ flex: 1 1 auto !important;
3906
+ min-width: 0 !important;
3880
3907
  column-gap: 8px !important;
3881
- overflow: visible !important;
3908
+ overflow: hidden !important;
3882
3909
  }
3883
3910
  div[class*="wSkVaW_titleCluster"],
3884
3911
  div[class*="_titleCluster"] {
3885
- flex: 1 1 auto !important;
3912
+ flex: 0 1 auto !important;
3886
3913
  min-width: 0 !important;
3887
3914
  max-width: 100% !important;
3915
+ overflow: hidden !important;
3916
+ }
3917
+ /* \u539F\u751F corner \u6309\u94AE\u5E26 margin-right:-16px \u60AC\u6302\u8BBE\u8BA1\uFF0C\u5728\u5355\u884C + overflow:hidden \u4E0B
3918
+ \u4F1A\u88AB\u88C1\u6389 16px\u2014\u2014\u5F52\u96F6\u540E\u6574\u884C\u521A\u597D\u653E\u4E0B\uFF1Acluster69 + utilities123 + corner28 */
3919
+ div[class*="wSkVaW_headerCorner"] {
3920
+ margin-right: 0 !important;
3921
+ margin-left: 0 !important;
3922
+ flex: 0 0 auto !important;
3888
3923
  }
3889
3924
  div[class*="wSkVaW_headerActions"],
3890
3925
  div[class*="_headerActions"] {
@@ -4055,13 +4090,9 @@ function injectMobileStyles() {
4055
4090
  box-sizing: border-box !important;
4056
4091
  }
4057
4092
 
4058
- /* ---- \u9876\u90E8\u63A7\u4EF6\u9632\u6EA2\u51FA\u88C1\u5207\uFF08320px \u4E0B main \u5206\u652F chip \u6EA2\u51FA 39px \u88AB\u88C1\uFF09 ---- */
4059
- div[class*="wSkVaW_titleRow"],
4060
- div[class*="_titleRow"] {
4061
- flex-wrap: wrap !important;
4062
- row-gap: 6px !important;
4063
- overflow: visible !important;
4064
- }
4093
+ /* ---- \u9876\u90E8\u63A7\u4EF6\u9632\u6EA2\u51FA\u88C1\u5207\uFF08320px \u4E0B main \u5206\u652F chip \u6EA2\u51FA 39px \u88AB\u88C1\uFF09 ----
4094
+ \u6CE8\u610F\uFF1AtitleRow \u7684 wrap \u89C4\u5219\u5DF2\u5220\u9664\u2014\u2014tabs \u6298\u53E0\u8FDB\u6807\u9898\u884C\u540E\u5FC5\u987B\u5355\u884C\uFF0C
4095
+ \u8FD9\u91CC\u7684 wrap \u4F1A\u5728\u7EA7\u8054\u4E2D\u8D62\u8FC7\u524D\u9762\u7684 nowrap\uFF0C\u5BFC\u81F4\u6807\u9898\u884C\u91CD\u65B0\u6298\u6210\u4E24\u884C\u3002 */
4065
4096
  button[class*="_7rgC5q_chip"],
4066
4097
  button[class*="cubgiG_seat"],
4067
4098
  div[class*="_7rgC5q_chipWrap"] {
@@ -4211,6 +4242,15 @@ function injectMobileStyles() {
4211
4242
  justify-content: center !important;
4212
4243
  gap: 0 !important;
4213
4244
  font-size: 0 !important;
4245
+ /* \u60AC\u505C\u9501\u5B9A\uFF1A\u5BBD\u5EA6\u4E0E\u4F4D\u79FB\u90FD\u4E0D\u53D8\uFF0C\u675C\u7EDD\u5DE5\u5177\u680F/\u5BF9\u8BDD\u6846\u6296\u52A8 */
4246
+ transition: none !important;
4247
+ border-radius: 8px !important;
4248
+ }
4249
+ div[class*="uV2eYG_trailing"] span > button:not([class]):hover {
4250
+ width: 26px !important;
4251
+ min-width: 26px !important;
4252
+ padding: 0 2px !important;
4253
+ background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.15)) !important;
4214
4254
  }
4215
4255
  div[class*="uV2eYG_trailing"] span > button:not([class]) svg {
4216
4256
  flex: 0 0 auto !important;
@@ -4775,6 +4815,125 @@ function setupMobileExperience(rpcCall, ctx) {
4775
4815
  const panelObserver = new MutationObserver(ensurePanelCloseButton);
4776
4816
  panelObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
4777
4817
  window.addEventListener("resize", ensurePanelCloseButton);
4818
+ const mobileTabState = { btn: null, dropdown: null };
4819
+ const nativeTabs = () => [...document.querySelectorAll('[role="tablist"] [role="tab"]')];
4820
+ const closeTabDropdown = () => {
4821
+ if (mobileTabState.dropdown) {
4822
+ mobileTabState.dropdown.remove();
4823
+ mobileTabState.dropdown = null;
4824
+ }
4825
+ };
4826
+ const renderTabDropdown = () => {
4827
+ closeTabDropdown();
4828
+ if (!mobileTabState.btn) return;
4829
+ const dropdown = document.createElement("div");
4830
+ dropdown.className = "dsh-mobile-tab-dropdown";
4831
+ const tabs = nativeTabs();
4832
+ tabs.forEach((tabEl) => {
4833
+ const isActive = tabEl.getAttribute("aria-selected") === "true";
4834
+ const option = document.createElement("button");
4835
+ option.type = "button";
4836
+ option.className = "dsh-mobile-tab-option";
4837
+ option.setAttribute("data-active", isActive ? "true" : "false");
4838
+ const name2 = document.createElement("span");
4839
+ name2.textContent = tabEl.textContent.replace(/\s+/g, " ").trim();
4840
+ option.appendChild(name2);
4841
+ if (isActive) {
4842
+ const check = document.createElementNS("http://www.w3.org/2000/svg", "svg");
4843
+ check.setAttribute("width", "14");
4844
+ check.setAttribute("height", "14");
4845
+ check.setAttribute("viewBox", "0 0 24 24");
4846
+ check.setAttribute("fill", "none");
4847
+ check.setAttribute("stroke", "currentColor");
4848
+ check.setAttribute("stroke-width", "2.5");
4849
+ check.setAttribute("stroke-linecap", "round");
4850
+ check.setAttribute("stroke-linejoin", "round");
4851
+ check.innerHTML = '<polyline points="20 6 9 17 4 12"></polyline>';
4852
+ option.appendChild(check);
4853
+ }
4854
+ option.onclick = (e) => {
4855
+ e.stopPropagation();
4856
+ tabEl.click();
4857
+ closeTabDropdown();
4858
+ syncTabButtonLabel();
4859
+ };
4860
+ dropdown.appendChild(option);
4861
+ });
4862
+ mobileTabState.btn.appendChild(dropdown);
4863
+ mobileTabState.dropdown = dropdown;
4864
+ };
4865
+ const syncTabButtonLabel = () => {
4866
+ if (!mobileTabState.btn || !mobileTabState.btn.isConnected) return;
4867
+ const active = nativeTabs().find((t) => t.getAttribute("aria-selected") === "true");
4868
+ const labelEl = mobileTabState.btn.querySelector(".dsh-mobile-tab-btn-label");
4869
+ if (!labelEl || !active) return;
4870
+ const name2 = active.textContent.replace(/\s+/g, " ").trim();
4871
+ if (labelEl.textContent !== name2) labelEl.textContent = name2;
4872
+ };
4873
+ const ensureTabButton = () => {
4874
+ if (typeof window === "undefined" || window.innerWidth > 768) return;
4875
+ const titleRow = document.querySelector('div[class*="wSkVaW_titleRow"]');
4876
+ const cluster = titleRow ? titleRow.querySelector('div[class*="wSkVaW_titleCluster"]') : null;
4877
+ if (!titleRow || !cluster) {
4878
+ closeTabDropdown();
4879
+ if (mobileTabState.btn) {
4880
+ mobileTabState.btn.remove();
4881
+ mobileTabState.btn = null;
4882
+ }
4883
+ return;
4884
+ }
4885
+ if (mobileTabState.btn && mobileTabState.btn.isConnected && titleRow.contains(mobileTabState.btn)) {
4886
+ syncTabButtonLabel();
4887
+ return;
4888
+ }
4889
+ document.querySelectorAll(".dsh-mobile-tab-btn").forEach((el) => el.remove());
4890
+ const tabBtn = document.createElement("button");
4891
+ tabBtn.type = "button";
4892
+ const pillBtn = titleRow.querySelector('div[class*="wSkVaW_titleCluster"] button');
4893
+ const utilBtn = titleRow.querySelector('div[class*="wSkVaW_headerUtilities"] button');
4894
+ const mimic = pillBtn && pillBtn.className || utilBtn && utilBtn.className || "";
4895
+ tabBtn.className = (mimic ? mimic + " " : "") + "dsh-mobile-tab-btn";
4896
+ tabBtn.innerHTML = `<span class="dsh-mobile-tab-btn-label">\u5BF9\u8BDD</span><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>`;
4897
+ tabBtn.onclick = (e) => {
4898
+ e.stopPropagation();
4899
+ if (mobileTabState.dropdown) closeTabDropdown();
4900
+ else renderTabDropdown();
4901
+ };
4902
+ titleRow.insertBefore(tabBtn, cluster.nextSibling);
4903
+ mobileTabState.btn = tabBtn;
4904
+ syncTabButtonLabel();
4905
+ };
4906
+ const cleanReasoningButtons = () => {
4907
+ if (typeof window === "undefined" || window.innerWidth > 768) return;
4908
+ document.querySelectorAll('div[class*="uV2eYG_trailing"] span > button:not([class])').forEach((btn) => {
4909
+ if (btn.dataset.dshTextCleaned === "1") return;
4910
+ [...btn.childNodes].forEach((node) => {
4911
+ if (node.nodeType === Node.ELEMENT_NODE) {
4912
+ if (node.tagName.toLowerCase() === "svg" || typeof node.querySelector === "function" && node.querySelector("svg")) return;
4913
+ }
4914
+ node.remove();
4915
+ });
4916
+ btn.dataset.dshTextCleaned = "1";
4917
+ });
4918
+ };
4919
+ const titleExtrasObserver = new MutationObserver(() => {
4920
+ ensureTabButton();
4921
+ cleanReasoningButtons();
4922
+ });
4923
+ titleExtrasObserver.observe(document.body, { childList: true, subtree: true });
4924
+ window.addEventListener("resize", () => {
4925
+ if (window.innerWidth > 768) closeTabDropdown();
4926
+ });
4927
+ document.addEventListener(
4928
+ "click",
4929
+ (e) => {
4930
+ if (!mobileTabState.dropdown) return;
4931
+ if (!mobileTabState.dropdown.contains(e.target) && !e.target.closest(".dsh-mobile-tab-btn")) closeTabDropdown();
4932
+ },
4933
+ true
4934
+ );
4935
+ ensureTabButton();
4936
+ cleanReasoningButtons();
4778
4937
  document.addEventListener("click", (e) => {
4779
4938
  if (typeof window === "undefined" || window.innerWidth > 768) return;
4780
4939
  const trigger = e.target.closest('button[aria-label*="\u9762\u677F"], button[aria-label*="\u5DE5\u4F5C\u533A"], div[class*="toggleCluster"] button, button[class*="subagent"], div[class*="headerActions"] button, div[class*="titleRow"] button');
package/client/index.js CHANGED
@@ -3426,62 +3426,97 @@ function injectMobileStyles() {
3426
3426
  display: none !important;
3427
3427
  }
3428
3428
 
3429
- /* ---- 对话/轨迹/上下文 标签行折叠进标题行 ----
3430
- 原本 tabs 独占一行(25px + 间距),移动端中间空间太小。
3431
- 改为绝对定位的悬浮胶囊:钉在标题行下缘右侧,不再占据布局高度,
3432
- 中间内容区净增约 33px;背景用原生 tooltip 灰与内容分层。 */
3433
- header[class*="wSkVaW_header"] {
3434
- position: relative !important;
3435
- }
3429
+ /* ---- 对话/轨迹/上下文 标签折叠进标题行(同一行) ----
3430
+ 原生 tablist 整行隐藏;由 setupMobileExperience 注入一个原生样式的
3431
+ 「对话」按钮(插在标准模式与访达图标之间),点击弹下拉切换三个视图。
3432
+ 按钮文案与选项文案都从原生 tab 动态读取,语言无关。 */
3436
3433
  div[class*="wSkVaW_tabs"] {
3437
- position: absolute !important;
3438
- top: 38px !important;
3439
- right: 6px !important;
3440
- left: auto !important;
3441
- width: auto !important;
3442
- max-width: 62vw !important;
3443
- height: 30px !important;
3444
- display: inline-flex !important;
3445
- align-items: center !important;
3446
- gap: 2px !important;
3447
- padding: 2px !important;
3448
- border-radius: 999px !important;
3449
- background: var(--dsw-alias-tooltip-bg, #43454a) !important;
3450
- box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3) !important;
3451
- overflow-x: auto !important;
3452
- scrollbar-width: none !important;
3453
- z-index: 30 !important;
3454
- }
3455
- div[class*="wSkVaW_tabs"]::-webkit-scrollbar {
3456
3434
  display: none !important;
3457
3435
  }
3458
- div[class*="wSkVaW_tabs"] [role="tab"] {
3459
- height: 24px !important;
3436
+ .dsh-mobile-tab-btn {
3437
+ display: inline-flex !important;
3438
+ align-items: center !important;
3439
+ gap: 5px !important;
3440
+ position: relative !important;
3441
+ height: 28px !important;
3442
+ min-height: 28px !important;
3460
3443
  padding: 0 10px !important;
3461
3444
  border-radius: 999px !important;
3462
- font-size: 12px !important;
3445
+ background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, 0.12)) !important;
3446
+ color: var(--dsw-alias-label-primary, inherit) !important;
3447
+ font-size: 12.5px !important;
3448
+ line-height: 28px !important;
3463
3449
  white-space: nowrap !important;
3450
+ cursor: pointer !important;
3451
+ }
3452
+ .dsh-mobile-tab-btn svg {
3464
3453
  flex: 0 0 auto !important;
3465
3454
  }
3455
+ .dsh-mobile-tab-dropdown {
3456
+ position: absolute !important;
3457
+ top: calc(100% + 6px) !important;
3458
+ left: 0 !important;
3459
+ min-width: 132px !important;
3460
+ z-index: 60 !important;
3461
+ padding: 4px !important;
3462
+ border-radius: 12px !important;
3463
+ background: var(--dsw-alias-bg-layer-2, #2c2c2e) !important;
3464
+ border: 1px solid var(--dsw-alias-border-l2, transparent) !important;
3465
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35) !important;
3466
+ }
3467
+ .dsh-mobile-tab-dropdown .dsh-mobile-tab-option {
3468
+ display: flex !important;
3469
+ align-items: center !important;
3470
+ justify-content: space-between !important;
3471
+ gap: 8px !important;
3472
+ width: 100% !important;
3473
+ padding: 7px 10px !important;
3474
+ border: 0 !important;
3475
+ border-radius: 8px !important;
3476
+ background: transparent !important;
3477
+ color: var(--dsw-alias-label-primary, inherit) !important;
3478
+ font-size: 13px !important;
3479
+ line-height: 18px !important;
3480
+ cursor: pointer !important;
3481
+ text-align: left !important;
3482
+ }
3483
+ .dsh-mobile-tab-dropdown .dsh-mobile-tab-option:hover {
3484
+ background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.15)) !important;
3485
+ }
3486
+ .dsh-mobile-tab-dropdown .dsh-mobile-tab-option[data-active='true'] {
3487
+ color: var(--dsw-alias-brand-text, var(--dsw-alias-label-primary, inherit)) !important;
3488
+ font-weight: 600 !important;
3489
+ }
3466
3490
 
3467
3491
  /* ---- 顶栏「标准模式」与「对话管理」重叠修复 ----
3468
3492
  实测(390px):标准模式(在 titleCluster 内) 右边界 102,
3469
3493
  对话管理(在 headerUtilities 内) 左边界 91 → 水平重叠 11px。
3470
- 根因:titleCluster 只有 46px 宽([33,79]),但内部的「标准模式」胶囊固定 69px,
3471
- 撑破父容器后与右侧 headerUtilities 相撞。
3472
- 修复:允许 titleRow 换行 + 让两侧各自可收缩并限宽。 */
3494
+ 修复:titleRow 强制单行(tabs 已并入同一行),超宽时 titleCluster
3495
+ 内部截断而不是换行——换行会让 header 变两行、挤压中间空间。 */
3473
3496
  div[class*="wSkVaW_titleRow"],
3474
3497
  div[class*="_titleRow"] {
3475
- flex-wrap: wrap !important;
3476
- row-gap: 6px !important;
3498
+ display: flex !important;
3499
+ flex-direction: row !important;
3500
+ flex-wrap: nowrap !important;
3501
+ align-items: center !important;
3502
+ flex: 1 1 auto !important;
3503
+ min-width: 0 !important;
3477
3504
  column-gap: 8px !important;
3478
- overflow: visible !important;
3505
+ overflow: hidden !important;
3479
3506
  }
3480
3507
  div[class*="wSkVaW_titleCluster"],
3481
3508
  div[class*="_titleCluster"] {
3482
- flex: 1 1 auto !important;
3509
+ flex: 0 1 auto !important;
3483
3510
  min-width: 0 !important;
3484
3511
  max-width: 100% !important;
3512
+ overflow: hidden !important;
3513
+ }
3514
+ /* 原生 corner 按钮带 margin-right:-16px 悬挂设计,在单行 + overflow:hidden 下
3515
+ 会被裁掉 16px——归零后整行刚好放下:cluster69 + utilities123 + corner28 */
3516
+ div[class*="wSkVaW_headerCorner"] {
3517
+ margin-right: 0 !important;
3518
+ margin-left: 0 !important;
3519
+ flex: 0 0 auto !important;
3485
3520
  }
3486
3521
  div[class*="wSkVaW_headerActions"],
3487
3522
  div[class*="_headerActions"] {
@@ -3652,13 +3687,9 @@ function injectMobileStyles() {
3652
3687
  box-sizing: border-box !important;
3653
3688
  }
3654
3689
 
3655
- /* ---- 顶部控件防溢出裁切(320px 下 main 分支 chip 溢出 39px 被裁) ---- */
3656
- div[class*="wSkVaW_titleRow"],
3657
- div[class*="_titleRow"] {
3658
- flex-wrap: wrap !important;
3659
- row-gap: 6px !important;
3660
- overflow: visible !important;
3661
- }
3690
+ /* ---- 顶部控件防溢出裁切(320px 下 main 分支 chip 溢出 39px 被裁) ----
3691
+ 注意:titleRow 的 wrap 规则已删除——tabs 折叠进标题行后必须单行,
3692
+ 这里的 wrap 会在级联中赢过前面的 nowrap,导致标题行重新折成两行。 */
3662
3693
  button[class*="_7rgC5q_chip"],
3663
3694
  button[class*="cubgiG_seat"],
3664
3695
  div[class*="_7rgC5q_chipWrap"] {
@@ -3808,6 +3839,15 @@ function injectMobileStyles() {
3808
3839
  justify-content: center !important;
3809
3840
  gap: 0 !important;
3810
3841
  font-size: 0 !important;
3842
+ /* 悬停锁定:宽度与位移都不变,杜绝工具栏/对话框抖动 */
3843
+ transition: none !important;
3844
+ border-radius: 8px !important;
3845
+ }
3846
+ div[class*="uV2eYG_trailing"] span > button:not([class]):hover {
3847
+ width: 26px !important;
3848
+ min-width: 26px !important;
3849
+ padding: 0 2px !important;
3850
+ background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.15)) !important;
3811
3851
  }
3812
3852
  div[class*="uV2eYG_trailing"] span > button:not([class]) svg {
3813
3853
  flex: 0 0 auto !important;
@@ -4390,6 +4430,141 @@ function setupMobileExperience(rpcCall, ctx) {
4390
4430
  panelObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] });
4391
4431
  window.addEventListener('resize', ensurePanelCloseButton);
4392
4432
 
4433
+ // 1.5 移动端标题行「对话」下拉:隐藏原生 tablist,在标准模式右边注入一个
4434
+ // 原生样式的「对话 ▾」按钮,点开下拉选择 对话/轨迹/上下文 切换视图。
4435
+ // 文案从原生 tab 动态读取(语言无关);按钮类名复刻原生标题行按钮。
4436
+ const mobileTabState = { btn: null, dropdown: null };
4437
+
4438
+ const nativeTabs = () => [...document.querySelectorAll('[role="tablist"] [role="tab"]')];
4439
+
4440
+ const closeTabDropdown = () => {
4441
+ if (mobileTabState.dropdown) {
4442
+ mobileTabState.dropdown.remove();
4443
+ mobileTabState.dropdown = null;
4444
+ }
4445
+ };
4446
+
4447
+ const renderTabDropdown = () => {
4448
+ closeTabDropdown();
4449
+ if (!mobileTabState.btn) return;
4450
+ const dropdown = document.createElement('div');
4451
+ dropdown.className = 'dsh-mobile-tab-dropdown';
4452
+ const tabs = nativeTabs();
4453
+ tabs.forEach((tabEl) => {
4454
+ const isActive = tabEl.getAttribute('aria-selected') === 'true';
4455
+ const option = document.createElement('button');
4456
+ option.type = 'button';
4457
+ option.className = 'dsh-mobile-tab-option';
4458
+ option.setAttribute('data-active', isActive ? 'true' : 'false');
4459
+ const name = document.createElement('span');
4460
+ name.textContent = tabEl.textContent.replace(/\s+/g, ' ').trim();
4461
+ option.appendChild(name);
4462
+ if (isActive) {
4463
+ const check = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
4464
+ check.setAttribute('width', '14');
4465
+ check.setAttribute('height', '14');
4466
+ check.setAttribute('viewBox', '0 0 24 24');
4467
+ check.setAttribute('fill', 'none');
4468
+ check.setAttribute('stroke', 'currentColor');
4469
+ check.setAttribute('stroke-width', '2.5');
4470
+ check.setAttribute('stroke-linecap', 'round');
4471
+ check.setAttribute('stroke-linejoin', 'round');
4472
+ check.innerHTML = '<polyline points="20 6 9 17 4 12"></polyline>';
4473
+ option.appendChild(check);
4474
+ }
4475
+ option.onclick = (e) => {
4476
+ e.stopPropagation();
4477
+ tabEl.click();
4478
+ closeTabDropdown();
4479
+ syncTabButtonLabel();
4480
+ };
4481
+ dropdown.appendChild(option);
4482
+ });
4483
+ mobileTabState.btn.appendChild(dropdown);
4484
+ mobileTabState.dropdown = dropdown;
4485
+ };
4486
+
4487
+ const syncTabButtonLabel = () => {
4488
+ if (!mobileTabState.btn || !mobileTabState.btn.isConnected) return;
4489
+ const active = nativeTabs().find((t) => t.getAttribute('aria-selected') === 'true');
4490
+ const labelEl = mobileTabState.btn.querySelector('.dsh-mobile-tab-btn-label');
4491
+ if (!labelEl || !active) return;
4492
+ // 只有文案真的变化才写入:textContent 无条件赋值会移除并重建文本节点,
4493
+ // 触发 MutationObserver → 无限循环,页面卡死。
4494
+ const name = active.textContent.replace(/\s+/g, ' ').trim();
4495
+ if (labelEl.textContent !== name) labelEl.textContent = name;
4496
+ };
4497
+
4498
+ const ensureTabButton = () => {
4499
+ if (typeof window === 'undefined' || window.innerWidth > 768) return;
4500
+ const titleRow = document.querySelector('div[class*="wSkVaW_titleRow"]');
4501
+ const cluster = titleRow ? titleRow.querySelector('div[class*="wSkVaW_titleCluster"]') : null;
4502
+ if (!titleRow || !cluster) {
4503
+ closeTabDropdown();
4504
+ if (mobileTabState.btn) {
4505
+ mobileTabState.btn.remove();
4506
+ mobileTabState.btn = null;
4507
+ }
4508
+ return;
4509
+ }
4510
+ if (mobileTabState.btn && mobileTabState.btn.isConnected && titleRow.contains(mobileTabState.btn)) {
4511
+ syncTabButtonLabel();
4512
+ return;
4513
+ }
4514
+ document.querySelectorAll('.dsh-mobile-tab-btn').forEach((el) => el.remove());
4515
+ const tabBtn = document.createElement('button');
4516
+ tabBtn.type = 'button';
4517
+ // 观感对齐:优先复刻标准模式胶囊的类,其次访达图标按钮,最后自有样式兜底
4518
+ const pillBtn = titleRow.querySelector('div[class*="wSkVaW_titleCluster"] button');
4519
+ const utilBtn = titleRow.querySelector('div[class*="wSkVaW_headerUtilities"] button');
4520
+ const mimic = (pillBtn && pillBtn.className) || (utilBtn && utilBtn.className) || '';
4521
+ tabBtn.className = (mimic ? mimic + ' ' : '') + 'dsh-mobile-tab-btn';
4522
+ tabBtn.innerHTML = `<span class="dsh-mobile-tab-btn-label">对话</span><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>`;
4523
+ tabBtn.onclick = (e) => {
4524
+ e.stopPropagation();
4525
+ if (mobileTabState.dropdown) closeTabDropdown();
4526
+ else renderTabDropdown();
4527
+ };
4528
+ titleRow.insertBefore(tabBtn, cluster.nextSibling);
4529
+ mobileTabState.btn = tabBtn;
4530
+ syncTabButtonLabel();
4531
+ };
4532
+
4533
+ // workbuddy-connect 的 Reasoning 按钮:移动端删掉文字节点只留图标(svg 保留),
4534
+ // 插件重渲染后新按钮由观察器重新清理
4535
+ const cleanReasoningButtons = () => {
4536
+ if (typeof window === 'undefined' || window.innerWidth > 768) return;
4537
+ document.querySelectorAll('div[class*="uV2eYG_trailing"] span > button:not([class])').forEach((btn) => {
4538
+ if (btn.dataset.dshTextCleaned === '1') return;
4539
+ [...btn.childNodes].forEach((node) => {
4540
+ if (node.nodeType === Node.ELEMENT_NODE) {
4541
+ if (node.tagName.toLowerCase() === 'svg' || (typeof node.querySelector === 'function' && node.querySelector('svg'))) return;
4542
+ }
4543
+ node.remove();
4544
+ });
4545
+ btn.dataset.dshTextCleaned = '1';
4546
+ });
4547
+ };
4548
+
4549
+ const titleExtrasObserver = new MutationObserver(() => {
4550
+ ensureTabButton();
4551
+ cleanReasoningButtons();
4552
+ });
4553
+ titleExtrasObserver.observe(document.body, { childList: true, subtree: true });
4554
+ window.addEventListener('resize', () => {
4555
+ if (window.innerWidth > 768) closeTabDropdown();
4556
+ });
4557
+ document.addEventListener(
4558
+ 'click',
4559
+ (e) => {
4560
+ if (!mobileTabState.dropdown) return;
4561
+ if (!mobileTabState.dropdown.contains(e.target) && !e.target.closest('.dsh-mobile-tab-btn')) closeTabDropdown();
4562
+ },
4563
+ true,
4564
+ );
4565
+ ensureTabButton();
4566
+ cleanReasoningButtons();
4567
+
4393
4568
  // 移动端点击面板/工作区触发按钮时,自动激活 dsh-workbench-open
4394
4569
  document.addEventListener('click', (e) => {
4395
4570
  if (typeof window === 'undefined' || window.innerWidth > 768) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-bridge-gateway",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "以 dsh-bridge 为根基、融合 remote-gateway 能力的 DSH 远程访问网关:局域网 / Cloudflare 隧道 / 自建隧道之外,新增「公网直连网关」(0.0.0.0 监听 + 自带 HTTPS 自签证书 + 强制登录门禁),解决移动端外网直连操控电脑。",
5
5
  "releaseNotes": "【dsh-bridge-gateway 0.1.5】\n• 🔧 适配 DSH 0.1.5:RPC 通道迁移到 webServer 直注册 + requestRejection 鉴权\n• 📱 移动端深度适配:顶栏防重叠、分支 chip 不再裁切、输入框宽度对齐并 sticky 固定、44px 点击热区、侧边栏焦点陷阱消除\n• 默认不启用,安装后原有局域网/隧道/IM 行为完全不变",
6
6
  "type": "module",