dsh-mobile 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -76,6 +76,13 @@ window.__ModuleLoader__.load({
76
76
  [data-dsh-mobile-settings-options] :is(input,select,textarea,button) { max-width:100%; }
77
77
  [data-dsh-mobile-settings-options] :is(input,select,textarea) { box-sizing:border-box; width:100%; min-width:0; }
78
78
  [data-dsh-mobile-settings-options] [class*="_head"] { min-width:0; flex-wrap:wrap; }
79
+ /* Provider names may shrink, but their edit/delete actions remain horizontal
80
+ and retain a full touch target on narrow screens. */
81
+ [data-dsh-mobile-settings-options] [class*="_rowHead"]:has(> [class*="_rowIdentity"]) { flex-wrap:nowrap !important; align-items:center !important; }
82
+ [data-dsh-mobile-settings-options] [class*="_rowIdentity"] { flex:1 1 auto !important; min-width:0 !important; overflow:hidden !important; }
83
+ [data-dsh-mobile-settings-options] [class*="_rowName"] { min-width:0 !important; overflow:hidden !important; text-overflow:ellipsis !important; white-space:nowrap !important; }
84
+ [data-dsh-mobile-settings-options] [class*="_rowActions"] { flex:0 0 auto !important; flex-wrap:nowrap !important; width:max-content !important; min-width:max-content !important; max-width:none !important; }
85
+ [data-dsh-mobile-settings-options] [class*="_rowActions"] button { flex:none !important; width:auto !important; min-width:44px !important; max-width:none !important; min-height:44px !important; padding-inline:10px !important; white-space:nowrap !important; word-break:keep-all !important; writing-mode:horizontal-tb !important; }
79
86
  [data-dsh-mobile-settings-content][data-dsh-mobile-view-transition="true"],
80
87
  [data-dsh-mobile-view][data-dsh-mobile-view-transition="true"] { animation:dsh-mobile-view-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }
81
88
  [data-dsh-mobile-center] textarea { font-size:16px !important; }
@@ -444,17 +451,38 @@ window.__ModuleLoader__.load({
444
451
  if (className !== void 0) node.className = className;
445
452
  return node;
446
453
  }
447
- async function requestJson(path, init) {
448
- const response = await fetch(path, {
449
- ...init,
450
- headers: {
451
- "content-type": "application/json",
452
- ...init?.headers
453
- }
454
- });
455
- const body = await response.json();
456
- if (!response.ok) throw new Error(typeof body.error === "string" ? body.error : `HTTP ${String(response.status)}`);
457
- return body;
454
+ const CONTROL_REQUEST_TIMEOUT_MS = 15e3;
455
+ const LONG_CONTROL_REQUEST_TIMEOUT_MS = 13e4;
456
+ async function requestJson(path, init, timeoutMs = CONTROL_REQUEST_TIMEOUT_MS) {
457
+ const controller = new AbortController();
458
+ const upstreamSignal = init?.signal;
459
+ const abortFromUpstream = () => {
460
+ controller.abort(upstreamSignal?.reason);
461
+ };
462
+ if (upstreamSignal?.aborted === true) abortFromUpstream();
463
+ else upstreamSignal?.addEventListener("abort", abortFromUpstream, { once: true });
464
+ const timer = window.setTimeout(() => {
465
+ controller.abort();
466
+ }, timeoutMs);
467
+ try {
468
+ const response = await fetch(path, {
469
+ ...init,
470
+ signal: controller.signal,
471
+ headers: {
472
+ "content-type": "application/json",
473
+ ...init?.headers
474
+ }
475
+ });
476
+ const body = await response.json();
477
+ if (!response.ok) throw new Error(typeof body.error === "string" ? body.error : `HTTP ${String(response.status)}`);
478
+ return body;
479
+ } catch (error) {
480
+ if (controller.signal.aborted && upstreamSignal?.aborted !== true) throw new Error("操作超时,请确认 DSH 仍在运行后重试。");
481
+ throw error;
482
+ } finally {
483
+ clearTimeout(timer);
484
+ upstreamSignal?.removeEventListener("abort", abortFromUpstream);
485
+ }
458
486
  }
459
487
  function officialFunnelSetupUrl(value) {
460
488
  if (typeof value !== "string" || value.length > 2048) return "";
@@ -477,10 +505,17 @@ window.__ModuleLoader__.load({
477
505
  const header = element("header", "dsh-mobile-control__header");
478
506
  const title = element("h2");
479
507
  title.textContent = "移动访问";
508
+ const headerActions = element("div", "dsh-mobile-control__header-actions");
509
+ const diagnosticsEntry = element("button", "dsh-mobile-control__diagnostic-entry");
510
+ diagnosticsEntry.type = "button";
511
+ diagnosticsEntry.textContent = "诊断";
512
+ diagnosticsEntry.setAttribute("aria-label", "打开连接诊断");
513
+ diagnosticsEntry.setAttribute("aria-pressed", "false");
480
514
  const close = element("button", "dsh-mobile-control__close");
481
515
  close.type = "button";
482
516
  close.textContent = "×";
483
517
  close.setAttribute("aria-label", "收起移动访问");
518
+ headerActions.append(diagnosticsEntry, close);
484
519
  const appDownload = element("a", "dsh-mobile-control__app-download");
485
520
  appDownload.href = "https://github.com/saya-ch/dsh-mobile/releases/latest";
486
521
  appDownload.target = "_blank";
@@ -588,7 +623,7 @@ window.__ModuleLoader__.load({
588
623
  cpolarChoiceDescription.textContent = "按需安装官方组件,适合国内网络环境。";
589
624
  cpolarChoiceTop.append(cpolarChoiceName, cpolarChoiceBadge);
590
625
  cpolarChoice.append(cpolarChoiceTop, cpolarChoiceDescription);
591
- providerChoices.append(tailscaleChoice, cpolarChoice);
626
+ providerChoices.append(cpolarChoice, tailscaleChoice);
592
627
  providerSection.append(providerHeading, providerInfo, providerChoices);
593
628
  const cpolarSetup = element("section", "dsh-mobile-control__cpolar-setup");
594
629
  cpolarSetup.hidden = true;
@@ -728,11 +763,53 @@ window.__ModuleLoader__.load({
728
763
  remoteManageRow.append(remoteDevices, remoteReset);
729
764
  const remoteDevicePanel = element("div", "dsh-mobile-control__devices");
730
765
  remoteDevicePanel.hidden = true;
731
- header.append(title, close);
766
+ const diagnosticsView = element("div", "dsh-mobile-control__view is-diagnostics");
767
+ diagnosticsView.hidden = true;
768
+ const diagnosticsIntro = element("p", "dsh-mobile-control__intro");
769
+ diagnosticsIntro.textContent = "检查版本、网关、网卡、防火墙和远程通道。报告自动脱敏,不读取对话或凭据。";
770
+ const diagnosticsSummary = element("section", "dsh-mobile-control__diagnostic-summary is-idle");
771
+ diagnosticsSummary.setAttribute("aria-live", "polite");
772
+ const diagnosticsSummaryMain = element("div", "dsh-mobile-control__diagnostic-summary-main");
773
+ const diagnosticsSummaryIcon = element("span", "dsh-mobile-control__diagnostic-summary-icon");
774
+ diagnosticsSummaryIcon.setAttribute("aria-hidden", "true");
775
+ const diagnosticsSummaryBody = element("div", "dsh-mobile-control__diagnostic-summary-body");
776
+ const diagnosticsSummaryTitle = element("strong");
777
+ diagnosticsSummaryTitle.textContent = "尚未检查";
778
+ const diagnosticsSummaryText = element("span");
779
+ diagnosticsSummaryText.textContent = "点击下方按钮开始。";
780
+ const diagnosticsSummaryMeta = element("span", "dsh-mobile-control__diagnostic-summary-meta");
781
+ diagnosticsSummaryMeta.textContent = "等待运行 · 仅收集连接状态";
782
+ diagnosticsSummaryBody.append(diagnosticsSummaryTitle, diagnosticsSummaryText);
783
+ diagnosticsSummaryMain.append(diagnosticsSummaryIcon, diagnosticsSummaryBody);
784
+ diagnosticsSummary.append(diagnosticsSummaryMain, diagnosticsSummaryMeta);
785
+ const diagnosticsToolbar = element("div", "dsh-mobile-control__diagnostic-toolbar");
786
+ const diagnosticsRun = element("button", "dsh-mobile-control__primary dsh-mobile-control__diagnostic-run");
787
+ diagnosticsRun.type = "button";
788
+ diagnosticsRun.textContent = "开始检查";
789
+ const diagnosticsCopy = element("button", "dsh-mobile-control__secondary dsh-mobile-control__diagnostic-copy");
790
+ diagnosticsCopy.type = "button";
791
+ diagnosticsCopy.textContent = "复制脱敏报告";
792
+ diagnosticsCopy.disabled = true;
793
+ diagnosticsCopy.hidden = true;
794
+ diagnosticsToolbar.append(diagnosticsRun, diagnosticsCopy);
795
+ const diagnosticsFeedback = element("p", "dsh-mobile-control__diagnostic-feedback");
796
+ diagnosticsFeedback.hidden = true;
797
+ diagnosticsFeedback.setAttribute("role", "status");
798
+ diagnosticsFeedback.setAttribute("aria-live", "polite");
799
+ const diagnosticsChecks = element("div", "dsh-mobile-control__diagnostic-checks");
800
+ diagnosticsChecks.hidden = true;
801
+ const diagnosticsDetails = element("details", "dsh-mobile-control__details dsh-mobile-control__diagnostic-details");
802
+ diagnosticsDetails.hidden = true;
803
+ const diagnosticsDetailsSummary = element("summary");
804
+ diagnosticsDetailsSummary.textContent = "高级诊断详情";
805
+ const diagnosticsReport = element("pre", "dsh-mobile-control__diagnostic-report");
806
+ diagnosticsDetails.append(diagnosticsDetailsSummary, diagnosticsReport);
807
+ header.append(title, headerActions);
732
808
  actions.append(toggle, pair, linkPair);
733
809
  lanView.append(access, qrBox, status, extensionStatus, actions, manageRow, devicePanel);
734
810
  remoteView.append(remoteIntro, providerSection, cpolarSetup, tailscaleInfo, remoteAccess, remoteQr, remoteStatus, remoteGuide, remoteActions, remoteManageRow, remoteDevicePanel);
735
- panel.append(header, appDownload, switcher, lanView, remoteView);
811
+ diagnosticsView.append(diagnosticsIntro, diagnosticsSummary, diagnosticsToolbar, diagnosticsFeedback, diagnosticsChecks, diagnosticsDetails);
812
+ panel.append(header, appDownload, switcher, lanView, remoteView, diagnosticsView);
736
813
  root.append(panel);
737
814
  document.body.append(root);
738
815
  let running = false;
@@ -750,6 +827,9 @@ window.__ModuleLoader__.load({
750
827
  let cpolarConfigured = false;
751
828
  let providerInfoPinned = false;
752
829
  let providerInfoHovered = false;
830
+ let previousAccessView = "lan";
831
+ let diagnosticsBusy = false;
832
+ let copiedDiagnosticReport = "";
753
833
  const syncProviderInfo = () => {
754
834
  const open = providerInfoPinned || providerInfoHovered || providerInfo.contains(document.activeElement);
755
835
  providerInfoPopover.hidden = !open;
@@ -778,20 +858,27 @@ window.__ModuleLoader__.load({
778
858
  providerInfoPopover.hidden = true;
779
859
  providerInfoButton.setAttribute("aria-expanded", "false");
780
860
  });
781
- const selectView = (remote) => {
782
- lanView.hidden = remote;
783
- remoteView.hidden = !remote;
784
- lanTab.classList.toggle("is-active", !remote);
785
- remoteTab.classList.toggle("is-active", remote);
786
- lanTab.setAttribute("aria-pressed", String(!remote));
787
- remoteTab.setAttribute("aria-pressed", String(remote));
788
- title.textContent = remote ? "远程访问" : "局域网访问";
861
+ const selectView = (view) => {
862
+ if (view !== "diagnostics") previousAccessView = view;
863
+ lanView.hidden = view !== "lan";
864
+ remoteView.hidden = view !== "remote";
865
+ diagnosticsView.hidden = view !== "diagnostics";
866
+ lanTab.classList.toggle("is-active", view === "lan");
867
+ remoteTab.classList.toggle("is-active", view === "remote");
868
+ lanTab.setAttribute("aria-pressed", String(view === "lan"));
869
+ remoteTab.setAttribute("aria-pressed", String(view === "remote"));
870
+ diagnosticsEntry.setAttribute("aria-pressed", String(view === "diagnostics"));
871
+ diagnosticsEntry.textContent = view === "diagnostics" ? "返回" : "诊断";
872
+ diagnosticsEntry.setAttribute("aria-label", view === "diagnostics" ? "返回移动访问" : "打开连接诊断");
873
+ appDownload.hidden = view === "diagnostics";
874
+ switcher.hidden = view === "diagnostics";
875
+ title.textContent = view === "lan" ? "局域网访问" : view === "remote" ? "远程访问" : "连接诊断";
789
876
  };
790
877
  lanTab.addEventListener("click", () => {
791
- selectView(false);
878
+ selectView("lan");
792
879
  });
793
880
  remoteTab.addEventListener("click", () => {
794
- selectView(true);
881
+ selectView("remote");
795
882
  loadRemote();
796
883
  });
797
884
  const setOpen = (open) => {
@@ -984,6 +1071,7 @@ window.__ModuleLoader__.load({
984
1071
  funnel_permission_required: "登录已完成。请继续授权 Funnel,完成后会自动建立远程连接。",
985
1072
  funnel_https_required: "登录已完成。请继续授权 Funnel,官方页面会同时启用 HTTPS。",
986
1073
  funnel_start_failed: "登录已完成。请继续完成 Tailscale Funnel 的首次授权。",
1074
+ funnel_start_timeout: "Tailscale 组件启动超时,请检查网络后重新连接。",
987
1075
  tailscale_dns_missing: "Tailscale 暂未提供远程地址。请重新连接并确认已完成登录。",
988
1076
  gateway_start_failed: "远程网关启动失败。请重新连接,局域网访问不受影响。",
989
1077
  control_channel_failed: "远程组件连接中断。请重新连接。",
@@ -1012,9 +1100,14 @@ window.__ModuleLoader__.load({
1012
1100
  if (!remoteReady) remoteQr.hidden = true;
1013
1101
  if (!needsFunnelSetup) remoteSetupPending = false;
1014
1102
  };
1103
+ let remoteLoadInFlight = false;
1015
1104
  const loadRemote = () => {
1105
+ if (remoteLoadInFlight) return;
1106
+ remoteLoadInFlight = true;
1016
1107
  requestJson("/api/mobile-access/remote/control").then(renderRemote, (error) => {
1017
1108
  remoteStatus.textContent = String(error);
1109
+ }).finally(() => {
1110
+ remoteLoadInFlight = false;
1018
1111
  });
1019
1112
  };
1020
1113
  const chooseRemoteProvider = (provider) => {
@@ -1050,7 +1143,7 @@ window.__ModuleLoader__.load({
1050
1143
  requestJson("/api/mobile-access/remote/cpolar/component/install", {
1051
1144
  method: "POST",
1052
1145
  body: JSON.stringify({ confirm: true })
1053
- }).then(renderRemote, (error) => {
1146
+ }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
1054
1147
  remoteStatus.textContent = `组件安装失败:${String(error)}`;
1055
1148
  }).finally(() => {
1056
1149
  remoteProviderBusy = false;
@@ -1072,7 +1165,7 @@ window.__ModuleLoader__.load({
1072
1165
  requestJson("/api/mobile-access/remote/cpolar/configure", {
1073
1166
  method: "POST",
1074
1167
  body: JSON.stringify({ authtoken })
1075
- }).then(() => {
1168
+ }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(() => {
1076
1169
  cpolarToken.value = "";
1077
1170
  remoteStatus.textContent = "账号配置已保存,正在建立 cpolar 远程通道…";
1078
1171
  return requestJson("/api/mobile-access/remote/control", {
@@ -1097,7 +1190,7 @@ window.__ModuleLoader__.load({
1097
1190
  requestJson("/api/mobile-access/remote/cpolar/component/purge", {
1098
1191
  method: "POST",
1099
1192
  body: JSON.stringify({ confirm: true })
1100
- }).then(renderRemote, (error) => {
1193
+ }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
1101
1194
  remoteStatus.textContent = `清理失败:${String(error)}`;
1102
1195
  }).finally(() => {
1103
1196
  remoteProviderBusy = false;
@@ -1224,6 +1317,120 @@ window.__ModuleLoader__.load({
1224
1317
  remoteStatus.textContent = String(error);
1225
1318
  });
1226
1319
  });
1320
+ const renderDiagnostics = (data) => {
1321
+ const overall = data.overall === "error" ? "error" : data.overall === "attention" ? "attention" : "ok";
1322
+ diagnosticsSummary.className = `dsh-mobile-control__diagnostic-summary is-${overall}`;
1323
+ diagnosticsSummaryTitle.textContent = overall === "ok" ? "检查完成" : overall === "attention" ? "有项目需要留意" : "发现连接问题";
1324
+ diagnosticsSummaryText.textContent = typeof data.summary === "string" ? data.summary : "检查已完成。";
1325
+ const entries = Array.isArray(data.checks) ? data.checks : [];
1326
+ diagnosticsChecks.replaceChildren();
1327
+ const statusLabels = {
1328
+ ok: "正常",
1329
+ warning: "注意",
1330
+ error: "问题",
1331
+ info: "说明"
1332
+ };
1333
+ const statusOf = (entry) => entry.status === "ok" || entry.status === "warning" || entry.status === "error" ? entry.status : "info";
1334
+ const appendGroup = (label, groupEntries) => {
1335
+ if (groupEntries.length === 0) return;
1336
+ const group = element("section", "dsh-mobile-control__diagnostic-group");
1337
+ const groupHeader = element("header", "dsh-mobile-control__diagnostic-group-header");
1338
+ const groupTitle = element("h3");
1339
+ groupTitle.textContent = label;
1340
+ const groupCount = element("span");
1341
+ groupCount.textContent = `${String(groupEntries.length)} 项`;
1342
+ const list = element("div", "dsh-mobile-control__diagnostic-list");
1343
+ list.setAttribute("role", "list");
1344
+ groupHeader.append(groupTitle, groupCount);
1345
+ for (const entry of groupEntries) {
1346
+ const state = statusOf(entry);
1347
+ const row = element("section", `dsh-mobile-control__diagnostic-check is-${state}`);
1348
+ row.setAttribute("role", "listitem");
1349
+ const marker = element("span", "dsh-mobile-control__diagnostic-marker");
1350
+ marker.setAttribute("aria-hidden", "true");
1351
+ const rowBody = element("div", "dsh-mobile-control__diagnostic-check-body");
1352
+ const rowHeader = element("div", "dsh-mobile-control__diagnostic-check-header");
1353
+ const rowTitle = element("strong");
1354
+ rowTitle.textContent = typeof entry.label === "string" ? entry.label : "检查项";
1355
+ const badge = element("span", "dsh-mobile-control__diagnostic-badge");
1356
+ badge.textContent = statusLabels[state];
1357
+ const detail = element("p");
1358
+ detail.textContent = typeof entry.detail === "string" ? entry.detail : "";
1359
+ rowHeader.append(rowTitle, badge);
1360
+ rowBody.append(rowHeader, detail);
1361
+ if (typeof entry.action === "string" && entry.action !== "") {
1362
+ const action = element("p", "dsh-mobile-control__diagnostic-action");
1363
+ const actionLabel = element("span");
1364
+ actionLabel.textContent = "建议";
1365
+ action.append(actionLabel, document.createTextNode(entry.action));
1366
+ rowBody.append(action);
1367
+ }
1368
+ row.append(marker, rowBody);
1369
+ list.append(row);
1370
+ }
1371
+ group.append(groupHeader, list);
1372
+ diagnosticsChecks.append(group);
1373
+ };
1374
+ const issues = entries.filter((entry) => statusOf(entry) === "error" || statusOf(entry) === "warning");
1375
+ const otherChecks = entries.filter((entry) => statusOf(entry) !== "error" && statusOf(entry) !== "warning");
1376
+ appendGroup("需要处理", issues);
1377
+ appendGroup(issues.length === 0 ? "检查详情" : "其他检查", otherChecks);
1378
+ diagnosticsSummaryMeta.textContent = issues.length === 0 ? `${String(entries.length)} 项检查 · 未发现阻断问题` : `${String(entries.length)} 项检查 · ${String(issues.length)} 项需要处理`;
1379
+ diagnosticsChecks.hidden = entries.length === 0;
1380
+ copiedDiagnosticReport = typeof data.report === "string" ? data.report : "";
1381
+ diagnosticsReport.textContent = copiedDiagnosticReport;
1382
+ diagnosticsDetails.hidden = copiedDiagnosticReport === "";
1383
+ diagnosticsCopy.disabled = copiedDiagnosticReport === "";
1384
+ diagnosticsCopy.hidden = copiedDiagnosticReport === "";
1385
+ diagnosticsToolbar.classList.toggle("has-report", copiedDiagnosticReport !== "");
1386
+ };
1387
+ const loadDiagnostics = () => {
1388
+ if (diagnosticsBusy) return;
1389
+ diagnosticsBusy = true;
1390
+ diagnosticsRun.disabled = true;
1391
+ diagnosticsRun.setAttribute("aria-busy", "true");
1392
+ diagnosticsRun.textContent = "正在检查…";
1393
+ diagnosticsSummary.className = "dsh-mobile-control__diagnostic-summary is-running";
1394
+ diagnosticsSummaryTitle.textContent = "正在检查连接";
1395
+ diagnosticsSummaryText.textContent = "通常几秒内完成。远程通道会执行一次真实可达性测试。";
1396
+ diagnosticsSummaryMeta.textContent = "正在运行 · 请保持 DSH 在线";
1397
+ diagnosticsFeedback.hidden = true;
1398
+ diagnosticsChecks.classList.add("is-refreshing");
1399
+ diagnosticsChecks.setAttribute("aria-busy", "true");
1400
+ requestJson("/api/mobile-access/diagnostics").then(renderDiagnostics, (error) => {
1401
+ diagnosticsSummary.className = "dsh-mobile-control__diagnostic-summary is-error";
1402
+ diagnosticsSummaryTitle.textContent = "检查未完成";
1403
+ diagnosticsSummaryText.textContent = `无法读取诊断结果:${String(error)}`;
1404
+ diagnosticsSummaryMeta.textContent = "诊断服务暂不可用 · 请稍后重试";
1405
+ }).finally(() => {
1406
+ diagnosticsBusy = false;
1407
+ diagnosticsRun.disabled = false;
1408
+ diagnosticsRun.setAttribute("aria-busy", "false");
1409
+ diagnosticsRun.textContent = "重新检查";
1410
+ diagnosticsChecks.classList.remove("is-refreshing");
1411
+ diagnosticsChecks.setAttribute("aria-busy", "false");
1412
+ });
1413
+ };
1414
+ diagnosticsEntry.addEventListener("click", () => {
1415
+ if (!diagnosticsView.hidden) {
1416
+ selectView(previousAccessView);
1417
+ return;
1418
+ }
1419
+ selectView("diagnostics");
1420
+ loadDiagnostics();
1421
+ });
1422
+ diagnosticsRun.addEventListener("click", loadDiagnostics);
1423
+ diagnosticsCopy.addEventListener("click", () => {
1424
+ if (copiedDiagnosticReport === "") return;
1425
+ navigator.clipboard.writeText(copiedDiagnosticReport).then(() => {
1426
+ diagnosticsFeedback.textContent = "脱敏报告已复制,可直接粘贴到 Issue。";
1427
+ diagnosticsFeedback.hidden = false;
1428
+ }, () => {
1429
+ diagnosticsDetails.open = true;
1430
+ diagnosticsFeedback.textContent = "浏览器未允许复制,已展开详情,请手动复制。";
1431
+ diagnosticsFeedback.hidden = false;
1432
+ });
1433
+ });
1227
1434
  pair.addEventListener("click", () => {
1228
1435
  pair.disabled = true;
1229
1436
  openPairing("key");
@@ -1296,6 +1503,9 @@ window.__ModuleLoader__.load({
1296
1503
  const styleNodes = /* @__PURE__ */ new Map();
1297
1504
  const styleEtags = /* @__PURE__ */ new Map();
1298
1505
  const scriptDigests = /* @__PURE__ */ new Map();
1506
+ const manifestExtensionIds = /* @__PURE__ */ new Set();
1507
+ const managedDefinitionIds = /* @__PURE__ */ new Set();
1508
+ const activationQueues = /* @__PURE__ */ new Map();
1299
1509
  let manifestEtag = "";
1300
1510
  let expectedDefinitionId;
1301
1511
  const SURFACE_HOST_STYLES = {
@@ -1435,7 +1645,7 @@ window.__ModuleLoader__.load({
1435
1645
  window
1436
1646
  };
1437
1647
  };
1438
- const activateDefinition = async (definition) => {
1648
+ const activateDefinitionNow = async (definition) => {
1439
1649
  const previousActive = active.get(definition.id);
1440
1650
  const controller = new AbortController();
1441
1651
  const surfaces = /* @__PURE__ */ new Map();
@@ -1453,7 +1663,7 @@ window.__ModuleLoader__.load({
1453
1663
  const current = active.get(definition.id);
1454
1664
  if (current === void 0 || current.controller !== controller) {
1455
1665
  if (typeof cleanup === "function") cleanup();
1456
- return;
1666
+ return false;
1457
1667
  }
1458
1668
  const oldDispose = current.dispose;
1459
1669
  active.set(definition.id, {
@@ -1464,17 +1674,30 @@ window.__ModuleLoader__.load({
1464
1674
  }
1465
1675
  });
1466
1676
  previousActive?.dispose();
1677
+ return true;
1467
1678
  } catch {
1468
- active.get(definition.id)?.dispose();
1469
- active.delete(definition.id);
1470
- if (previousActive !== void 0) active.set(definition.id, previousActive);
1679
+ const current = active.get(definition.id);
1680
+ if (current?.controller === controller) {
1681
+ current.dispose();
1682
+ active.delete(definition.id);
1683
+ if (previousActive !== void 0) active.set(definition.id, previousActive);
1684
+ }
1685
+ return false;
1471
1686
  }
1472
1687
  };
1688
+ const activateDefinition = (definition) => {
1689
+ const task = (activationQueues.get(definition.id) ?? Promise.resolve(true)).then(() => definitions.get(definition.id) === definition ? activateDefinitionNow(definition) : false, () => definitions.get(definition.id) === definition ? activateDefinitionNow(definition) : false);
1690
+ activationQueues.set(definition.id, task);
1691
+ task.finally(() => {
1692
+ if (activationQueues.get(definition.id) === task) activationQueues.delete(definition.id);
1693
+ });
1694
+ return task;
1695
+ };
1473
1696
  const define = (definition) => {
1474
1697
  if (definition.apiVersion !== 1 || !/^[a-z][a-z0-9-]{0,63}$/u.test(definition.id) || typeof definition.activate !== "function") return;
1475
1698
  if (expectedDefinitionId !== void 0 && definition.id !== expectedDefinitionId) return;
1476
1699
  definitions.set(definition.id, definition);
1477
- if (started) activateDefinition(definition);
1700
+ if (started && expectedDefinitionId === void 0) activateDefinition(definition);
1478
1701
  };
1479
1702
  let started = false;
1480
1703
  window.dshMobile = Object.freeze({
@@ -1486,7 +1709,9 @@ window.__ModuleLoader__.load({
1486
1709
  for (const definition of queuedDefinitions.splice(0)) define(definition);
1487
1710
  let legacyJsEtag = "";
1488
1711
  let legacyJsModified = "";
1489
- const refreshLegacy = async () => {
1712
+ const refreshLegacy = async (signal) => {
1713
+ const previousMount = legacyMount;
1714
+ let pendingRoot;
1490
1715
  try {
1491
1716
  const headers = {};
1492
1717
  if (legacyJsEtag !== "") headers["if-none-match"] = legacyJsEtag;
@@ -1494,23 +1719,27 @@ window.__ModuleLoader__.load({
1494
1719
  const response = await fetch("/mobile-access/custom.js", {
1495
1720
  credentials: "same-origin",
1496
1721
  cache: "no-store",
1497
- headers
1722
+ headers,
1723
+ ...signal === void 0 ? {} : { signal }
1498
1724
  });
1499
- if (response.status === 304) return;
1500
- if (!response.ok) return;
1725
+ if (response.status === 304) return true;
1726
+ if (!response.ok) return false;
1501
1727
  legacyJsEtag = response.headers.get("etag") ?? "";
1502
1728
  legacyJsModified = response.headers.get("last-modified") ?? "";
1503
1729
  const next = await response.text();
1504
- if (next === legacySource) return;
1505
- legacySource = next;
1730
+ if (next === legacySource) return true;
1506
1731
  legacyMount = void 0;
1507
1732
  const script = element("script");
1508
1733
  script.textContent = `${next}\n//# sourceURL=dsh-mobile-custom.js`;
1509
1734
  document.head.append(script);
1510
1735
  script.remove();
1511
1736
  const mount = legacyMount;
1512
- if (mount === void 0) return;
1737
+ if (mount === void 0) {
1738
+ legacySource = next;
1739
+ return true;
1740
+ }
1513
1741
  const nextRoot = element("div");
1742
+ pendingRoot = nextRoot;
1514
1743
  nextRoot.dataset.dshMobileExtension = "true";
1515
1744
  document.body.append(nextRoot);
1516
1745
  const nextDispose = mount({
@@ -1522,105 +1751,188 @@ window.__ModuleLoader__.load({
1522
1751
  legacyDispose?.();
1523
1752
  legacyRoot?.remove();
1524
1753
  legacyRoot = nextRoot;
1754
+ pendingRoot = void 0;
1525
1755
  legacyDispose = typeof nextDispose === "function" ? nextDispose : void 0;
1526
- } catch {}
1756
+ legacySource = next;
1757
+ return true;
1758
+ } catch {
1759
+ pendingRoot?.remove();
1760
+ legacyMount = previousMount;
1761
+ return false;
1762
+ }
1527
1763
  };
1528
- const refreshExtensions = async () => {
1764
+ let legacyScriptRevision = "";
1765
+ let legacyStyleRevision = "";
1766
+ const refreshExtensions = async (signal) => {
1529
1767
  try {
1530
1768
  const headers = {};
1531
1769
  if (manifestEtag !== "") headers["if-none-match"] = manifestEtag;
1532
1770
  const response = await fetch("/mobile-access/extensions/manifest", {
1533
1771
  credentials: "same-origin",
1534
1772
  cache: "no-store",
1535
- headers
1773
+ headers,
1774
+ ...signal === void 0 ? {} : { signal }
1536
1775
  });
1537
- if (response.status === 304) return;
1538
- if (!response.ok) return;
1539
- manifestEtag = response.headers.get("etag") ?? "";
1776
+ if (response.status === 404) return (await Promise.all([refreshLegacy(signal), refreshCssLegacy(legacyStyle, signal)])).every(Boolean);
1777
+ if (response.status === 304) return true;
1778
+ if (!response.ok) return false;
1779
+ const nextManifestEtag = response.headers.get("etag") ?? "";
1540
1780
  const payload = await response.json();
1781
+ const scriptRevision = typeof payload.legacy?.scriptRevision === "string" ? payload.legacy.scriptRevision : "";
1782
+ const styleRevision = typeof payload.legacy?.styleRevision === "string" ? payload.legacy.styleRevision : "";
1783
+ let refreshComplete = true;
1784
+ if (scriptRevision === "" || scriptRevision !== legacyScriptRevision) {
1785
+ if (await refreshLegacy(signal)) legacyScriptRevision = scriptRevision;
1786
+ else refreshComplete = false;
1787
+ }
1788
+ if (styleRevision === "" || styleRevision !== legacyStyleRevision) {
1789
+ if (await refreshCssLegacy(legacyStyle, signal)) legacyStyleRevision = styleRevision;
1790
+ else refreshComplete = false;
1791
+ }
1541
1792
  const entries = Array.isArray(payload.extensions) ? payload.extensions : [];
1542
1793
  const seen = /* @__PURE__ */ new Set();
1543
1794
  for (const entry of entries) {
1544
1795
  if (typeof entry.id !== "string") continue;
1545
1796
  seen.add(entry.id);
1546
- const cssUrl = typeof entry.styleUrl === "string" ? entry.styleUrl : void 0;
1547
- if (cssUrl !== void 0) {
1548
- const cssHeaders = {};
1549
- const storedEtag = styleEtags.get(entry.id);
1550
- if (storedEtag !== void 0) cssHeaders["if-none-match"] = storedEtag;
1551
- const cssResponse = await fetch(cssUrl, {
1552
- credentials: "same-origin",
1553
- cache: "no-store",
1554
- headers: cssHeaders
1555
- });
1556
- if (cssResponse.status === 304) {} else if (cssResponse.ok) {
1557
- const nextEtag = cssResponse.headers.get("etag");
1558
- if (nextEtag !== null && nextEtag !== "") styleEtags.set(entry.id, nextEtag);
1559
- const css = await cssResponse.text();
1797
+ try {
1798
+ let pendingCss;
1799
+ let pendingStyleEtag;
1800
+ const cssUrl = typeof entry.styleUrl === "string" ? entry.styleUrl : void 0;
1801
+ if (cssUrl !== void 0) {
1802
+ const cssHeaders = {};
1803
+ const storedEtag = styleEtags.get(entry.id);
1804
+ if (storedEtag !== void 0) cssHeaders["if-none-match"] = storedEtag;
1805
+ const cssResponse = await fetch(cssUrl, {
1806
+ credentials: "same-origin",
1807
+ cache: "no-store",
1808
+ headers: cssHeaders,
1809
+ ...signal === void 0 ? {} : { signal }
1810
+ });
1811
+ if (cssResponse.status !== 304) {
1812
+ if (!cssResponse.ok) throw new Error("mobile extension style failed to load");
1813
+ pendingStyleEtag = cssResponse.headers.get("etag") ?? void 0;
1814
+ pendingCss = await cssResponse.text();
1815
+ }
1816
+ }
1817
+ const scriptUrl = typeof entry.scriptUrl === "string" ? entry.scriptUrl : void 0;
1818
+ if (scriptUrl !== void 0) {
1819
+ const scriptHeaders = {};
1820
+ const storedDigest = scriptDigests.get(entry.id);
1821
+ if (storedDigest !== void 0) scriptHeaders["if-none-match"] = storedDigest;
1822
+ const scriptResponse = await fetch(scriptUrl, {
1823
+ credentials: "same-origin",
1824
+ cache: "no-store",
1825
+ headers: scriptHeaders,
1826
+ ...signal === void 0 ? {} : { signal }
1827
+ });
1828
+ if (scriptResponse.status !== 304) {
1829
+ if (!scriptResponse.ok) throw new Error("mobile extension script failed to load");
1830
+ const source = await scriptResponse.text();
1831
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(source));
1832
+ const key = [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
1833
+ if (scriptDigests.get(entry.id) !== key) {
1834
+ const previousDefinition = definitions.get(entry.id);
1835
+ try {
1836
+ expectedDefinitionId = entry.id;
1837
+ try {
1838
+ const script = element("script");
1839
+ script.textContent = `${source}\n//# sourceURL=dsh-mobile-extension-${entry.id}.js`;
1840
+ document.head.append(script);
1841
+ script.remove();
1842
+ } finally {
1843
+ expectedDefinitionId = void 0;
1844
+ }
1845
+ const nextDefinition = definitions.get(entry.id);
1846
+ if (nextDefinition === void 0 || nextDefinition === previousDefinition) throw new Error("mobile extension did not define its manifest id");
1847
+ if (!await activateDefinition(nextDefinition)) throw new Error("mobile extension activation failed");
1848
+ managedDefinitionIds.add(entry.id);
1849
+ scriptDigests.set(entry.id, key);
1850
+ } catch (error) {
1851
+ if (previousDefinition === void 0) definitions.delete(entry.id);
1852
+ else definitions.set(entry.id, previousDefinition);
1853
+ throw error;
1854
+ }
1855
+ }
1856
+ }
1857
+ const definition = definitions.get(entry.id);
1858
+ if (definition !== void 0 && !active.has(entry.id) && !await activateDefinition(definition)) throw new Error("mobile extension activation failed");
1859
+ }
1860
+ if (pendingCss !== void 0) {
1560
1861
  const oldStyle = styleNodes.get(entry.id);
1561
- if (oldStyle?.textContent !== css) {
1862
+ if (oldStyle?.textContent !== pendingCss) {
1562
1863
  const node = element("style");
1563
1864
  node.dataset.dshMobileExtensionStyle = entry.id;
1564
- node.textContent = css;
1865
+ node.textContent = pendingCss;
1565
1866
  document.head.append(node);
1566
1867
  styleNodes.set(entry.id, node);
1567
1868
  oldStyle?.remove();
1568
1869
  }
1569
- } else continue;
1570
- }
1571
- const scriptUrl = typeof entry.scriptUrl === "string" ? entry.scriptUrl : void 0;
1572
- if (scriptUrl !== void 0) {
1573
- const scriptHeaders = {};
1574
- const storedDigest = scriptDigests.get(entry.id);
1575
- if (storedDigest !== void 0) scriptHeaders["if-none-match"] = storedDigest;
1576
- const scriptResponse = await fetch(scriptUrl, {
1577
- credentials: "same-origin",
1578
- cache: "no-store",
1579
- headers: scriptHeaders
1580
- });
1581
- if (scriptResponse.status === 304) {} else if (scriptResponse.ok) {
1582
- const source = await scriptResponse.text();
1583
- const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(source));
1584
- const key = [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
1585
- if (scriptDigests.get(entry.id) !== key) {
1586
- scriptDigests.set(entry.id, key);
1587
- expectedDefinitionId = entry.id;
1588
- try {
1589
- const script = element("script");
1590
- script.textContent = `${source}\n//# sourceURL=dsh-mobile-extension-${entry.id}.js`;
1591
- document.head.append(script);
1592
- script.remove();
1593
- } finally {
1594
- expectedDefinitionId = void 0;
1595
- }
1596
- }
1597
- } else continue;
1870
+ if (pendingStyleEtag !== void 0 && pendingStyleEtag !== "") styleEtags.set(entry.id, pendingStyleEtag);
1871
+ }
1872
+ } catch {
1873
+ refreshComplete = false;
1598
1874
  }
1599
- const definition = definitions.get(entry.id);
1600
- if (definition !== void 0 && !active.has(entry.id)) await activateDefinition(definition);
1601
1875
  }
1602
- for (const [id, current] of active) if (!seen.has(id) && !definitions.has(id) && id !== "legacy-custom") {
1603
- current.dispose();
1876
+ for (const id of manifestExtensionIds) {
1877
+ if (seen.has(id)) continue;
1878
+ active.get(id)?.dispose();
1604
1879
  active.delete(id);
1605
1880
  styleNodes.get(id)?.remove();
1606
1881
  styleNodes.delete(id);
1607
1882
  styleEtags.delete(id);
1608
1883
  scriptDigests.delete(id);
1884
+ if (managedDefinitionIds.delete(id)) definitions.delete(id);
1609
1885
  }
1610
- } catch {}
1886
+ manifestExtensionIds.clear();
1887
+ for (const id of seen) manifestExtensionIds.add(id);
1888
+ manifestEtag = refreshComplete ? nextManifestEtag : "";
1889
+ return refreshComplete;
1890
+ } catch {
1891
+ return false;
1892
+ }
1611
1893
  };
1612
1894
  started = true;
1613
1895
  for (const definition of definitions.values()) activateDefinition(definition);
1614
- refreshLegacy();
1615
- refreshExtensions();
1616
- refreshCssLegacy(legacyStyle);
1617
- const timer = window.setInterval(() => {
1618
- refreshLegacy();
1619
- refreshExtensions();
1620
- refreshCssLegacy(legacyStyle);
1621
- }, 3e3);
1896
+ const remoteOrigin = window.location.hostname.endsWith(".ts.net") || window.location.hostname.includes(".cpolar.");
1897
+ const refreshIntervalMs = remoteOrigin ? 15e3 : 3e3;
1898
+ const refreshTimeoutMs = remoteOrigin ? 2e4 : 8e3;
1899
+ let stopped = false;
1900
+ let refreshTimer;
1901
+ let refreshFailures = 0;
1902
+ let refreshController;
1903
+ const scheduleRefresh = (delayMs) => {
1904
+ if (stopped || document.visibilityState === "hidden" || !navigator.onLine) return;
1905
+ if (refreshTimer !== void 0) clearTimeout(refreshTimer);
1906
+ refreshTimer = window.setTimeout(runRefresh, delayMs);
1907
+ };
1908
+ const runRefresh = () => {
1909
+ if (stopped || refreshController !== void 0) return;
1910
+ refreshController = new AbortController();
1911
+ const controller = refreshController;
1912
+ const timeout = window.setTimeout(() => {
1913
+ controller.abort();
1914
+ }, refreshTimeoutMs);
1915
+ refreshExtensions(controller.signal).then((success) => {
1916
+ refreshFailures = success ? 0 : refreshFailures + 1;
1917
+ }).finally(() => {
1918
+ clearTimeout(timeout);
1919
+ if (refreshController === controller) refreshController = void 0;
1920
+ const backoff = Math.min(6e4, refreshIntervalMs * 2 ** Math.min(refreshFailures, 3));
1921
+ scheduleRefresh(backoff);
1922
+ });
1923
+ };
1924
+ const resumeRefresh = () => {
1925
+ if (document.visibilityState !== "hidden" && navigator.onLine) scheduleRefresh(0);
1926
+ };
1927
+ document.addEventListener("visibilitychange", resumeRefresh);
1928
+ window.addEventListener("online", resumeRefresh);
1929
+ scheduleRefresh(0);
1622
1930
  return () => {
1623
- clearInterval(timer);
1931
+ stopped = true;
1932
+ if (refreshTimer !== void 0) clearTimeout(refreshTimer);
1933
+ refreshController?.abort();
1934
+ document.removeEventListener("visibilitychange", resumeRefresh);
1935
+ window.removeEventListener("online", resumeRefresh);
1624
1936
  started = false;
1625
1937
  legacyDispose?.();
1626
1938
  legacyRoot?.remove();
@@ -1635,7 +1947,7 @@ window.__ModuleLoader__.load({
1635
1947
  }
1636
1948
  let cssEtag = "";
1637
1949
  let cssModified = "";
1638
- async function refreshCssLegacy(style) {
1950
+ async function refreshCssLegacy(style, signal) {
1639
1951
  try {
1640
1952
  const headers = {};
1641
1953
  if (cssEtag !== "") headers["if-none-match"] = cssEtag;
@@ -1643,33 +1955,44 @@ window.__ModuleLoader__.load({
1643
1955
  const response = await fetch("/mobile-access/custom.css", {
1644
1956
  credentials: "same-origin",
1645
1957
  cache: "no-store",
1646
- headers
1958
+ headers,
1959
+ ...signal === void 0 ? {} : { signal }
1647
1960
  });
1648
- if (response.status === 304) return;
1961
+ if (response.status === 304) return true;
1649
1962
  if (response.ok) {
1650
1963
  cssEtag = response.headers.get("etag") ?? "";
1651
1964
  cssModified = response.headers.get("last-modified") ?? "";
1652
1965
  style.textContent = await response.text();
1966
+ return true;
1653
1967
  }
1654
- } catch {}
1968
+ return false;
1969
+ } catch {
1970
+ return false;
1971
+ }
1655
1972
  }
1656
1973
  const CONTROL_STYLES = `
1657
1974
  .dsh-mobile-control{position:fixed;z-index:1000;left:16px;bottom:112px;font:14px/1.45 system-ui;color:var(--dsw-alias-label-primary,#16181d)}
1658
1975
  .dsh-mobile-control__panel{box-sizing:border-box;width:min(380px,calc(100vw - 32px));max-height:calc(100vh - 140px);overflow-y:auto;padding:16px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:18px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 18px 50px rgb(15 23 42 / 18%)}
1659
- .dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;font-size:24px;line-height:1;cursor:pointer}.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
1660
- .dsh-mobile-control__app-download{display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;min-height:38px;margin:0 0 10px;padding:8px 11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f7f8fa);color:var(--dsw-alias-label-primary,#16181d);font:600 12px/1.3 system-ui;text-decoration:none}.dsh-mobile-control__app-download::after{color:#2563eb;font-size:14px;content:"↗"}.dsh-mobile-control__app-download:hover{border-color:#9fb9e8;background:#f5f8ff;color:#1d4ed8}
1661
- .dsh-mobile-control__switcher{display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:0 0 14px;padding:4px;border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f3f5f8)}.dsh-mobile-control__tab{min-height:36px;border:0;border-radius:9px;background:transparent;color:var(--dsw-alias-label-secondary,#606873);font:600 13px/1 system-ui;cursor:pointer}.dsh-mobile-control__tab.is-active{background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);box-shadow:0 1px 3px rgb(15 23 42 / 10%)}.dsh-mobile-control__view[hidden]{display:none}.dsh-mobile-control__intro{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px;line-height:1.55}.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions button[hidden]{display:none}
1976
+ .dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__header-actions{display:flex;align-items:center;gap:2px}.dsh-mobile-control__diagnostic-entry,.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:44px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;cursor:pointer}.dsh-mobile-control__diagnostic-entry{padding:0 9px;color:#2563eb;font:650 12px/1 system-ui}.dsh-mobile-control__close{font-size:24px;line-height:1}.dsh-mobile-control__diagnostic-entry:hover,.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
1977
+ .dsh-mobile-control__app-download{display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;min-height:38px;margin:0 0 10px;padding:8px 11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f7f8fa);color:var(--dsw-alias-label-primary,#16181d);font:600 12px/1.3 system-ui;text-decoration:none}.dsh-mobile-control__app-download[hidden]{display:none}.dsh-mobile-control__app-download::after{color:#2563eb;font-size:14px;content:"↗"}.dsh-mobile-control__app-download:hover{border-color:#9fb9e8;background:#f5f8ff;color:#1d4ed8}
1978
+ .dsh-mobile-control__switcher{display:grid;grid-template-columns:repeat(2,1fr);gap:4px;margin:0 0 14px;padding:4px;border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f3f5f8)}.dsh-mobile-control__switcher[hidden]{display:none}.dsh-mobile-control__tab{min-height:36px;border:0;border-radius:9px;background:transparent;color:var(--dsw-alias-label-secondary,#606873);font:600 13px/1 system-ui;cursor:pointer}.dsh-mobile-control__tab.is-active{background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);box-shadow:0 1px 3px rgb(15 23 42 / 10%)}.dsh-mobile-control__view[hidden]{display:none}.dsh-mobile-control__intro{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px;line-height:1.55}.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions button[hidden]{display:none}
1662
1979
  .dsh-mobile-control__provider-section{position:relative;margin:0 0 12px}.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;gap:8px}.dsh-mobile-control__provider{display:flex;flex-direction:column;gap:5px;min-height:68px;padding:11px 12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:#fff;color:inherit;text-align:left;cursor:pointer;transition:border-color 160ms ease,background-color 160ms ease,box-shadow 160ms ease}.dsh-mobile-control__provider:hover{border-color:#9fb9e8;background:#f8fbff}.dsh-mobile-control__provider.is-selected{border-color:#2563eb;background:#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;align-items:center;justify-content:space-between;gap:8px}.dsh-mobile-control__provider-top strong{font-size:13px}.dsh-mobile-control__provider-badge{flex:none;padding:3px 7px;border-radius:999px;background:#e8f0ff;color:#1d4ed8;font:650 10px/1.2 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:11px;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:#475569;cursor:pointer;touch-action:manipulation}.dsh-mobile-control__provider-info-button:hover{background:#f1f5f9;color:#1d4ed8}.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}
1663
1980
  .dsh-mobile-control__cpolar-setup{margin:0 0 12px;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:#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:#fff;color:inherit;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:#f3f5f8;color:#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:#b91c1c;font:12px/1.3 system-ui;cursor:pointer}
1664
1981
  .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}
1665
1982
  .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}
1666
1983
  .dsh-mobile-control__guide{margin:0 0 14px;padding:12px;border:1px solid #bfdbfe;border-radius:12px;background:#eff6ff}.dsh-mobile-control__guide[hidden]{display:none}.dsh-mobile-control__guide-title{margin:0;color:#172554;font:650 13px/1.45 system-ui}.dsh-mobile-control__guide-summary,.dsh-mobile-control__guide-note{margin:4px 0 0;color:#475569;font-size:12px;line-height:1.5}.dsh-mobile-control__guide-steps{margin:8px 0 0;padding-left:20px;color:#1e293b;font-size:12px;line-height:1.6}.dsh-mobile-control__guide-note{color:#64748b}.dsh-mobile-control__guide-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}.dsh-mobile-control__guide-actions button{min-width:0;min-height:44px;padding:8px;border-radius:10px;font:12px/1.25 system-ui;cursor:pointer}.dsh-mobile-control__guide-actions button:disabled{cursor:not-allowed;opacity:.45}
1667
1984
  .dsh-mobile-control__extensions{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px}
1985
+ .dsh-mobile-control__view.is-diagnostics{--dsh-diagnostic-ok:#087454;--dsh-diagnostic-warning:#a35b00;--dsh-diagnostic-error:#c62828;--dsh-diagnostic-info:#526071}.dsh-mobile-control__diagnostic-summary{box-sizing:border-box;margin:0;padding:13px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:16px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-summary-main{display:grid;grid-template-columns:36px minmax(0,1fr);align-items:center;gap:11px}.dsh-mobile-control__diagnostic-summary-icon{position:relative;display:block;width:36px;height:36px;border-radius:50%;background:#e8edf3;color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-summary-icon::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-summary-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__diagnostic-summary-body strong{font-size:13px;line-height:1.35}.dsh-mobile-control__diagnostic-summary-body span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__diagnostic-summary-meta{display:block;margin-top:11px;padding-top:9px;border-top:1px solid var(--dsw-alias-border-subtle,#dbe1e8);color:var(--dsw-alias-label-secondary,#606873);font-size:10px;line-height:1.45}.dsh-mobile-control__diagnostic-summary.is-ok .dsh-mobile-control__diagnostic-summary-icon{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-summary.is-ok .dsh-mobile-control__diagnostic-summary-icon::before{top:10px;left:10px;width:13px;height:7px;border-bottom:2px solid currentColor;border-left:2px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon::before{top:8px;left:17px;width:2px;height:13px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon::after,.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon::after{bottom:8px;left:17px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon{background:#e8f0ff;color:#2563eb}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before{inset:9px;border:2px solid rgb(37 99 235 / 24%);border-top-color:currentColor;border-radius:50%;animation:dsh-diagnostic-spin .8s linear infinite}
1986
+ .dsh-mobile-control__diagnostic-summary.is-idle .dsh-mobile-control__diagnostic-summary-icon::before{top:8px;left:17px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-idle .dsh-mobile-control__diagnostic-summary-icon::after{top:13px;left:17px;width:2px;height:11px;border-radius:2px;background:currentColor}
1987
+ .dsh-mobile-control__diagnostic-toolbar{display:grid;grid-template-columns:1fr;gap:8px;margin-top:10px}.dsh-mobile-control__diagnostic-toolbar.has-report{grid-template-columns:1fr 1fr}.dsh-mobile-control__diagnostic-run,.dsh-mobile-control__diagnostic-copy{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border-radius:11px;font:650 12px/1.3 system-ui;cursor:pointer;touch-action:manipulation}.dsh-mobile-control__diagnostic-copy[hidden]{display:none}.dsh-mobile-control__diagnostic-run:disabled{cursor:wait;opacity:.58}.dsh-mobile-control__diagnostic-feedback{margin:8px 0 0;padding:8px 10px;border-radius:9px;background:#eff6ff;color:#1d4ed8;font-size:11px;line-height:1.45}.dsh-mobile-control__diagnostic-feedback[hidden]{display:none}
1988
+ .dsh-mobile-control__diagnostic-checks{display:grid;gap:12px;margin-top:12px;animation:dsh-diagnostic-reveal 160ms ease-out both}.dsh-mobile-control__diagnostic-checks[hidden]{display:none}.dsh-mobile-control__diagnostic-group{overflow:hidden;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-group-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 11px;border-bottom:1px solid var(--dsw-alias-border-subtle,#e1e5eb);background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-group-header h3{margin:0;font:650 11px/1.4 system-ui}.dsh-mobile-control__diagnostic-group-header span{color:var(--dsw-alias-label-secondary,#606873);font-size:10px}.dsh-mobile-control__diagnostic-list{display:flex;flex-direction:column}.dsh-mobile-control__diagnostic-check{display:grid;grid-template-columns:26px minmax(0,1fr);gap:9px;padding:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-check + .dsh-mobile-control__diagnostic-check{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__diagnostic-marker{position:relative;width:26px;height:26px;border-radius:50%;background:#edf1f5;color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-marker::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker::before{top:7px;left:7px;width:9px;height:5px;border-bottom:1.8px solid currentColor;border-left:1.8px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::after,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::after{bottom:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::after{top:10px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check-body{min-width:0}.dsh-mobile-control__diagnostic-check-header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-mobile-control__diagnostic-check-header strong{min-width:0;font-size:12px;line-height:1.4}.dsh-mobile-control__diagnostic-badge{flex:none;padding:2px 6px;border-radius:999px;background:#edf1f5;color:var(--dsh-diagnostic-info);font:650 10px/1.3 system-ui}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-badge{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-badge{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-badge{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check p{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5;overflow-wrap:anywhere}.dsh-mobile-control__diagnostic-check .dsh-mobile-control__diagnostic-action{margin-top:7px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f8fafc);color:var(--dsw-alias-label-primary,#16181d)}.dsh-mobile-control__diagnostic-action span{display:inline-block;margin-right:6px;color:#2563eb;font-weight:700}.dsh-mobile-control__diagnostic-details{margin-top:12px}.dsh-mobile-control__diagnostic-details[hidden]{display:none}.dsh-mobile-control__diagnostic-details>summary{box-sizing:border-box;min-height:44px;line-height:44px}.dsh-mobile-control__diagnostic-report{box-sizing:border-box;max-height:220px;margin:4px 0 0;overflow:auto;padding:10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#606873);font:10px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
1989
+ .dsh-mobile-control__diagnostic-checks{transition:opacity 150ms ease}.dsh-mobile-control__diagnostic-checks.is-refreshing{opacity:.52}
1990
+ @keyframes dsh-diagnostic-spin{to{transform:rotate(360deg)}}@keyframes dsh-diagnostic-reveal{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
1668
1991
  .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}
1669
1992
  .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}
1670
1993
  .dsh-mobile-control__trigger{box-sizing:border-box;display:flex;align-items:center;gap:8px;width:calc(100% + 8px);height:34px;margin:4px -4px;padding:6px 2px 6px 10px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font:14px/22px system-ui;cursor:pointer}.dsh-mobile-control__trigger:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}.dsh-mobile-control__trigger.is-rail{width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;border-radius:50%}.dsh-mobile-control__trigger-icon{position:relative;box-sizing:border-box;flex:none;width:14px;height:19px;border:1.7px solid currentColor;border-radius:3px}.dsh-mobile-control__trigger-icon::after{position:absolute;right:4px;bottom:2px;width:4px;height:1.5px;border-radius:2px;background:currentColor;content:""}.dsh-mobile-control__trigger-label{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
1671
1994
  .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}
1672
- @media (prefers-reduced-motion:reduce){.dsh-mobile-control__provider,.dsh-mobile-control__cpolar-connect{transition:none}}
1995
+ @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}}
1673
1996
  `;
1674
1997
  /** Mount the desktop control or mobile feature enhancements. */
1675
1998
  function apply(ctx) {