dsh-mobile 0.3.10 → 0.3.12

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
@@ -3,6 +3,16 @@
3
3
  Notable changes to DSH Mobile are recorded here. GitHub Releases remain the source for downloadable packages and complete generated commit notes.
4
4
 
5
5
 
6
+ ## 0.3.12 - 2026-09-07
7
+
8
+ - Document remote notification limits: browser permission is per origin, OS toasts stay on the computer, and server-side channels (e.g. dsh-messager webhooks) are the reliable push path to the phone (thanks @idoall for reporting #46). No code change since 0.3.11.
9
+
10
+ ## 0.3.11 - 2026-09-07
11
+
12
+ - Sync the browser tab title with the current session on the dedicated layout, matching the stock behavior (thanks @idoall for reporting #45).
13
+ - Allow installed plugins' WebSocket endpoints through the gateway via admin-approved exact paths (e.g. `/sidebar/ws/terminal`), managed in the remote panel behind local-admin trust; built-in DSH paths keep working and everything else stays blocked (thanks @idoall for reporting #47).
14
+ - Document remote notification limits: browser permission is per origin, OS toasts stay on the computer, and server-side channels (e.g. dsh-messager webhooks) are the reliable push path to the phone (thanks @idoall for reporting #46).
15
+
6
16
  ## 0.3.10 - 2026-09-07
7
17
 
8
18
  - Keep the workspace sidebar open on desktop-sized viewports (≥900px): the dedicated layout docks it as a persistent panel that survives reconnects and no longer auto-closes after selecting a session, and the dimming scrim only covers the narrow overlay drawer and details panel. Narrow screens keep the overlay drawer behavior unchanged (thanks @idoall for reporting #42).
package/README.en.md CHANGED
@@ -19,14 +19,14 @@
19
19
 
20
20
  > DSH Mobile is a DeepSeek Harness community plugin; the native app supports Android only.
21
21
  >
22
- > **0.3.10 update**: thanks to @idoall for reporting — on wide viewports (≥900px) the session sidebar now stays open until explicitly collapsed; plugin-market storefront screenshots are declared. [Details](CHANGELOG.md).
22
+ > **0.3.12 update**: remote notification limits documented (#46, docs only, no code change). [Details](CHANGELOG.md).
23
23
  >
24
- > **Upgrade reminder**: Windows DSH Desktop users should update to plugin 0.3.10. Existing 0.3.3-0.3.9 apps and paired devices remain compatible without re-pairing. [Compatibility notes](#compatibility).
24
+ > **Upgrade reminder**: Windows DSH Desktop users should update to plugin 0.3.12. Existing 0.3.3-0.3.11 apps and paired devices remain compatible without re-pairing. [Compatibility notes](#compatibility).
25
25
 
26
26
  <p align="center">
27
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.3.10/dsh-mobile-android-v0.3.10.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile Android app icon" width="72" height="72"></a><br>
28
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.3.10/dsh-mobile-android-v0.3.10.apk"><strong>Download Android app 0.3.10</strong></a><br>
29
- <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.3.10">Release notes and checksums</a></sub>
27
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.3.12/dsh-mobile-android-v0.3.12.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile Android app icon" width="72" height="72"></a><br>
28
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.3.12/dsh-mobile-android-v0.3.12.apk"><strong>Download Android app 0.3.12</strong></a><br>
29
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.3.12">Release notes and checksums</a></sub>
30
30
  </p>
31
31
 
32
32
  DSH Mobile is a DeepSeek Harness plugin that lets a mobile browser or the Android app connect over a protected LAN or an optional Tailscale Funnel, cpolar, or self-hosted FRP remote path. Local and remote access keep the same sessions, Workspaces, messages, and tools while using separate switches and paired-device stores without modifying DeepSeek Harness source.
@@ -118,6 +118,8 @@ Remote providers may impose bandwidth and connection limits: the [cpolar Free pl
118
118
  3. In the Android app, open **Remote access** and scan the QR code to create its separate pairing.
119
119
  4. The app keeps device trust and reconnects automatically. Disable remote access when it is not needed; LAN access remains unchanged.
120
120
 
121
+ > **Remote notifications**: browser notification permission is granted per origin, so allow it separately for the remote address; OS-level toasts only appear on the computer, never on the phone. To push task completion to the phone, use a third-party channel (e.g. dsh-messager's Feishu/WeCom/Telegram push), which is delivered server-side and unaffected by the gateway.
122
+
121
123
  Tailscale Funnel has broad reach but may be unreliable from mainland China. Its runtime ties the public listener to the parent process and a bounded control channel; parent exit, channel closure, or an explicit stop ends the current generation and cleans up its resources. cpolar is better suited to mainland networks, while self-hosted FRP fits users who already have a VPS and want to avoid public-provider bandwidth quotas. An unregistered domain on a mainland-China VPS may be intercepted by the cloud provider; public IPv4 mode avoids that dependency. The plugin validates pinned on-demand components, stores their configuration and programs entirely under `$DSH_HOME/mobile-access/`, and can remove them completely from the panel.
122
124
 
123
125
  Self-hosted FRP generates only one HTTP vhost to the DSH loopback gateway. It exposes no arbitrary FRP configuration, TCP/UDP proxy, or FRP plugin. The VPS plaintext vhost must bind to `127.0.0.1`, with Caddy providing public HTTPS; the plugin rejects a publicly reachable plaintext port and reports readiness only after public discovery identifies the current computer.
@@ -198,14 +200,14 @@ The table below lists, for each plugin version, the DeepSeek Harness version it
198
200
 
199
201
  | DSH Mobile plugin | Verified DeepSeek Harness version |
200
202
  | --- | --- |
201
- | `0.3.10` | `0.1.3-alpha.1` |
203
+ | `0.3.10`-`0.3.12` | `0.1.3-alpha.1` |
202
204
  | `0.3.9` | `0.1.3-alpha.1` |
203
205
  | `0.3.6`-`0.3.8` | `0.1.2-rc.1` |
204
206
  | `0.3.4`, `0.3.5` | `0.1.2-alpha.2` |
205
207
  | `0.3.0`-`0.3.3` | `0.1.2-alpha.1` |
206
208
  | `0.1.4`, `0.2.x` | `0.1.1-rc.2` |
207
209
 
208
- Existing 0.3.3-0.3.9 apps do not need re-pairing. Earlier apps use a different status-bar strategy, so updating both is recommended. App 0.1.3 or earlier requires reinstalling and pairing again.
210
+ Existing 0.3.3-0.3.11 apps do not need re-pairing. Earlier apps use a different status-bar strategy, so updating both is recommended. App 0.1.3 or earlier requires reinstalling and pairing again.
209
211
 
210
212
  ## Uninstall
211
213
 
package/README.md CHANGED
@@ -26,14 +26,14 @@
26
26
 
27
27
  > DSH Mobile 是 DeepSeek Harness 社区插件,原生 App 仅支持 Android。
28
28
  >
29
- > **0.3.10 更新**:感谢 @idoall 反馈,大屏(≥900px)会话侧边栏不再自动收起,常驻显示直到手动关闭;同时声明插件市场展示截图。[详细记录](CHANGELOG.md)。
29
+ > **0.3.12 更新**:补充远程通知限制说明(#46,纯文档,无代码改动)。[详细记录](CHANGELOG.md)。
30
30
  >
31
- > **升级提醒**:Windows DSH Desktop 用户请更新至插件 0.3.10;现有 0.3.3-0.3.9 App 可继续使用,无需重新配对。[兼容说明](#兼容性)。
31
+ > **升级提醒**:Windows DSH Desktop 用户请更新至插件 0.3.12;现有 0.3.3-0.3.11 App 可继续使用,无需重新配对。[兼容说明](#兼容性)。
32
32
 
33
33
  <p align="center">
34
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.3.10/dsh-mobile-android-v0.3.10.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile 安卓应用图标" width="72" height="72"></a><br>
35
- <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.3.10/dsh-mobile-android-v0.3.10.apk"><strong>下载 Android App 0.3.10</strong></a><br>
36
- <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.3.10">版本说明与校验文件</a></sub>
34
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.3.12/dsh-mobile-android-v0.3.12.apk"><img src="assets/brand/app-icon-rounded.svg" alt="DSH Mobile 安卓应用图标" width="72" height="72"></a><br>
35
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.3.12/dsh-mobile-android-v0.3.12.apk"><strong>下载 Android App 0.3.12</strong></a><br>
36
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.3.12">版本说明与校验文件</a></sub>
37
37
  </p>
38
38
 
39
39
  DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App 通过局域网,或可选的 Tailscale Funnel、cpolar、自建 FRP 远程通道连接电脑,继续使用同一份会话、工作区、消息和工具。局域网与远程访问分别启停、分别管理设备,且都不修改 DeepSeek Harness 源码。
@@ -125,6 +125,8 @@ dsh plugin --profile web add dshmarket
125
125
  3. 在 Android App 中进入 **远程访问**,扫描二维码完成独立配对。
126
126
  4. 此后 App 会保存设备信任并自动重连;不使用时可以关闭远程访问,局域网连接不会受影响。
127
127
 
128
+ > **远程通知说明**:浏览器通知权限按地址分别授权,远程地址需在浏览器中单独允许;系统级弹窗只出现在电脑上,手机收不到。任务完成要推送到手机时,请使用第三方通道(如 dsh-messager 的飞书/企业微信/Telegram 推送),它们走服务端下发,不受网关影响。
129
+
128
130
  Tailscale Funnel 覆盖范围广,但在中国大陆网络下可能不稳定。其运行组件把公开监听生命周期绑定到父进程和受限控制通道;父进程退出、控制通道关闭或显式停止时会结束当前代次并清理资源。cpolar 更适合国内网络;自建 FRP 适合已有 VPS、希望避开公共服务带宽限制的用户。中国大陆 VPS 上的未备案域名可能被云厂商拦截,此时可使用公网 IPv4 模式。插件会校验按需下载的固定版本组件,配置与程序均保存在 `$DSH_HOME/mobile-access/`,可随时在面板中彻底清除。
129
131
 
130
132
  自建 FRP 只生成一个指向 DSH 回环网关的 HTTP vhost,不提供任意 FRP 配置、TCP/UDP 代理或 FRP 插件。VPS 的明文 vhost 必须只监听 `127.0.0.1`,由 Caddy 提供公网 HTTPS;插件会拒绝可从公网访问的明文端口,并在公开发现接口确认连接到当前电脑后才显示“已就绪”。
@@ -207,14 +209,14 @@ flowchart LR
207
209
 
208
210
  | DSH Mobile 插件 | 验证支持的 DeepSeek Harness 版本 |
209
211
  | --- | --- |
210
- | `0.3.10` | `0.1.3-alpha.1` |
212
+ | `0.3.10`-`0.3.12` | `0.1.3-alpha.1` |
211
213
  | `0.3.9` | `0.1.3-alpha.1` |
212
214
  | `0.3.6`-`0.3.8` | `0.1.2-rc.1` |
213
215
  | `0.3.4`、`0.3.5` | `0.1.2-alpha.2` |
214
216
  | `0.3.0`-`0.3.3` | `0.1.2-alpha.1` |
215
217
  | `0.1.4`、`0.2.x` | `0.1.1-rc.2` |
216
218
 
217
- 现有 0.3.3-0.3.9 App 无需重新配对;更早的 App 使用不同的状态栏策略,建议同步升级;App 0.1.3 及更早版本需卸载重装并重新配对。
219
+ 现有 0.3.3-0.3.11 App 无需重新配对;更早的 App 使用不同的状态栏策略,建议同步升级;App 0.1.3 及更早版本需卸载重装并重新配对。
218
220
 
219
221
  ## 卸载
220
222
 
package/lib/client.js CHANGED
@@ -151,7 +151,14 @@ window.__ModuleLoader__.load({
151
151
  remoteQrReady: "Remote pairing QR code generated. Scan it from “Remote access” in the app.",
152
152
  resetCpolarConfirm: "Disable the cpolar remote channel and remove all remote paired devices? Your cpolar account and other tunnels are unchanged.",
153
153
  resetTailscaleConfirm: "Sign out of Tailscale on this computer and remove all remote paired devices? Local network configuration is unchanged.",
154
- requestFailed: "Request failed: {error}"
154
+ requestFailed: "Request failed: {error}",
155
+ wsPathsTitle: "Third-party WebSocket paths",
156
+ wsPathsIntro: "Let installed plugins’ WebSocket endpoints (exact paths such as /sidebar/ws/terminal) work through the gateway. Built-in DSH paths always work; everything else stays blocked.",
157
+ wsPathsPlaceholder: "/sidebar/ws/terminal",
158
+ wsPathsAdd: "Allow path",
159
+ wsPathsRemove: "Remove",
160
+ wsPathsEmpty: "No extra paths allowed.",
161
+ wsPathsInvalid: "Enter an exact path starting with / (no query string)."
155
162
  },
156
163
  it: {
157
164
  mobileAccess: "Accesso mobile",
@@ -297,7 +304,14 @@ window.__ModuleLoader__.load({
297
304
  remoteQrReady: "QR di abbinamento remoto generato. Scansionalo da “Accesso remoto” nell’app.",
298
305
  resetCpolarConfirm: "Disattivare il canale remoto cpolar e rimuovere tutti i dispositivi remoti? Account cpolar e altri tunnel restano invariati.",
299
306
  resetTailscaleConfirm: "Uscire da Tailscale su questo computer e rimuovere tutti i dispositivi remoti? La configurazione della rete locale resta invariata.",
300
- requestFailed: "Richiesta non riuscita: {error}"
307
+ requestFailed: "Richiesta non riuscita: {error}",
308
+ wsPathsTitle: "Percorsi WebSocket di terze parti",
309
+ wsPathsIntro: "Consenti agli endpoint WebSocket dei plugin installati (percorsi esatti come /sidebar/ws/terminal) di passare dal gateway. I percorsi DSH integrati funzionano sempre; tutto il resto resta bloccato.",
310
+ wsPathsPlaceholder: "/sidebar/ws/terminal",
311
+ wsPathsAdd: "Consenti percorso",
312
+ wsPathsRemove: "Rimuovi",
313
+ wsPathsEmpty: "Nessun percorso aggiuntivo consentito.",
314
+ wsPathsInvalid: "Inserisci un percorso esatto che inizi con / (senza query)."
301
315
  },
302
316
  zh: {}
303
317
  };
@@ -445,7 +459,14 @@ window.__ModuleLoader__.load({
445
459
  remoteQrReady: "远程配对二维码已生成。请在 App 的“远程访问”中扫描。",
446
460
  resetCpolarConfirm: "关闭 cpolar 远程通道并移除所有远程配对设备?不会修改你的 cpolar 账号或其他隧道。",
447
461
  resetTailscaleConfirm: "退出电脑上的 Tailscale 登录并移除所有远程配对设备?局域网配置不会改变。",
448
- requestFailed: "请求失败:{error}"
462
+ requestFailed: "请求失败:{error}",
463
+ wsPathsTitle: "第三方 WebSocket 路径",
464
+ wsPathsIntro: "允许已装插件的 WebSocket 端点(确切路径,如 /sidebar/ws/terminal)经网关通行。DSH 内置路径始终可用,其余一律拦截。",
465
+ wsPathsPlaceholder: "/sidebar/ws/terminal",
466
+ wsPathsAdd: "允许该路径",
467
+ wsPathsRemove: "移除",
468
+ wsPathsEmpty: "暂无额外允许的路径。",
469
+ wsPathsInvalid: "请输入以 / 开头的确切路径(不带 query)。"
449
470
  });
450
471
  Object.assign(MOBILE_CONTROL_MESSAGES.en, {
451
472
  selfHostedConnections: "Self-hosted connection",
@@ -2527,8 +2548,85 @@ window.__ModuleLoader__.load({
2527
2548
  remoteManageRow.append(remoteDevices, remoteReset);
2528
2549
  const remoteDevicePanel = element("div", "dsh-mobile-control__devices");
2529
2550
  remoteDevicePanel.hidden = true;
2551
+ const wsPathsSection = element("section", "dsh-mobile-control__ws-paths");
2552
+ const wsPathsTitle = element("h3", "dsh-mobile-control__section-title");
2553
+ wsPathsTitle.textContent = t("wsPathsTitle");
2554
+ const wsPathsIntro = element("p", "dsh-mobile-control__intro");
2555
+ wsPathsIntro.textContent = t("wsPathsIntro");
2556
+ const wsPathsList = element("ul", "dsh-mobile-control__ws-paths-list");
2557
+ const wsPathsRow = element("div", "dsh-mobile-control__ws-paths-row");
2558
+ const wsPathsInput = element("input", "dsh-mobile-control__ws-paths-input");
2559
+ wsPathsInput.type = "text";
2560
+ wsPathsInput.placeholder = t("wsPathsPlaceholder");
2561
+ wsPathsInput.setAttribute("aria-label", t("wsPathsTitle"));
2562
+ const wsPathsAdd = element("button", "dsh-mobile-control__secondary");
2563
+ wsPathsAdd.type = "button";
2564
+ wsPathsAdd.textContent = t("wsPathsAdd");
2565
+ const wsPathsStatus = element("p", "dsh-mobile-control__status");
2566
+ wsPathsStatus.hidden = true;
2567
+ wsPathsStatus.setAttribute("aria-live", "polite");
2568
+ wsPathsRow.append(wsPathsInput, wsPathsAdd);
2569
+ wsPathsSection.append(wsPathsTitle, wsPathsIntro, wsPathsList, wsPathsRow, wsPathsStatus);
2570
+ let wsPathsCurrent = [];
2571
+ let wsPathsLoaded = false;
2572
+ const renderWsPaths = (paths) => {
2573
+ wsPathsCurrent = [...paths];
2574
+ wsPathsList.replaceChildren();
2575
+ if (paths.length === 0) {
2576
+ const empty = element("li", "dsh-mobile-control__ws-paths-empty");
2577
+ empty.textContent = t("wsPathsEmpty");
2578
+ wsPathsList.append(empty);
2579
+ }
2580
+ for (const path of paths) {
2581
+ const item = element("li", "dsh-mobile-control__ws-paths-item");
2582
+ const label = element("code");
2583
+ label.textContent = path;
2584
+ const remove = element("button", "dsh-mobile-control__ws-paths-remove");
2585
+ remove.type = "button";
2586
+ remove.textContent = t("wsPathsRemove");
2587
+ remove.setAttribute("aria-label", `${t("wsPathsRemove")}: ${path}`);
2588
+ remove.addEventListener("click", () => {
2589
+ saveWsPaths(wsPathsCurrent.filter((entry) => entry !== path));
2590
+ });
2591
+ item.append(label, remove);
2592
+ wsPathsList.append(item);
2593
+ }
2594
+ };
2595
+ const loadWsPaths = () => {
2596
+ controlRequestJson("/api/mobile-access/remote/websocket-paths").then((data) => {
2597
+ wsPathsLoaded = true;
2598
+ wsPathsStatus.hidden = true;
2599
+ renderWsPaths(Array.isArray(data.paths) ? data.paths.filter((entry) => typeof entry === "string") : []);
2600
+ }, (error) => {
2601
+ wsPathsStatus.hidden = false;
2602
+ wsPathsStatus.textContent = t("requestFailed", { error: String(error) });
2603
+ });
2604
+ };
2605
+ const saveWsPaths = (paths) => {
2606
+ wsPathsStatus.hidden = true;
2607
+ controlRequestJson("/api/mobile-access/remote/websocket-paths", {
2608
+ method: "POST",
2609
+ body: JSON.stringify({ paths })
2610
+ }).then((data) => {
2611
+ wsPathsLoaded = true;
2612
+ renderWsPaths(Array.isArray(data.paths) ? data.paths.filter((entry) => typeof entry === "string") : []);
2613
+ }, (error) => {
2614
+ wsPathsStatus.hidden = false;
2615
+ wsPathsStatus.textContent = t("requestFailed", { error: String(error) });
2616
+ });
2617
+ };
2618
+ wsPathsAdd.addEventListener("click", () => {
2619
+ const value = wsPathsInput.value.trim();
2620
+ if (value === "") {
2621
+ wsPathsStatus.hidden = false;
2622
+ wsPathsStatus.textContent = t("wsPathsInvalid");
2623
+ return;
2624
+ }
2625
+ wsPathsInput.value = "";
2626
+ saveWsPaths([...wsPathsCurrent, value]);
2627
+ });
2530
2628
  const remoteWorkspace = element("section", "dsh-mobile-control__remote-workspace");
2531
- remoteWorkspace.append(providerSetupHeader, remoteStatus, remoteAccess, remoteGuide, providerSetupBody, remoteActions, remoteQr, remoteManageRow, remoteDevicePanel);
2629
+ remoteWorkspace.append(providerSetupHeader, remoteStatus, remoteAccess, remoteGuide, providerSetupBody, remoteActions, remoteQr, remoteManageRow, remoteDevicePanel, wsPathsSection);
2532
2630
  const diagnosticsView = element("div", "dsh-mobile-control__view is-diagnostics");
2533
2631
  diagnosticsView.hidden = true;
2534
2632
  const diagnosticsIntro = element("p", "dsh-mobile-control__intro");
@@ -2669,6 +2767,7 @@ window.__ModuleLoader__.load({
2669
2767
  remoteTab.addEventListener("click", () => {
2670
2768
  selectView("remote");
2671
2769
  loadRemote();
2770
+ if (!wsPathsLoaded) loadWsPaths();
2672
2771
  });
2673
2772
  const setOpen = (open) => {
2674
2773
  panel.hidden = !open;
@@ -5003,7 +5102,7 @@ window.__ModuleLoader__.load({
5003
5102
  .dsh-mobile-control__actions{display:flex;flex-wrap:nowrap;gap:6px}.dsh-mobile-control__actions button{flex:1 1 0;min-width:0;min-height:40px;padding:8px 4px;border-radius:10px;font:12px/1.2 system-ui;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-normal,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
5004
5103
  .dsh-mobile-control button:focus-visible,.dsh-mobile-control a:focus-visible,.dsh-mobile-control input:focus-visible,.dsh-mobile-control summary:focus-visible{outline:3px solid rgb(37 99 235 / 28%);outline-offset:2px}
5005
5104
  .dsh-mobile-control__trigger{box-sizing:border-box;flex:none;display:flex;align-items:center;gap:8px;width:calc(100% + 4px);height:42px;margin:4px -2px;padding:0 10px 0 8px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font-family:inherit;font-size:14px;line-height:22px;cursor:pointer;overflow:hidden}.dsh-mobile-control__trigger:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}.dsh-mobile-control__trigger:active,.dsh-mobile-control__trigger[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-active,#e8ebf0)}.dsh-mobile-control__trigger:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px}.dsh-mobile-control__trigger.is-rail{width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;gap:0;border-radius:50%}.dsh-mobile-control__trigger-icon{display:block;flex:none}.dsh-mobile-control__trigger-label{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
5006
- .dsh-mobile-control__manage-row{display:flex;justify-content:space-between;gap:8px;margin-top:10px}.dsh-mobile-control__manage{flex:1 1 0;min-width:0;min-height:34px;padding:6px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__devices{margin-top:10px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:10px;padding:8px;max-height:220px;overflow-y:auto}.dsh-mobile-control__device-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;margin:0}.dsh-mobile-control__device{display:flex;align-items:center;gap:8px;padding:6px 2px}.dsh-mobile-control__device + .dsh-mobile-control__device{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__device-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.dsh-mobile-control__device-meta{flex:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;white-space:nowrap}.dsh-mobile-control__device-revoke{flex:none;min-height:28px;padding:4px 8px;border:1px solid #dc2626;border-radius:8px;background:transparent;color:#dc2626;font:12px/1.2 system-ui;cursor:pointer}
5105
+ .dsh-mobile-control__manage-row{display:flex;justify-content:space-between;gap:8px;margin-top:10px}.dsh-mobile-control__manage{flex:1 1 0;min-width:0;min-height:34px;padding:6px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__devices{margin-top:10px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:10px;padding:8px;max-height:220px;overflow-y:auto}.dsh-mobile-control__device-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;margin:0}.dsh-mobile-control__device{display:flex;align-items:center;gap:8px;padding:6px 2px}.dsh-mobile-control__device + .dsh-mobile-control__device{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__device-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.dsh-mobile-control__device-meta{flex:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;white-space:nowrap}.dsh-mobile-control__device-revoke{flex:none;min-height:28px;padding:4px 8px;border:1px solid #dc2626;border-radius:8px;background:transparent;color:#dc2626;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__ws-paths{margin:10px 0 0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__ws-paths-list{margin:0 0 8px;padding:0;list-style:none}.dsh-mobile-control__ws-paths-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px}.dsh-mobile-control__ws-paths-item{display:flex;align-items:center;gap:8px;padding:6px 0}.dsh-mobile-control__ws-paths-item + .dsh-mobile-control__ws-paths-item{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__ws-paths-item code{flex:1 1 0;min-width:0;overflow:hidden;font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__ws-paths-remove{flex:none;min-height:28px;padding:4px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:8px;background:transparent;color:inherit;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__ws-paths-row{display:flex;gap:8px}.dsh-mobile-control__ws-paths-row input{flex:1 1 0;min-width:0;box-sizing:border-box;min-height:40px;padding:8px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:14px/1.4 system-ui}.dsh-mobile-control__ws-paths-row button{flex:none}
5007
5106
  @media (max-width:359px){.dsh-mobile-control__provider-choices{grid-template-columns:1fr}.dsh-mobile-control__provider{min-height:68px}}@media (prefers-reduced-motion:reduce){.dsh-mobile-control__provider,.dsh-mobile-control__cpolar-connect{transition:none}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-checks{animation:none}}
5008
5107
  `;
5009
5108
  /** Mount the desktop control or mobile feature enhancements. */