dsh-mobile 0.3.12 → 0.3.13

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,12 @@
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.13 - 2026-09-08
7
+
8
+ - Third-party WebSocket approval is interception-driven and generic: whenever a plugin's WebSocket connection is blocked, the diagnostics view groups the rejected paths by directory with attempt counts and offers per-path or allow-all approval, behind the same local-admin trust as pairing, and both the sidebar entry and the in-panel diagnostics button carry a red badge until reviewed. Manual path entry is tucked under an “advanced” disclosure that most users never need to open (thanks @idoall for reporting #47). Badge changes are announced to screen readers and the disclosure has a visible focus ring.
9
+ - Panel polish: number the FRP VPS deployment group as step 2, restyle its deploy-changes list, align the path input with sibling fields, and auto-expand the VPS group until FRP is configured.
10
+ - Adapted to DeepSeek Harness 0.1.3-alpha.2: the front-end layout contract renamed the right details slot from `details` to `rightbar` and now hands the occupant resolved column geometry; the mobile layout registers and renders the slot under its new name with the real width, viewport, and eligibility values. The mobile page requires a host exposing the `rightbar` seat — 0.1.3-alpha.2 or later.
11
+
6
12
  ## 0.3.12 - 2026-09-07
7
13
 
8
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). No code change since 0.3.11.
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.12 update**: remote notification limits documented (#46, docs only, no code change). [Details](CHANGELOG.md).
22
+ > **0.3.13 update**: interception-driven, plugin-agnostic WebSocket approval — grouped blocked paths, dual red badges, manual entry under an advanced disclosure (#47) — plus panel polish (FRP step numbering, deploy-notes layout, approval button) and adaptation to DeepSeek Harness 0.1.3-alpha.2 (layout right-bar slot rename). [Details](CHANGELOG.md).
23
23
  >
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).
24
+ > **Upgrade reminder**: check your desktop DeepSeek Harness version before updating the plugin — **0.1.3-alpha.2 or later** pairs with plugin **0.3.13**; on an earlier desktop (0.1.2 line or 0.1.3-alpha.1) stay on plugin **0.3.12**. Existing 0.3.3-0.3.12 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.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>
27
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.3.13/dsh-mobile-android-v0.3.13.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.13/dsh-mobile-android-v0.3.13.apk"><strong>Download Android app 0.3.13</strong></a><br>
29
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.3.13">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.
@@ -43,6 +43,7 @@ It also lets you customize the phone from a DSH conversation: `/mobile <what you
43
43
  - **Image attachments**: use the top row of the composer plus menu to select an image or take a photo; PNG, JPEG, WebP, and GIF files up to 8 MiB are supported, plus full-resolution JPEG capture.
44
44
  - **Auto-discovery, no re-pairing**: Wi-Fi, hotspot, or IP changes normally recover automatically.
45
45
  - **One-click connection diagnostics**: check versions, gateway, network interface, firewall, and the remote path; stable reason codes are localized in the UI, and the copied report excludes credentials and complete addresses.
46
+ - **One-click approval for third-party plugin WebSockets**: the diagnostics view groups blocked plugin connections by directory (with attempt counts); allowing a path unblocks that exact path while everything unapproved stays blocked, and a red badge marks the sidebar entry until reviewed (#47). If a plugin keeps failing to connect (for example a terminal reporting 1006), first look at the diagnostics view for blocked connections and approve them in one click — manual configuration is usually unnecessary.
46
47
  - **Faster reconnection**: trusted connections race during restore, revisioned assets are reused, and mobile boot batches are compressed.
47
48
  - **Three pairing options**: scan a QR code, paste a pairing link, or enter a key.
48
49
 
@@ -200,14 +201,14 @@ The table below lists, for each plugin version, the DeepSeek Harness version it
200
201
 
201
202
  | DSH Mobile plugin | Verified DeepSeek Harness version |
202
203
  | --- | --- |
203
- | `0.3.10`-`0.3.12` | `0.1.3-alpha.1` |
204
- | `0.3.9` | `0.1.3-alpha.1` |
204
+ | `0.3.13` | `0.1.3-alpha.2` |
205
+ | `0.3.9`-`0.3.12` | `0.1.3-alpha.1` |
205
206
  | `0.3.6`-`0.3.8` | `0.1.2-rc.1` |
206
207
  | `0.3.4`, `0.3.5` | `0.1.2-alpha.2` |
207
208
  | `0.3.0`-`0.3.3` | `0.1.2-alpha.1` |
208
209
  | `0.1.4`, `0.2.x` | `0.1.1-rc.2` |
209
210
 
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.
211
+ Existing 0.3.3-0.3.13 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.
211
212
 
212
213
  ## Uninstall
213
214
 
package/README.md CHANGED
@@ -26,14 +26,14 @@
26
26
 
27
27
  > DSH Mobile 是 DeepSeek Harness 社区插件,原生 App 仅支持 Android。
28
28
  >
29
- > **0.3.12 更新**:补充远程通知限制说明(#46,纯文档,无代码改动)。[详细记录](CHANGELOG.md)。
29
+ > **0.3.13 更新**:第三方插件 WebSocket 连接拦截即通用一键放行——按目录分组、红点双入口、手动配置收进高级项(#47)+ 面板修整(FRP 步骤编号、部署说明排版、批准按钮)+ 适配 DeepSeek Harness 0.1.3-alpha.2(布局右栏插槽更名)。[详细记录](CHANGELOG.md)。
30
30
  >
31
- > **升级提醒**:Windows DSH Desktop 用户请更新至插件 0.3.12;现有 0.3.3-0.3.11 App 可继续使用,无需重新配对。[兼容说明](#兼容性)。
31
+ > **升级提醒**:先看电脑端 DeepSeek Harness 版本再升插件——**0.1.3-alpha.2 及以上**用插件 **0.3.13**;电脑端为更早版本(0.1.2 线或 0.1.3-alpha.1)时请保持插件 **0.3.12**。现有 0.3.3-0.3.12 App 可继续使用,无需重新配对。[兼容说明](#兼容性)。
32
32
 
33
33
  <p align="center">
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>
34
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/download/v0.3.13/dsh-mobile-android-v0.3.13.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.13/dsh-mobile-android-v0.3.13.apk"><strong>下载 Android App 0.3.13</strong></a><br>
36
+ <sub><a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.3.13">版本说明与校验文件</a></sub>
37
37
  </p>
38
38
 
39
39
  DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App 通过局域网,或可选的 Tailscale Funnel、cpolar、自建 FRP 远程通道连接电脑,继续使用同一份会话、工作区、消息和工具。局域网与远程访问分别启停、分别管理设备,且都不修改 DeepSeek Harness 源码。
@@ -50,6 +50,7 @@ DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App
50
50
  - **图片附件**:在已打开会话的输入栏加号菜单顶部选择图片或拍照;支持 PNG、JPEG、WebP、GIF(不超过 8 MiB)和完整分辨率 JPEG。
51
51
  - **自动发现、无需重新配对**:切换 Wi-Fi、热点或 IP 后通常自动恢复。
52
52
  - **一键连接诊断**:检查版本、网关、网卡、防火墙和远程通道;稳定的原因码在界面中本地化,并生成不含凭据与完整地址的脱敏报告。
53
+ - **第三方插件 WebSocket 一键放行**:诊断页按目录分组记录被拦截的插件连接(含次数),点允许即放行确切路径,未批准的一律拦截;有新拦截时侧栏红点提醒(#47)。若某插件的连接一直失败(如终端报 1006),先到诊断页看看有没有被拦的连接,一键放行即可,通常无需手动配置。
53
54
  - **更快恢复连接**:远程重开会并行恢复可信连接、复用版本化资源,并压缩移动端启动批次。
54
55
  - **三种配对方式**:扫码、配对链接、密钥。
55
56
 
@@ -209,14 +210,14 @@ flowchart LR
209
210
 
210
211
  | DSH Mobile 插件 | 验证支持的 DeepSeek Harness 版本 |
211
212
  | --- | --- |
212
- | `0.3.10`-`0.3.12` | `0.1.3-alpha.1` |
213
- | `0.3.9` | `0.1.3-alpha.1` |
213
+ | `0.3.13` | `0.1.3-alpha.2` |
214
+ | `0.3.9`-`0.3.12` | `0.1.3-alpha.1` |
214
215
  | `0.3.6`-`0.3.8` | `0.1.2-rc.1` |
215
216
  | `0.3.4`、`0.3.5` | `0.1.2-alpha.2` |
216
217
  | `0.3.0`-`0.3.3` | `0.1.2-alpha.1` |
217
218
  | `0.1.4`、`0.2.x` | `0.1.1-rc.2` |
218
219
 
219
- 现有 0.3.3-0.3.11 App 无需重新配对;更早的 App 使用不同的状态栏策略,建议同步升级;App 0.1.3 及更早版本需卸载重装并重新配对。
220
+ 现有 0.3.3-0.3.13 App 无需重新配对;更早的 App 使用不同的状态栏策略,建议同步升级;App 0.1.3 及更早版本需卸载重装并重新配对。
220
221
 
221
222
  ## 卸载
222
223
 
package/lib/client.js CHANGED
@@ -153,12 +153,20 @@ window.__ModuleLoader__.load({
153
153
  resetTailscaleConfirm: "Sign out of Tailscale on this computer and remove all remote paired devices? Local network configuration is unchanged.",
154
154
  requestFailed: "Request failed: {error}",
155
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.",
156
+ wsPathsIntro: "When an installed plugin cannot connect (say a sidebar terminal or a live panel), it is usually because its connection paths have not been approved yet. Everything that needs your attention is listed below — one click on “Allow” fixes it.",
157
157
  wsPathsPlaceholder: "/sidebar/ws/terminal",
158
158
  wsPathsAdd: "Allow path",
159
159
  wsPathsRemove: "Remove",
160
- wsPathsEmpty: "No extra paths allowed.",
161
- wsPathsInvalid: "Enter an exact path starting with / (no query string)."
160
+ wsPathsEmpty: "Nothing to configure right now. If a plugin cannot connect later, one-click options appear here.",
161
+ wsPathsInvalid: "Enter an exact path starting with / (no query string).",
162
+ wsPathsDetected: "A plugin connection was blocked — press Allow to let it through",
163
+ wsPathsAllow: "Allow",
164
+ wsPathsAllowAll: "Allow all",
165
+ wsPathsGroupMeta: "{n} paths · blocked {m} times",
166
+ wsPathsNewBlocked: "New blocked third-party connections — open diagnostics to review",
167
+ wsPathsAdvancedToggle: "Add paths manually (advanced)",
168
+ wsPathsAnnounce: "{n} plugin connections waiting to be approved",
169
+ wsPathsAdvancedHint: "Manual entries are rarely necessary: use the buttons above. Only fill this in when you know the exact path."
162
170
  },
163
171
  it: {
164
172
  mobileAccess: "Accesso mobile",
@@ -306,12 +314,20 @@ window.__ModuleLoader__.load({
306
314
  resetTailscaleConfirm: "Uscire da Tailscale su questo computer e rimuovere tutti i dispositivi remoti? La configurazione della rete locale resta invariata.",
307
315
  requestFailed: "Richiesta non riuscita: {error}",
308
316
  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.",
317
+ wsPathsIntro: "Quando un plugin installato non riesce a connettersi (per esempio un terminale laterale o un pannello live), di solito è perché i suoi percorsi di connessione non sono ancora stati approvati. Tutto ciò che richiede attenzione è elencato qui sotto: un clic su “Consenti” risolve.",
310
318
  wsPathsPlaceholder: "/sidebar/ws/terminal",
311
319
  wsPathsAdd: "Consenti percorso",
312
320
  wsPathsRemove: "Rimuovi",
313
- wsPathsEmpty: "Nessun percorso aggiuntivo consentito.",
314
- wsPathsInvalid: "Inserisci un percorso esatto che inizi con / (senza query)."
321
+ wsPathsEmpty: "Non serve configurare nulla per ora. Se in seguito un plugin non riesce a connettersi, qui compariranno opzioni con un clic.",
322
+ wsPathsInvalid: "Inserisci un percorso esatto che inizi con / (senza query).",
323
+ wsPathsDetected: "Una connessione di un plugin è stata bloccata — premi Consenti per lasciarla passare",
324
+ wsPathsAllow: "Consenti",
325
+ wsPathsAllowAll: "Consenti tutti",
326
+ wsPathsGroupMeta: "{n} percorsi · bloccati {m} volte",
327
+ wsPathsNewBlocked: "Nuove connessioni di terze parti bloccate — apri la diagnostica",
328
+ wsPathsAdvancedToggle: "Aggiungi percorsi manualmente (avanzato)",
329
+ wsPathsAnnounce: "{n} connessioni di plugin in attesa di approvazione",
330
+ wsPathsAdvancedHint: "Le voci manuali servono raramente: usa i pulsanti qui sopra. Compila solo se conosci il percorso esatto."
315
331
  },
316
332
  zh: {}
317
333
  };
@@ -461,12 +477,20 @@ window.__ModuleLoader__.load({
461
477
  resetTailscaleConfirm: "退出电脑上的 Tailscale 登录并移除所有远程配对设备?局域网配置不会改变。",
462
478
  requestFailed: "请求失败:{error}",
463
479
  wsPathsTitle: "第三方 WebSocket 路径",
464
- wsPathsIntro: "允许已装插件的 WebSocket 端点(确切路径,如 /sidebar/ws/terminal)经网关通行。DSH 内置路径始终可用,其余一律拦截。",
480
+ wsPathsIntro: "插件(如侧边栏终端、实时面板)连不上时,通常是它的连接路径还没放行。下面列出需要处理的项,点「允许」即可。",
465
481
  wsPathsPlaceholder: "/sidebar/ws/terminal",
466
482
  wsPathsAdd: "允许该路径",
467
483
  wsPathsRemove: "移除",
468
- wsPathsEmpty: "暂无额外允许的路径。",
469
- wsPathsInvalid: "请输入以 / 开头的确切路径(不带 query)。"
484
+ wsPathsEmpty: "目前无需任何设置。以后有插件连不上,会在这里出现可一键放行的选项。",
485
+ wsPathsInvalid: "请输入以 / 开头的确切路径(不带 query)。",
486
+ wsPathsDetected: "有插件连接被拦截——点「允许」放行",
487
+ wsPathsAllow: "允许",
488
+ wsPathsAllowAll: "全部允许",
489
+ wsPathsGroupMeta: "{n} 条路径 · 共拦截 {m} 次",
490
+ wsPathsNewBlocked: "发现被拦截的第三方连接,去诊断页查看",
491
+ wsPathsAdvancedToggle: "手动添加路径(高级)",
492
+ wsPathsAnnounce: "{n} 条插件连接等待放行",
493
+ wsPathsAdvancedHint: "通常不需要手动填写:用上方按钮即可。只有当你确切知道所需路径时才填。"
470
494
  });
471
495
  Object.assign(MOBILE_CONTROL_MESSAGES.en, {
472
496
  selfHostedConnections: "Self-hosted connection",
@@ -489,7 +513,6 @@ window.__ModuleLoader__.load({
489
513
  copyServerTemplate: "Copy server template",
490
514
  templateCopied: "Server template copied. Save frps.toml and the Caddy snippet on the VPS, then add the import line to the Caddyfile.",
491
515
  templateCopyFailed: "Could not copy the template. Check the four fields above.",
492
- vpsDeployTitle: "Deploy the VPS automatically",
493
516
  vpsDeployText: "Use an existing OpenSSH key or agent. The VPS must be Ubuntu/Debian with systemd; existing Caddy site configuration is never overwritten.",
494
517
  vpsDeployChangesTitle: "Automatic deployment will change the following on the VPS (manual template changes nothing by itself):",
495
518
  vpsDeployChangePackages: "Install Caddy from its official APT repository, plus a Python venv and Certbot for public-IPv4 certificates.",
@@ -578,7 +601,6 @@ window.__ModuleLoader__.load({
578
601
  copyServerTemplate: "Copia modello server",
579
602
  templateCopied: "Modello server copiato. Salva frps.toml e lo snippet Caddy sul VPS, poi aggiungi la riga import al Caddyfile.",
580
603
  templateCopyFailed: "Impossibile copiare il modello. Controlla i quattro campi sopra.",
581
- vpsDeployTitle: "Prepara automaticamente il VPS",
582
604
  vpsDeployText: "Usa una chiave OpenSSH o un agent esistente. Il VPS deve essere Ubuntu/Debian con systemd; una configurazione Caddy non vuota non viene sovrascritta.",
583
605
  vpsDeployChangesTitle: "Il deploy automatico modifica quanto segue sul VPS (il modello manuale da solo non cambia nulla):",
584
606
  vpsDeployChangePackages: "Installa Caddy dal repository APT ufficiale, più un venv Python e Certbot per i certificati IPv4 pubblici.",
@@ -667,7 +689,6 @@ window.__ModuleLoader__.load({
667
689
  copyServerTemplate: "复制手动部署模板",
668
690
  templateCopied: "服务器模板已复制,请在 VPS 上保存 frps.toml 和 Caddy 片段,并在 Caddyfile 里加一行 import。",
669
691
  templateCopyFailed: "无法复制模板,请检查上方连接信息。",
670
- vpsDeployTitle: "中转服务器部署",
671
692
  vpsDeployText: "填写 SSH 登录信息。服务器需为带 systemd 的 Ubuntu/Debian;不会覆盖已有的 Caddy 站点配置。",
672
693
  vpsDeployChangesTitle: "自动部署会在 VPS 上做这些改动(只复制手动模板不会改变任何东西):",
673
694
  vpsDeployChangePackages: "从官方 APT 源安装 Caddy,并安装 Python venv 与 Certbot(公网 IP 证书用)。",
@@ -2447,7 +2468,7 @@ window.__ModuleLoader__.load({
2447
2468
  frpConnectionGroup.append(frpConnectionSummary, frpStep1);
2448
2469
  const frpVpsGroup = element("details", "dsh-mobile-control__frp-group");
2449
2470
  const frpVpsSummary = element("summary");
2450
- frpVpsSummary.textContent = t("vpsDeployTitle");
2471
+ frpVpsSummary.textContent = t("frpStep2Title");
2451
2472
  frpVpsGroup.append(frpVpsSummary, frpStep2);
2452
2473
  const frpComponentGroup = element("details", "dsh-mobile-control__frp-group");
2453
2474
  const frpComponentSummary = element("summary");
@@ -2553,21 +2574,34 @@ window.__ModuleLoader__.load({
2553
2574
  wsPathsTitle.textContent = t("wsPathsTitle");
2554
2575
  const wsPathsIntro = element("p", "dsh-mobile-control__intro");
2555
2576
  wsPathsIntro.textContent = t("wsPathsIntro");
2577
+ const wsPathsDetected = element("div", "dsh-mobile-control__ws-paths-detected");
2578
+ wsPathsDetected.hidden = true;
2579
+ const wsPathsDetectedTitle = element("p", "dsh-mobile-control__ws-paths-detected-title");
2580
+ wsPathsDetectedTitle.textContent = t("wsPathsDetected");
2581
+ const wsPathsDetectedList = element("ul", "dsh-mobile-control__ws-paths-list");
2582
+ wsPathsDetected.append(wsPathsDetectedTitle, wsPathsDetectedList);
2556
2583
  const wsPathsList = element("ul", "dsh-mobile-control__ws-paths-list");
2584
+ const wsPathsAdvanced = element("details", "dsh-mobile-control__ws-paths-advanced");
2585
+ const wsPathsAdvancedSummary = element("summary");
2586
+ wsPathsAdvancedSummary.textContent = t("wsPathsAdvancedToggle");
2587
+ const wsPathsAdvancedHint = element("p", "dsh-mobile-control__intro");
2588
+ wsPathsAdvancedHint.textContent = t("wsPathsAdvancedHint");
2557
2589
  const wsPathsRow = element("div", "dsh-mobile-control__ws-paths-row");
2558
2590
  const wsPathsInput = element("input", "dsh-mobile-control__ws-paths-input");
2559
2591
  wsPathsInput.type = "text";
2560
2592
  wsPathsInput.placeholder = t("wsPathsPlaceholder");
2561
2593
  wsPathsInput.setAttribute("aria-label", t("wsPathsTitle"));
2562
- const wsPathsAdd = element("button", "dsh-mobile-control__secondary");
2594
+ const wsPathsAdd = element("button", "dsh-mobile-control__primary");
2563
2595
  wsPathsAdd.type = "button";
2564
2596
  wsPathsAdd.textContent = t("wsPathsAdd");
2565
2597
  const wsPathsStatus = element("p", "dsh-mobile-control__status");
2566
2598
  wsPathsStatus.hidden = true;
2567
2599
  wsPathsStatus.setAttribute("aria-live", "polite");
2568
2600
  wsPathsRow.append(wsPathsInput, wsPathsAdd);
2569
- wsPathsSection.append(wsPathsTitle, wsPathsIntro, wsPathsList, wsPathsRow, wsPathsStatus);
2601
+ wsPathsAdvanced.append(wsPathsAdvancedSummary, wsPathsAdvancedHint, wsPathsRow, wsPathsStatus);
2602
+ wsPathsSection.append(wsPathsTitle, wsPathsIntro, wsPathsDetected, wsPathsList, wsPathsAdvanced);
2570
2603
  let wsPathsCurrent = [];
2604
+ let wsPathsBlocked = [];
2571
2605
  let wsPathsLoaded = false;
2572
2606
  const renderWsPaths = (paths) => {
2573
2607
  wsPathsCurrent = [...paths];
@@ -2592,11 +2626,103 @@ window.__ModuleLoader__.load({
2592
2626
  wsPathsList.append(item);
2593
2627
  }
2594
2628
  };
2629
+ const wsGroupOf = (path) => {
2630
+ const slash = path.lastIndexOf("/");
2631
+ return slash > 0 ? path.slice(0, slash) : "/";
2632
+ };
2633
+ let wsPathsSeenAttempts = 0;
2634
+ let wsDotAnnounced = 0;
2635
+ const wsDotStatus = element("p", "dsh-mobile-control__ws-sr-status");
2636
+ wsDotStatus.setAttribute("role", "status");
2637
+ wsDotStatus.setAttribute("aria-atomic", "true");
2638
+ const wsBlockedTotal = (blocked) => blocked.reduce((sum, entry) => sum + entry.attempts, 0);
2639
+ const renderWsDot = () => {
2640
+ for (const stale of document.querySelectorAll(".dsh-mobile-control__ws-dot")) stale.remove();
2641
+ if (!wsDotStatus.isConnected) root.append(wsDotStatus);
2642
+ const pendingCount = wsPathsBlocked.filter((entry) => !wsPathsCurrent.includes(entry.path)).length;
2643
+ if (pendingCount === 0 || wsBlockedTotal(wsPathsBlocked) <= wsPathsSeenAttempts) {
2644
+ if (pendingCount === 0 && wsDotAnnounced !== 0) {
2645
+ wsDotAnnounced = 0;
2646
+ wsDotStatus.textContent = "";
2647
+ }
2648
+ return;
2649
+ }
2650
+ if (wsDotAnnounced !== pendingCount) {
2651
+ wsDotAnnounced = pendingCount;
2652
+ wsDotStatus.textContent = t("wsPathsAnnounce", { n: String(pendingCount) });
2653
+ }
2654
+ for (const trigger of document.querySelectorAll(".dsh-mobile-control__trigger, .dsh-mobile-control__diagnostic-entry")) {
2655
+ const dot = element("span", "dsh-mobile-control__ws-dot");
2656
+ dot.textContent = String(pendingCount);
2657
+ dot.title = t("wsPathsNewBlocked");
2658
+ dot.setAttribute("aria-hidden", "true");
2659
+ dot.addEventListener("click", (event) => {
2660
+ event.stopPropagation();
2661
+ setOpen(true);
2662
+ selectView("diagnostics");
2663
+ });
2664
+ trigger.append(dot);
2665
+ }
2666
+ };
2667
+ const renderWsBlocked = (blocked) => {
2668
+ wsPathsDetectedList.replaceChildren();
2669
+ const pending = blocked.filter((entry) => !wsPathsCurrent.includes(entry.path));
2670
+ wsPathsDetected.hidden = pending.length === 0;
2671
+ const groups = /* @__PURE__ */ new Map();
2672
+ for (const entry of pending) {
2673
+ const group = wsGroupOf(entry.path);
2674
+ const list = groups.get(group);
2675
+ if (list) list.push(entry);
2676
+ else groups.set(group, [entry]);
2677
+ }
2678
+ for (const [group, entries] of groups) {
2679
+ const groupItem = element("li", "dsh-mobile-control__ws-paths-group");
2680
+ const groupHead = element("div", "dsh-mobile-control__ws-paths-group-head");
2681
+ const groupLabel = element("code");
2682
+ groupLabel.textContent = `${group}/*`;
2683
+ const groupCount = element("span", "dsh-mobile-control__ws-count");
2684
+ groupCount.textContent = t("wsPathsGroupMeta", {
2685
+ n: entries.length,
2686
+ m: entries.reduce((sum, entry) => sum + entry.attempts, 0)
2687
+ });
2688
+ groupHead.append(groupLabel, groupCount);
2689
+ if (entries.length > 1) {
2690
+ const allowAll = element("button", "dsh-mobile-control__primary");
2691
+ allowAll.type = "button";
2692
+ allowAll.textContent = t("wsPathsAllowAll");
2693
+ allowAll.addEventListener("click", () => {
2694
+ saveWsPaths([...wsPathsCurrent, ...entries.map((entry) => entry.path)]);
2695
+ });
2696
+ groupHead.append(allowAll);
2697
+ }
2698
+ groupItem.append(groupHead);
2699
+ for (const entry of entries) {
2700
+ const item = element("div", "dsh-mobile-control__ws-paths-item is-child");
2701
+ const label = element("code");
2702
+ label.textContent = `${entry.path} ×${String(entry.attempts)}`;
2703
+ const allow = element("button", "dsh-mobile-control__primary");
2704
+ allow.type = "button";
2705
+ allow.textContent = t("wsPathsAllow");
2706
+ allow.setAttribute("aria-label", `${t("wsPathsAllow")}: ${entry.path}`);
2707
+ allow.addEventListener("click", () => {
2708
+ saveWsPaths([...wsPathsCurrent, entry.path]);
2709
+ });
2710
+ item.append(label, allow);
2711
+ groupItem.append(item);
2712
+ }
2713
+ wsPathsDetectedList.append(groupItem);
2714
+ }
2715
+ renderWsDot();
2716
+ };
2595
2717
  const loadWsPaths = () => {
2596
2718
  controlRequestJson("/api/mobile-access/remote/websocket-paths").then((data) => {
2597
2719
  wsPathsLoaded = true;
2598
2720
  wsPathsStatus.hidden = true;
2599
2721
  renderWsPaths(Array.isArray(data.paths) ? data.paths.filter((entry) => typeof entry === "string") : []);
2722
+ controlRequestJson("/api/mobile-access/remote/websocket-paths/blocked").then((blocked) => {
2723
+ wsPathsBlocked = (Array.isArray(blocked.blocked) ? blocked.blocked : []).filter((entry) => typeof entry === "object" && entry !== null && typeof entry.path === "string" && typeof entry.attempts === "number");
2724
+ renderWsBlocked(wsPathsBlocked);
2725
+ }, () => {});
2600
2726
  }, (error) => {
2601
2727
  wsPathsStatus.hidden = false;
2602
2728
  wsPathsStatus.textContent = t("requestFailed", { error: String(error) });
@@ -2610,6 +2736,7 @@ window.__ModuleLoader__.load({
2610
2736
  }).then((data) => {
2611
2737
  wsPathsLoaded = true;
2612
2738
  renderWsPaths(Array.isArray(data.paths) ? data.paths.filter((entry) => typeof entry === "string") : []);
2739
+ renderWsBlocked(wsPathsBlocked);
2613
2740
  }, (error) => {
2614
2741
  wsPathsStatus.hidden = false;
2615
2742
  wsPathsStatus.textContent = t("requestFailed", { error: String(error) });
@@ -2626,7 +2753,7 @@ window.__ModuleLoader__.load({
2626
2753
  saveWsPaths([...wsPathsCurrent, value]);
2627
2754
  });
2628
2755
  const remoteWorkspace = element("section", "dsh-mobile-control__remote-workspace");
2629
- remoteWorkspace.append(providerSetupHeader, remoteStatus, remoteAccess, remoteGuide, providerSetupBody, remoteActions, remoteQr, remoteManageRow, remoteDevicePanel, wsPathsSection);
2756
+ remoteWorkspace.append(providerSetupHeader, remoteStatus, remoteAccess, remoteGuide, providerSetupBody, remoteActions, remoteQr, remoteManageRow, remoteDevicePanel);
2630
2757
  const diagnosticsView = element("div", "dsh-mobile-control__view is-diagnostics");
2631
2758
  diagnosticsView.hidden = true;
2632
2759
  const diagnosticsIntro = element("p", "dsh-mobile-control__intro");
@@ -2672,7 +2799,7 @@ window.__ModuleLoader__.load({
2672
2799
  actions.append(toggle, pair, linkPair);
2673
2800
  lanView.append(access, qrBox, status, extensionStatus, actions, manageRow, devicePanel);
2674
2801
  remoteView.append(remoteIntro, providerSection, remoteWorkspace);
2675
- diagnosticsView.append(diagnosticsIntro, diagnosticsSummary, diagnosticsToolbar, diagnosticsFeedback, diagnosticsChecks, diagnosticsDetails);
2802
+ diagnosticsView.append(diagnosticsIntro, diagnosticsSummary, diagnosticsToolbar, diagnosticsFeedback, diagnosticsChecks, wsPathsSection, diagnosticsDetails);
2676
2803
  panel.append(header, releaseNotice, appDownload, switcher, lanView, remoteView, diagnosticsView);
2677
2804
  root.append(panel);
2678
2805
  document.body.append(root);
@@ -2756,6 +2883,11 @@ window.__ModuleLoader__.load({
2756
2883
  diagnosticsEntry.setAttribute("aria-pressed", String(view === "diagnostics"));
2757
2884
  diagnosticsEntry.textContent = view === "diagnostics" ? t("back") : t("diagnostics");
2758
2885
  diagnosticsEntry.setAttribute("aria-label", view === "diagnostics" ? t("backToMobile") : t("openDiagnostics"));
2886
+ if (view === "diagnostics") {
2887
+ wsPathsSeenAttempts = wsBlockedTotal(wsPathsBlocked);
2888
+ renderWsDot();
2889
+ if (!wsPathsLoaded) loadWsPaths();
2890
+ }
2759
2891
  updatePlugin.hidden = view === "diagnostics" || !pluginUpdateAvailable;
2760
2892
  appDownload.hidden = view === "diagnostics";
2761
2893
  switcher.hidden = view === "diagnostics";
@@ -2767,7 +2899,6 @@ window.__ModuleLoader__.load({
2767
2899
  remoteTab.addEventListener("click", () => {
2768
2900
  selectView("remote");
2769
2901
  loadRemote();
2770
- if (!wsPathsLoaded) loadWsPaths();
2771
2902
  });
2772
2903
  const setOpen = (open) => {
2773
2904
  panel.hidden = !open;
@@ -2984,7 +3115,7 @@ window.__ModuleLoader__.load({
2984
3115
  if (!frpLayoutInitialized) {
2985
3116
  frpConnectionGroup.open = !frpConfigured;
2986
3117
  frpComponentGroup.open = !frpInstalled;
2987
- frpVpsGroup.open = false;
3118
+ frpVpsGroup.open = !frpConfigured;
2988
3119
  frpLayoutInitialized = true;
2989
3120
  }
2990
3121
  selfHostedBadge.textContent = frpConfigured && frpInstalled ? t("ready") : t("advanced");
@@ -3900,10 +4031,19 @@ window.__ModuleLoader__.load({
3900
4031
  const remotePoll = window.setInterval(() => {
3901
4032
  if (!panel.hidden && !remoteView.hidden) loadRemote();
3902
4033
  }, 1500);
4034
+ const pollWsBlocked = () => {
4035
+ controlRequestJson("/api/mobile-access/remote/websocket-paths/blocked").then((blocked) => {
4036
+ wsPathsBlocked = (Array.isArray(blocked.blocked) ? blocked.blocked : []).filter((entry) => typeof entry === "object" && entry !== null && typeof entry.path === "string" && typeof entry.attempts === "number");
4037
+ renderWsDot();
4038
+ if (!diagnosticsView.hidden) renderWsBlocked(wsPathsBlocked);
4039
+ }, () => {});
4040
+ };
4041
+ const wsBlockedPoll = window.setInterval(pollWsBlocked, 2e4);
3903
4042
  return {
3904
4043
  remove: () => {
3905
4044
  lifecycle.abort();
3906
4045
  window.clearInterval(remotePoll);
4046
+ window.clearInterval(wsBlockedPoll);
3907
4047
  window.removeEventListener("focus", retryAfterSetup);
3908
4048
  document.removeEventListener("visibilitychange", retryAfterSetup);
3909
4049
  document.removeEventListener("pointerdown", dismiss);
@@ -5087,7 +5227,7 @@ window.__ModuleLoader__.load({
5087
5227
  .dsh-mobile-control__provider-section{position:relative;margin:0 0 14px}.dsh-mobile-control__section-title{margin:0 0 8px;color:var(--dsw-alias-label-primary,#16181d);font:650 13px/1.4 system-ui}.dsh-mobile-control__provider-section>.dsh-mobile-control__section-title{padding-right:42px}.dsh-mobile-control__provider-choices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.dsh-mobile-control__provider{display:flex;min-width:0;flex-direction:column;gap:6px;min-height:94px;padding:10px 11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);text-align:left;cursor:pointer;touch-action:manipulation;transition:border-color 160ms ease,background-color 160ms ease,box-shadow 160ms ease}.dsh-mobile-control__provider:hover{border-color:#6f96db;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-1,#f8fbff))}.dsh-mobile-control__provider.is-selected{border-color:#2563eb;background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-bg-layer-1,#f5f8ff));box-shadow:0 0 0 1px #2563eb inset}.dsh-mobile-control__provider:disabled{cursor:wait;opacity:.62}.dsh-mobile-control__provider-top{display:flex;min-width:0;align-items:flex-start;justify-content:space-between;gap:5px}.dsh-mobile-control__provider-top strong{min-width:0;font-size:12px;line-height:1.3}.dsh-mobile-control__provider-badge{flex:none;padding:2px 5px;border-radius:999px;background:#e8f0ff;color:#1d4ed8;font:650 9px/1.25 system-ui}.dsh-mobile-control__provider-badge.is-cpolar{background:#eaf8f2;color:#087454}.dsh-mobile-control__provider-description{color:var(--dsw-alias-label-secondary,#606873);font-size:10px;line-height:1.45}.dsh-mobile-control__provider-info{position:absolute;z-index:5;top:-13px;right:-8px}.dsh-mobile-control__provider-info-button{display:flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary,#475569);cursor:pointer;touch-action:manipulation}.dsh-mobile-control__provider-info-button:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f5f9);color:#2563eb}.dsh-mobile-control__provider-info-glyph{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:18px;height:18px;border:1.5px solid currentColor;border-radius:50%;font:700 12px/1 system-ui}.dsh-mobile-control__provider-info-popover{position:absolute;z-index:6;top:38px;right:4px;box-sizing:border-box;width:min(292px,calc(100vw - 72px));padding:10px 12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 10px 28px rgb(15 23 42 / 16%)}.dsh-mobile-control__provider-info-popover[hidden]{display:none}.dsh-mobile-control__provider-info-popover strong,.dsh-mobile-control__provider-info-popover span{display:block}.dsh-mobile-control__provider-info-popover strong{margin-bottom:3px;font-size:12px}.dsh-mobile-control__provider-info-popover span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}
5088
5228
  .dsh-mobile-control__self-hosted{margin:8px 0 0;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__self-hosted-summary{display:flex;box-sizing:border-box;min-height:48px;align-items:center;justify-content:space-between;gap:10px;padding:8px 11px;cursor:pointer;list-style-position:inside}.dsh-mobile-control__self-hosted-summary>span:first-child{display:flex;min-width:0;flex-direction:column;gap:1px}.dsh-mobile-control__self-hosted-summary strong{font-size:11px}.dsh-mobile-control__self-hosted-summary span span{color:var(--dsw-alias-label-secondary,#606873);font-size:9px;line-height:1.35}.dsh-mobile-control__provider-badge.is-frp{background:#eef0f3;color:#475569}.dsh-mobile-control__self-hosted-body{padding:0 8px 8px}.dsh-mobile-control__provider.is-frp{width:100%;min-height:64px;background:var(--dsw-alias-bg-layer-2,#fff)}
5089
5229
  .dsh-mobile-control__cpolar-setup{margin:0 0 12px;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__cpolar-setup[hidden],.dsh-mobile-control__cpolar-account[hidden],.dsh-mobile-control__details[hidden],.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions[hidden],.dsh-mobile-control__danger[hidden]{display:none}.dsh-mobile-control__component-status,.dsh-mobile-control__component-note{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__primary{width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:600 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__cpolar-account{margin-top:10px}.dsh-mobile-control__link-row{display:flex;flex-wrap:wrap;gap:6px 12px;margin:0 0 10px}.dsh-mobile-control__text-link{color:#2563eb;font-size:11px;text-decoration:none}.dsh-mobile-control__text-link:hover{text-decoration:underline}.dsh-mobile-control__token-label{display:flex;flex-direction:column;gap:5px;margin:0 0 8px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__token{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2,#fff));color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__cpolar-connect{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;min-height:44px;padding:10px 14px;border-radius:12px;font:650 13px/1.2 system-ui;cursor:pointer;transition:background-color 160ms ease,border-color 160ms ease,opacity 160ms ease}.dsh-mobile-control__cpolar-connect:hover:not(:disabled){border-color:#1d4ed8;background:#1d4ed8}.dsh-mobile-control__cpolar-connect:active:not(:disabled){border-color:#1e40af;background:#1e40af}.dsh-mobile-control__cpolar-connect:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__details{margin:10px 0 0;border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb);padding-top:9px}.dsh-mobile-control__details>summary{min-height:30px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:30px;cursor:pointer}.dsh-mobile-control__details-body{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;padding:4px 0}.dsh-mobile-control__details-body p{flex:1 0 100%;margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__storage{display:block;flex:1 0 100%;max-width:100%;overflow:hidden;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#475569);font:10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__danger{flex:1 0 100%;min-height:38px;margin-top:3px;padding:7px 10px;border:1px solid #dc2626;border-radius:9px;background:transparent;color:#dc2626;font:12px/1.3 system-ui;cursor:pointer}
5090
- .dsh-mobile-control__frp-setup{margin: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__frp-setup[hidden]{display:none}.dsh-mobile-control__frp-step{padding:11px 0}.dsh-mobile-control__frp-step + .dsh-mobile-control__frp-step{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__frp-step>strong{display:block;margin-bottom:3px;font-size:12px;line-height:1.4}.dsh-mobile-control__frp-step>p{margin:0 0 9px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-step>.dsh-mobile-control__frp-requirement{padding:8px 9px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:11px}.dsh-mobile-control__frp-fields{display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px}.dsh-mobile-control__field{display:flex;min-width:0;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__field:nth-child(3),.dsh-mobile-control__field:nth-child(4){grid-column:1/-1}.dsh-mobile-control__field input{box-sizing:border-box;width:100%;min-height:44px;padding:9px 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:16px/1.4 system-ui}.dsh-mobile-control__frp-action{box-sizing:border-box;width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__frp-action:disabled{cursor:not-allowed;opacity:.5}.dsh-mobile-control__remote-workspace{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f7f8fa)}.dsh-mobile-control__stage-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.dsh-mobile-control__stage-header .dsh-mobile-control__section-title{margin:0}.dsh-mobile-control__stage-meta{display:flex;min-width:0;align-items:center;justify-content:flex-end;gap:5px}.dsh-mobile-control__stage-value{max-width:115px;overflow:hidden;color:var(--dsw-alias-label-primary,#16181d);font:650 10px/1.3 system-ui;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__state-badge{flex:none;padding:3px 7px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#606873);font:650 9px/1.25 system-ui}.dsh-mobile-control__state-badge.is-ready{background:#e6f7f0;color:#087454}.dsh-mobile-control__state-badge.is-busy{background:#e8f0ff;color:#1d4ed8}.dsh-mobile-control__state-badge.is-attention{background:#fff4dc;color:#935100}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__status{box-sizing:border-box;margin:0 0 10px;padding:9px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:11px;line-height:1.45}.dsh-mobile-control__provider-setup-body{margin:0 0 10px}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup{margin:0}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__section-title,.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__frp-setup>.dsh-mobile-control__section-title{display:none}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__details{margin:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__actions{margin-top:2px}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__qr{margin:10px 0 0}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__manage-row{margin-top:10px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}
5230
+ .dsh-mobile-control__frp-setup{margin: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__frp-setup[hidden]{display:none}.dsh-mobile-control__frp-step{padding:11px 0}.dsh-mobile-control__frp-step + .dsh-mobile-control__frp-step{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__frp-step>strong{display:block;margin-bottom:3px;font-size:12px;line-height:1.4}.dsh-mobile-control__frp-step>p{margin:0 0 9px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes{margin:0 0 9px;padding:0;list-style:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes li{position:relative;margin:0;padding-left:12px}.dsh-mobile-control__frp-changes li+li{margin-top:5px}.dsh-mobile-control__frp-changes li::before{content:"•";position:absolute;left:2px;color:var(--dsw-alias-label-tertiary,#98a1ad)}.dsh-mobile-control__frp-step>.dsh-mobile-control__frp-requirement{padding:8px 9px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:11px}.dsh-mobile-control__frp-fields{display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px}.dsh-mobile-control__field{display:flex;min-width:0;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__field:nth-child(3),.dsh-mobile-control__field:nth-child(4){grid-column:1/-1}.dsh-mobile-control__field input{box-sizing:border-box;width:100%;min-height:44px;padding:9px 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:16px/1.4 system-ui}.dsh-mobile-control__frp-action{box-sizing:border-box;width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__frp-action:disabled{cursor:not-allowed;opacity:.5}.dsh-mobile-control__remote-workspace{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f7f8fa)}.dsh-mobile-control__stage-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.dsh-mobile-control__stage-header .dsh-mobile-control__section-title{margin:0}.dsh-mobile-control__stage-meta{display:flex;min-width:0;align-items:center;justify-content:flex-end;gap:5px}.dsh-mobile-control__stage-value{max-width:115px;overflow:hidden;color:var(--dsw-alias-label-primary,#16181d);font:650 10px/1.3 system-ui;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__state-badge{flex:none;padding:3px 7px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#606873);font:650 9px/1.25 system-ui}.dsh-mobile-control__state-badge.is-ready{background:#e6f7f0;color:#087454}.dsh-mobile-control__state-badge.is-busy{background:#e8f0ff;color:#1d4ed8}.dsh-mobile-control__state-badge.is-attention{background:#fff4dc;color:#935100}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__status{box-sizing:border-box;margin:0 0 10px;padding:9px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:11px;line-height:1.45}.dsh-mobile-control__provider-setup-body{margin:0 0 10px}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup{margin:0}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__section-title,.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__frp-setup>.dsh-mobile-control__section-title{display:none}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__details{margin:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__actions{margin-top:2px}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__qr{margin:10px 0 0}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__manage-row{margin-top:10px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}
5091
5231
  .dsh-mobile-control__frp-overview{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;gap:10px;margin:0 0 10px;padding:10px;border:1px solid #a9dfc9;border-radius:11px;background:#edf9f4}.dsh-mobile-control__frp-overview[hidden]{display:none}.dsh-mobile-control__frp-overview-mark{display:grid;width:32px;height:32px;place-items:center;border-radius:50%;background:#087454;color:#fff;font:700 15px/1 system-ui}.dsh-mobile-control__frp-overview-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__frp-overview-body strong{color:#075d46;font-size:12px;line-height:1.35}.dsh-mobile-control__frp-overview-body span{overflow:hidden;color:#357061;font:10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__frp-group{margin:8px 0 0;overflow:hidden;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__frp-group>summary{box-sizing:border-box;min-height:44px;padding:12px 34px 10px 12px;color:var(--dsw-alias-label-primary,#16181d);font:650 12px/1.4 system-ui;cursor:pointer}.dsh-mobile-control__frp-group[open]>summary{border-bottom:1px solid var(--dsw-alias-border-subtle,#e1e5eb);background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step{padding:12px}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step>strong:first-child{display:none}.dsh-mobile-control__frp-group .dsh-mobile-control__frp-step{border-top:0}.dsh-mobile-control__frp-setup>.dsh-mobile-control__frp-step{margin-bottom:8px;padding:10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}
5092
5232
  .dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:#2563eb;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}
5093
5233
  .dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:#98a1ad;content:""}.dsh-mobile-control__status.is-running::before{background:#16a36a}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}
@@ -5102,7 +5242,7 @@ window.__ModuleLoader__.load({
5102
5242
  .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}
5103
5243
  .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}
5104
5244
  .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}
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}
5245
+ .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__trigger,.dsh-mobile-control__diagnostic-entry{position:relative}.dsh-mobile-control__ws-dot{position:absolute;top:2px;right:2px;z-index:1;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:#dc2626;color:#fff;font:650 10px/16px system-ui;text-align:center;cursor:pointer}.dsh-mobile-control__ws-dot::after{content:'';position:absolute;inset:-5px}.dsh-mobile-control__ws-sr-status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.dsh-mobile-control__ws-paths-row{display:flex;gap:8px}.dsh-mobile-control__ws-paths-row .dsh-mobile-control__primary{width:auto;flex:none;padding:9px 14px;border-radius:10px}.dsh-mobile-control__ws-paths-group{padding:6px 0}.dsh-mobile-control__ws-paths-group + .dsh-mobile-control__ws-paths-group{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__ws-paths-group-head{display:flex;align-items:center;gap:8px}.dsh-mobile-control__ws-count{flex:none;padding:1px 7px;border-radius:999px;background:#e8f0ff;color:#1d4ed8;font:650 10px/1.6 system-ui}.dsh-mobile-control__ws-paths-item.is-child{padding-left:12px}.dsh-mobile-control__ws-paths-group-head .dsh-mobile-control__primary,.dsh-mobile-control__ws-paths-item .dsh-mobile-control__primary{width:auto;flex:none;min-height:34px;padding:6px 12px;border-radius:10px}.dsh-mobile-control__ws-paths-row input{flex:1 1 0;min-width:0;box-sizing:border-box;min-height:44px;padding:9px 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:16px/1.4 system-ui}.dsh-mobile-control__ws-paths-row button{flex:none}.dsh-mobile-control__ws-paths-detected-title{margin:0 0 2px;font:600 12px/1.4 system-ui}.dsh-mobile-control__ws-paths-advanced{margin-top:8px}.dsh-mobile-control__ws-paths-advanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary,#606873);font:12px/1.4 system-ui;user-select:none}.dsh-mobile-control__ws-paths-advanced summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px;border-radius:4px}.dsh-mobile-control__ws-paths-advanced[open] summary{margin-bottom:6px}.dsh-mobile-control__ws-paths-advanced .dsh-mobile-control__status{margin:6px 0 0}
5106
5246
  @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}}
5107
5247
  `;
5108
5248
  /** Mount the desktop control or mobile feature enhancements. */