dsh-deeppilot 0.8.2 → 0.9.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
@@ -52,7 +52,8 @@ window.__ModuleLoader__.load({
52
52
  "prompt.send",
53
53
  "sessions.manage",
54
54
  "interactions.respond",
55
- "notifications.register"
55
+ "notifications.register",
56
+ "schedule.manage"
56
57
  ];
57
58
  const DEFAULT_DEVICE_SCOPES = DEVICE_SCOPES;
58
59
  function normalizeDeviceScopes(value) {
@@ -68,6 +69,7 @@ window.__ModuleLoader__.load({
68
69
  const REPORT_ENDPOINT = "deeppilot/report";
69
70
  const BEGIN_PAIRING_ENDPOINT = "deeppilot/beginPairing";
70
71
  const REVOKE_DEVICE_ENDPOINT = "deeppilot/revokeDevice";
72
+ const SET_DEVICE_NAME_ENDPOINT = "deeppilot/setDeviceName";
71
73
  const SET_DEVICE_SCOPES_ENDPOINT = "deeppilot/setDeviceScopes";
72
74
  function reject(field) {
73
75
  throw new TypeError(`deeppilot/report result: invalid ${field}`);
@@ -110,9 +112,12 @@ window.__ModuleLoader__.load({
110
112
  updatedAt: int(a, "updatedAt", "device.apns.updatedAt")
111
113
  };
112
114
  }
115
+ const customName = s.customName;
116
+ if (customName !== void 0 && (typeof customName !== "string" || customName.length === 0 || customName.length > 64 || customName.trim() !== customName || /[\u0000-\u001f\u007f-\u009f]/u.test(customName))) reject("device.customName");
113
117
  return {
114
118
  deviceId: str(s, "deviceId", "device.deviceId"),
115
119
  deviceName: str(s, "deviceName", "device.deviceName"),
120
+ ...typeof customName === "string" ? { customName } : {},
116
121
  appVersion: str(s, "appVersion", "device.appVersion"),
117
122
  firstSeenTs: int(s, "firstSeenTs", "device.firstSeenTs"),
118
123
  lastSeenTs: int(s, "lastSeenTs", "device.lastSeenTs"),
@@ -282,6 +287,13 @@ window.__ModuleLoader__.load({
282
287
  if (typeof value !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value)) reject("deviceId");
283
288
  return value;
284
289
  } };
290
+ const customNameSchema = { parse(value) {
291
+ if (value === null) return null;
292
+ if (typeof value !== "string") reject("customName");
293
+ const trimmed = value.trim();
294
+ if (trimmed.length === 0 || trimmed.length > 64 || /[\u0000-\u001f\u007f-\u009f]/u.test(value)) reject("customName");
295
+ return trimmed;
296
+ } };
285
297
  const scopesSchema = { parse(value) {
286
298
  if (!Array.isArray(value) || value.some((scope) => typeof scope !== "string" || !DEVICE_SCOPES.includes(scope))) reject("scopes");
287
299
  return normalizeDeviceScopes(value);
@@ -290,7 +302,7 @@ window.__ModuleLoader__.load({
290
302
  if (typeof value !== "boolean") reject("boolean");
291
303
  return value;
292
304
  } };
293
- /** The rc.1 Gateway materializes each strict codec through `create()`. */
305
+ /** The rc.2 Gateway materializes each strict codec through `create()`. */
294
306
  function strictCodec(typeSymbol, schema) {
295
307
  return {
296
308
  mode: "strict",
@@ -333,6 +345,25 @@ window.__ModuleLoader__.load({
333
345
  }],
334
346
  result: strictCodec(`${REPORT_REMOTE_PACKAGE}#Boolean`, booleanSchema)
335
347
  },
348
+ {
349
+ id: `${REPORT_REMOTE_PACKAGE}#${SET_DEVICE_NAME_ENDPOINT}`,
350
+ service: "deeppilotReport",
351
+ namespace: "deeppilot",
352
+ method: "setDeviceName",
353
+ invocation: { kind: "direct" },
354
+ parameters: [{
355
+ name: "deviceId",
356
+ wire: "deviceId",
357
+ source: "json",
358
+ codec: strictCodec(`${REPORT_REMOTE_PACKAGE}#DeviceId`, deviceIdSchema)
359
+ }, {
360
+ name: "customName",
361
+ wire: "customName",
362
+ source: "json",
363
+ codec: strictCodec(`${REPORT_REMOTE_PACKAGE}#CustomDeviceName`, customNameSchema)
364
+ }],
365
+ result: strictCodec(`${REPORT_REMOTE_PACKAGE}#CustomDeviceName`, customNameSchema)
366
+ },
336
367
  {
337
368
  id: `${REPORT_REMOTE_PACKAGE}#${SET_DEVICE_SCOPES_ENDPOINT}`,
338
369
  service: "deeppilotReport",
@@ -414,6 +445,21 @@ window.__ModuleLoader__.load({
414
445
  "master.loading": "正在读取配置…",
415
446
  "master.on": "已开启:接受手机连接。",
416
447
  "master.off": "已关闭:不接受手机连接。",
448
+ "status.loading": "正在读取状态…",
449
+ "status.off": "未启用",
450
+ "status.ready": "服务已就绪",
451
+ "status.attention": "需要处理",
452
+ "connection.title": "连接",
453
+ "connection.description": "控制手机可用的局域网与公网连接。",
454
+ "connection.optionsSummary": "连接选项",
455
+ "pair.description": "选择连接方式并生成一次性配对信息。",
456
+ "devices.description": "管理已经配对的 iPhone。",
457
+ "troubleshooting.summary": "测试与故障排查",
458
+ "troubleshooting.title": "诊断工具",
459
+ "troubleshooting.description": "仅在排查连接或推送问题时使用。",
460
+ "system.summary": "系统信息",
461
+ "system.title": "运行信息",
462
+ "system.description": "插件、协议与连接端点的技术信息。",
417
463
  "local.title": "局域网连接",
418
464
  "local.on": "已开启:手机可通过独立端口 {port} 以 TLS 加密连接。",
419
465
  "local.off": "已关闭:不接受局域网连接。",
@@ -518,6 +564,20 @@ window.__ModuleLoader__.load({
518
564
  "devices.col.lastSeen": "最近在线",
519
565
  "devices.col.fingerprint": "密钥指纹",
520
566
  "devices.col.actions": "操作",
567
+ "devices.rename": "重命名",
568
+ "devices.renameAria": "重命名设备“{name}”",
569
+ "devices.nameLabel": "设备名称",
570
+ "devices.namePlaceholder": "例如:工作手机",
571
+ "devices.nameSave": "保存",
572
+ "devices.nameCancel": "取消",
573
+ "devices.nameSaving": "保存中…",
574
+ "devices.nameInvalid": "设备名称不能超过 64 个字符。",
575
+ "devices.customBadge": "自定义",
576
+ "devices.unnamed": "未命名设备",
577
+ "devices.renamed": "设备名称已保存",
578
+ "devices.nameCleared": "已恢复设备默认名称",
579
+ "devices.renameFailed": "设备名称保存失败:",
580
+ "devices.renameStaleHost": "宿主插件版本较旧,不支持设备重命名 — 请更新 dsh-deeppilot",
521
581
  "devices.revoke": "删除",
522
582
  "devices.revokeAria": "删除设备“{name}”",
523
583
  "devices.deleting": "删除中…",
@@ -553,10 +613,11 @@ window.__ModuleLoader__.load({
553
613
  "help.security1": "Funnel 公网地址可从互联网访问,但连接必须使用已配对设备的 P-256 私钥签名;不要分享仍在有效期内的配对二维码。",
554
614
  "help.security2": "丢失设备时可在设备列表中单独删除;该设备会立即断开,不影响其他手机。",
555
615
  "help.security3": "Funnel 和局域网监听器都只提供 /phone、/phone/pair 和 /phone/health,不会暴露完整 DSH Web/API。",
556
- "help.security4": "局域网连接使用插件独立的 3098 端口和明文 WebSocket。只在可信网络中使用,不要把 3098 转发到公网。",
616
+ "help.security4": "局域网连接使用插件独立的 3098 端口并启用 TLS。只在可信网络中使用,不要把该端口转发到公网。",
557
617
  "help.security5": "关闭远程连接开关会停止 Funnel,但不会影响可用的局域网连接。",
558
618
  "help.funnelDocs": "Tailscale Funnel 官方文档",
559
619
  "help.docsPrefix": "更多信息:",
620
+ "diag.summary": "技术诊断",
560
621
  "diag.remoteUnavailable": "remote 服务不可用",
561
622
  "diag.remoteUnmounted": "report remote 未挂载",
562
623
  "diag.mountFailed": "报告远程未挂载(remote mount 失败)— 请确认宿主包含 typert 组合",
@@ -572,6 +633,7 @@ window.__ModuleLoader__.load({
572
633
  "diag.missingRefresh": "refresh 回调缺失",
573
634
  "diag.missingReveal": "beginPairing 回调缺失",
574
635
  "diag.missingRotate": "revokeDevice 回调缺失",
636
+ "diag.missingRename": "setDeviceName 回调缺失",
575
637
  "diag.missingTestRelay": "testRelay 回调缺失",
576
638
  "diag.missingTestPush": "testPush 回调缺失",
577
639
  "diag.missingSetEnabled": "setDeepPilotEnabled 回调缺失",
@@ -597,6 +659,21 @@ window.__ModuleLoader__.load({
597
659
  "master.loading": "Loading settings…",
598
660
  "master.on": "On: accepting phone connections.",
599
661
  "master.off": "Off: not accepting phone connections.",
662
+ "status.loading": "Loading status…",
663
+ "status.off": "Disabled",
664
+ "status.ready": "Service ready",
665
+ "status.attention": "Needs attention",
666
+ "connection.title": "Connections",
667
+ "connection.description": "Control the LAN and public connections available to your phone.",
668
+ "connection.optionsSummary": "Connection options",
669
+ "pair.description": "Choose a connection type and generate one-time pairing information.",
670
+ "devices.description": "Manage iPhones that are already paired.",
671
+ "troubleshooting.summary": "Tests and troubleshooting",
672
+ "troubleshooting.title": "Diagnostic tools",
673
+ "troubleshooting.description": "Use these only when diagnosing connection or push issues.",
674
+ "system.summary": "System information",
675
+ "system.title": "Runtime information",
676
+ "system.description": "Technical details about the plugin, protocol, and endpoints.",
600
677
  "local.title": "LAN connection",
601
678
  "local.on": "On: phones connect over TLS through the independent port {port}.",
602
679
  "local.off": "Off: LAN connections are not accepted.",
@@ -701,6 +778,20 @@ window.__ModuleLoader__.load({
701
778
  "devices.col.lastSeen": "Last seen",
702
779
  "devices.col.fingerprint": "Key fingerprint",
703
780
  "devices.col.actions": "Actions",
781
+ "devices.rename": "Rename",
782
+ "devices.renameAria": "Rename device “{name}”",
783
+ "devices.nameLabel": "Device name",
784
+ "devices.namePlaceholder": "For example: Work phone",
785
+ "devices.nameSave": "Save",
786
+ "devices.nameCancel": "Cancel",
787
+ "devices.nameSaving": "Saving…",
788
+ "devices.nameInvalid": "Device names cannot exceed 64 characters.",
789
+ "devices.customBadge": "Custom",
790
+ "devices.unnamed": "Unnamed device",
791
+ "devices.renamed": "Device name saved",
792
+ "devices.nameCleared": "Restored the device default name",
793
+ "devices.renameFailed": "Failed to save device name: ",
794
+ "devices.renameStaleHost": "The host plugin is too old to rename devices — please update dsh-deeppilot",
704
795
  "devices.revoke": "Delete",
705
796
  "devices.revokeAria": "Delete device “{name}”",
706
797
  "devices.deleting": "Deleting…",
@@ -736,10 +827,11 @@ window.__ModuleLoader__.load({
736
827
  "help.security1": "A Funnel URL is public, but every connection requires a signature from a paired device P-256 private key. Do not share a pairing QR while it is valid.",
737
828
  "help.security2": "If a device is lost, delete only that device from the list. It disconnects immediately; other phones stay paired.",
738
829
  "help.security3": "Funnel and the LAN listener expose only /phone, /phone/pair, and /phone/health, never the full DSH Web/API surface.",
739
- "help.security4": "LAN uses the plugin's independent port 3098 over plaintext WebSocket. Use trusted networks only and never forward 3098 to the public Internet.",
830
+ "help.security4": "LAN uses the plugin's independent port 3098 with TLS enabled. Use trusted networks only and never forward that port to the public Internet.",
740
831
  "help.security5": "Disabling the remote switch stops Funnel but does not affect any LAN connection you already have.",
741
832
  "help.funnelDocs": "Tailscale Funnel docs",
742
833
  "help.docsPrefix": "More info: ",
834
+ "diag.summary": "Technical diagnostics",
743
835
  "diag.remoteUnavailable": "remote service unavailable",
744
836
  "diag.remoteUnmounted": "report remote not mounted",
745
837
  "diag.mountFailed": "Report remote not mounted (mount failed) — confirm the host includes the typert composition",
@@ -755,6 +847,7 @@ window.__ModuleLoader__.load({
755
847
  "diag.missingRefresh": "refresh callback missing",
756
848
  "diag.missingReveal": "beginPairing callback missing",
757
849
  "diag.missingRotate": "revokeDevice callback missing",
850
+ "diag.missingRename": "setDeviceName callback missing",
758
851
  "diag.missingTestRelay": "testRelay callback missing",
759
852
  "diag.missingTestPush": "testPush callback missing",
760
853
  "diag.missingSetEnabled": "setDeepPilotEnabled callback missing",
@@ -837,87 +930,154 @@ window.__ModuleLoader__.load({
837
930
  //#endregion
838
931
  //#region src/client/styles.ts
839
932
  const CSS = [
840
- ".pbb-section{max-width:720px;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:12px}",
841
- ".pbb-title{margin:0;font-size:18px;font-weight:600}",
842
- ".pbb-intro{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5;margin:0}",
843
- ".pbb-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:4px 16px}",
844
- ".pbb-field{display:flex;flex-direction:column;gap:6px;padding:12px 0}",
845
- ".pbb-field+.pbb-field{border-top:1px solid var(--dsw-alias-border-l2)}",
933
+ ".pbb-section{max-width:760px;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:10px}",
934
+ ".pbb-pageHeader{display:flex;align-items:flex-start;gap:16px;padding:2px 2px 4px}",
935
+ ".pbb-title{margin:0;font-size:18px;font-weight:600;letter-spacing:-.01em}",
936
+ ".pbb-intro{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;margin:4px 0 0;max-width:560px}",
937
+ ".pbb-refresh{font:inherit;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:none;border:none;padding:3px 0;margin-left:auto;white-space:nowrap}",
938
+ ".pbb-refresh:hover:not(:disabled){color:var(--dsw-alias-label-primary)}",
939
+ ".pbb-refresh:disabled{cursor:default;opacity:.5}",
940
+ ".pbb-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;overflow:hidden}",
941
+ ".pbb-cardHeader{display:flex;align-items:flex-start;gap:16px;padding:15px 18px 13px}",
942
+ ".pbb-cardHeader>div:first-child{min-width:0;flex:1}",
943
+ ".pbb-cardTitle{margin:0;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}",
944
+ ".pbb-cardDescription{margin:3px 0 0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.45}",
945
+ ".pbb-cardBody,.pbb-pairingBody{display:flex;flex-direction:column;gap:10px;padding:0 18px 15px}",
946
+ ".pbb-sectionIntro{padding-bottom:2px}",
846
947
  ".pbb-row{display:flex;align-items:center;gap:8px}",
847
- ".pbb-label{flex:1;font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary);min-width:0}",
848
- ".pbb-value{font-size:13px;color:var(--dsw-alias-label-secondary);word-break:break-all;text-align:right}",
849
- ".pbb-badge{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;white-space:nowrap}",
948
+ ".pbb-label{flex:1;font-size:12px;font-weight:500;color:var(--dsw-alias-label-primary);min-width:0}",
949
+ ".pbb-value{font-size:12px;color:var(--dsw-alias-label-secondary);word-break:break-all;text-align:right}",
950
+ ".pbb-badge,.pbb-countBadge{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:2px 8px;font-size:11px;font-weight:500;white-space:nowrap}",
951
+ ".pbb-countBadge{min-width:24px;text-align:center}",
952
+ ".pbb-statusBadge{display:inline-flex;align-items:center;gap:6px;flex:none;padding:3px 8px;border-radius:999px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:500;white-space:nowrap}",
850
953
  ".pbb-ok{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary));font-weight:500}",
851
954
  ".pbb-bad{color:var(--dsw-alias-label-error);font-weight:500}",
852
- ".pbb-table{width:100%;border-collapse:collapse;font-size:12px}",
853
- ".pbb-table th{color:var(--dsw-alias-label-tertiary);text-align:left;font-weight:500;padding:6px 8px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
854
- ".pbb-table td{padding:6px 8px;border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}",
855
- ".pbb-empty{color:var(--dsw-alias-label-tertiary);font-size:12px;margin:0}",
856
- ".pbb-diag{font-size:11px;color:var(--dsw-alias-label-tertiary);margin:0;line-height:1.5}",
955
+ ".pbb-diag{font-size:11px;color:var(--dsw-alias-label-tertiary);margin:0;line-height:1.5;overflow-wrap:anywhere}",
857
956
  ".pbb-diagBad{color:var(--dsw-alias-label-error)}",
858
- ".pbb-refresh{font:inherit;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:none;border:none;padding:0}",
859
- ".pbb-refresh:hover:not(:disabled){color:var(--dsw-alias-label-primary)}",
860
- ".pbb-refresh:disabled{cursor:default;opacity:.5}",
957
+ ".pbb-empty{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.55;margin:0;padding:12px;border:1px dashed var(--dsw-alias-border-l2);border-radius:8px}",
861
958
  ".pbb-tokenRow{flex-wrap:wrap}",
862
- ".pbb-token{max-width:100%;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--dsw-alias-label-secondary);word-break:break-all}",
959
+ ".pbb-token{max-width:100%;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--dsw-alias-label-secondary);word-break:break-all}",
863
960
  ".pbb-tokenActions{display:flex;align-items:center;gap:8px}",
864
- ".pbb-action{font:inherit;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:none;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:3px 8px}",
961
+ ".pbb-action{font:inherit;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:none;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:5px 9px;line-height:1.2;white-space:nowrap}",
865
962
  ".pbb-action:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}",
866
963
  ".pbb-action:disabled{cursor:default;opacity:.5}",
964
+ ".pbb-buttonPrimary{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-state-success-primary,#3b82f6);background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#3b82f6) 12%,transparent)}",
965
+ ".pbb-buttonPrimary:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#3b82f6) 18%,transparent)}",
867
966
  ".pbb-actionSelected{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-state-success-primary,#22a06b);background:var(--dsw-alias-bg-module-platform)}",
868
- ".pbb-targetList{display:flex;flex-wrap:wrap;gap:6px;padding-top:4px}",
869
- ".pbb-actionDangerGhost{color:#dc2626;border-color:rgba(220,38,38,.38);background:transparent;line-height:1.35;padding:3px 9px;box-shadow:none;transition:color .15s ease,background-color .15s ease,border-color .15s ease,box-shadow .15s ease,transform .1s ease}",
870
- ".pbb-actionDangerGhost:hover:not(:disabled){color:#b91c1c;background:rgba(220,38,38,.09);border-color:rgba(185,28,28,.5);box-shadow:0 1px 2px rgba(127,29,29,.1)}",
871
- ".pbb-actionDangerGhost:active:not(:disabled){color:#991b1b;background:rgba(220,38,38,.14);border-color:rgba(153,27,27,.55);box-shadow:none;transform:translateY(1px)}",
967
+ ".pbb-actionDangerGhost{color:#dc2626;border-color:rgba(220,38,38,.38);background:transparent;line-height:1.35;padding:4px 9px;transition:color .15s ease,background-color .15s ease,border-color .15s ease,transform .1s ease}",
968
+ ".pbb-actionDangerGhost:hover:not(:disabled){color:#b91c1c;background:rgba(220,38,38,.09);border-color:rgba(185,28,28,.5)}",
969
+ ".pbb-actionDangerGhost:active:not(:disabled){transform:translateY(1px)}",
872
970
  ".pbb-actionDangerGhost:focus-visible{outline:2px solid rgba(220,38,38,.32);outline-offset:1px}",
873
971
  ".pbb-actionDangerGhost:disabled{cursor:default;opacity:.62}",
874
972
  ".pbb-actionIcon{flex:none;display:inline-block;vertical-align:-2px;margin-right:5px;pointer-events:none}",
875
973
  ".pbb-actionBusy{display:inline-flex;align-items:center;gap:6px}",
876
974
  ".pbb-actionBusy::before{content:\"\";width:9px;height:9px;border-radius:50%;border:1.5px solid rgba(220,38,38,.35);border-top-color:currentColor;animation:pbb-spin .7s linear infinite}",
877
975
  "@keyframes pbb-spin{to{transform:rotate(360deg)}}",
878
- ".pbb-qrPanel{display:flex;flex-direction:column;align-items:center;gap:9px;padding:12px 0 4px}",
879
- ".pbb-qrImage{width:240px;height:240px;max-width:100%;background:#fff;border-radius:10px;padding:8px;box-sizing:border-box}",
880
- ".pbb-pairCodeBlock{width:min(100%,420px);display:flex;flex-direction:column;gap:5px}",
881
- ".pbb-pairCodeLabel{font-size:11px;color:var(--dsw-alias-label-tertiary);text-align:center}",
882
- ".pbb-pairCodeRow{display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap}",
883
- ".pbb-pairCode{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;line-height:1.4;letter-spacing:.04em;color:var(--dsw-alias-label-primary);word-break:break-all;text-align:center;user-select:all}",
884
- ".pbb-qrHint{max-width:420px;text-align:center;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-tertiary);margin:0}",
885
- ".pbb-switchRow{padding:12px 0;display:flex;align-items:center;gap:12px}",
886
- ".pbb-switchRow+.pbb-field{border-top:1px solid var(--dsw-alias-border-l2)}",
887
- ".pbb-switchText{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}",
976
+ ".pbb-masterRow{display:flex;align-items:center;gap:14px;padding:0 18px 14px}",
977
+ ".pbb-switchText{display:flex;flex-direction:column;gap:3px;flex:1;min-width:0}",
888
978
  ".pbb-switchTitle{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}",
889
- ".pbb-dotRow{display:flex;align-items:center;gap:7px}",
890
- ".pbb-dot{width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-tertiary);flex:none}",
979
+ ".pbb-switchDesc{font-size:11px;color:var(--dsw-alias-label-tertiary);line-height:1.4}",
980
+ ".pbb-switch{appearance:none;-webkit-appearance:none;width:34px;height:20px;border-radius:999px;background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);position:relative;cursor:pointer;padding:0;flex:none;transition:background .15s ease,border-color .15s ease}",
981
+ ".pbb-switch::after{content:\"\";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:var(--dsw-alias-label-secondary);transition:transform .15s ease,background .15s ease}",
982
+ ".pbb-switchOn{background:var(--dsw-alias-state-success-primary,#22a06b);border-color:transparent}",
983
+ ".pbb-switchOn::after{transform:translateX(14px);background:#fff}",
984
+ ".pbb-switch:disabled{opacity:.5;cursor:default}",
985
+ ".pbb-switch:focus-visible,.pbb-action:focus-visible,.pbb-targetButton:focus-visible{outline:2px solid color-mix(in srgb,var(--dsw-alias-state-success-primary,#3b82f6) 55%,transparent);outline-offset:2px}",
986
+ ".pbb-connectionGrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:0 18px 14px}",
987
+ ".pbb-connectionTile{min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-module-platform);padding:11px 12px;display:flex;flex-direction:column;gap:5px}",
988
+ ".pbb-connectionTileHeader{display:flex;align-items:center;gap:10px}",
989
+ ".pbb-dotRow{display:flex;align-items:center;gap:7px;flex:1;min-width:0}",
990
+ ".pbb-dot{width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-tertiary);flex:none}",
891
991
  ".pbb-dotOk{background:var(--dsw-alias-state-success-primary,#22a06b)}",
892
992
  ".pbb-dotWarn{background:var(--dsw-alias-state-warning-primary,#e2b203)}",
893
993
  ".pbb-dotBad{background:var(--dsw-alias-label-error)}",
994
+ ".pbb-connectionState{font-size:11px;color:var(--dsw-alias-label-secondary);font-weight:500}",
995
+ ".pbb-connectionDescription{font-size:11px;color:var(--dsw-alias-label-tertiary);line-height:1.45;margin:0}",
894
996
  ".pbb-rowAction{display:flex;gap:8px;margin-top:4px}",
895
- ".pbb-switchDesc{font-size:11px;color:var(--dsw-alias-label-tertiary);line-height:1.4}",
896
- ".pbb-switch{appearance:none;-webkit-appearance:none;width:38px;height:22px;border-radius:999px;background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);position:relative;cursor:pointer;padding:0;flex:none;transition:background .15s ease}",
897
- ".pbb-switch::after{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-secondary);transition:transform .15s ease,background .15s ease}",
898
- ".pbb-switchOn{background:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}",
899
- ".pbb-switchOn::after{transform:translateX(16px);background:#fff}",
900
- ".pbb-switch:disabled{opacity:.5;cursor:default}",
901
- ".pbb-limitRow{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 0;display:flex;align-items:center;gap:12px}",
902
- ".pbb-limitNested{border-top:0;padding:0}",
903
- ".pbb-limitControl{display:flex;align-items:center;gap:8px;flex:none}",
904
- ".pbb-numberInput{width:72px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;padding:5px 8px}",
905
- ".pbb-numberInput:focus{outline:2px solid var(--dsw-alias-state-success-primary,#22a06b);outline-offset:1px}",
906
- ".pbb-numberInput:disabled{opacity:.5}",
907
- ".pbb-help{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 0}",
908
- ".pbb-help summary{cursor:pointer;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;user-select:none}",
909
- ".pbb-help summary:hover{color:var(--dsw-alias-label-secondary)}",
910
- ".pbb-helpBody{display:flex;flex-direction:column;gap:14px;padding:12px 0 2px}",
997
+ ".pbb-statGrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:0 18px 14px}",
998
+ ".pbb-stat{display:flex;align-items:center;gap:8px;min-width:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-bg-module-platform)}",
999
+ ".pbb-statLabel{font-size:11px;color:var(--dsw-alias-label-tertiary);flex:1}",
1000
+ ".pbb-statValue{font-size:12px;color:var(--dsw-alias-label-primary);font-weight:600}",
1001
+ ".pbb-inlineDetails,.pbb-help{border-top:1px solid var(--dsw-alias-border-l2)}",
1002
+ ".pbb-inlineDetails>summary,.pbb-help>summary{cursor:pointer;color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;user-select:none;list-style:none;display:flex;align-items:center;padding:12px 18px}",
1003
+ ".pbb-inlineDetails>summary::-webkit-details-marker,.pbb-help>summary::-webkit-details-marker{display:none}",
1004
+ ".pbb-inlineDetails>summary::after,.pbb-help>summary::after{content:\"›\";margin-left:auto;color:var(--dsw-alias-label-tertiary);font-size:16px;line-height:1;transform:rotate(90deg);transition:transform .15s ease}",
1005
+ ".pbb-inlineDetails[open]>summary::after,.pbb-help[open]>summary::after{transform:rotate(-90deg)}",
1006
+ ".pbb-inlineDetails[open]>summary,.pbb-help[open]>summary{color:var(--dsw-alias-label-secondary)}",
1007
+ ".pbb-helpBody{display:flex;flex-direction:column;gap:14px;padding:2px 18px 15px}",
911
1008
  ".pbb-helpSection{display:flex;flex-direction:column;gap:6px}",
912
1009
  ".pbb-helpHeading{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary)}",
913
- ".pbb-helpList{margin:0;padding-left:20px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6}",
1010
+ ".pbb-helpList{margin:0;padding-left:20px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.6}",
914
1011
  ".pbb-helpList li+li{margin-top:4px}",
915
- ".pbb-helpText{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6}",
1012
+ ".pbb-helpText{margin:0;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.6}",
916
1013
  ".pbb-helpCode{display:block;margin:2px 0 0;padding:10px;overflow:auto;white-space:pre-wrap;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:11px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace}",
917
1014
  ".pbb-helpLink{color:var(--dsw-alias-label-secondary);text-decoration:underline;text-underline-offset:2px}",
918
- ".pbb-versionFooter{display:flex;justify-content:center;align-items:center;gap:8px;font-size:11px;color:var(--dsw-alias-label-tertiary);padding:8px 0 4px;line-height:1.4}",
919
- ".pbb-versionFooter a{color:var(--dsw-alias-state-success-primary,#22a06b);text-decoration:none}",
920
- ".pbb-versionFooter a:hover{text-decoration:underline;text-underline-offset:2px}"
1015
+ ".pbb-limitRow{display:flex;align-items:center;gap:12px;padding:10px 0;border-top:1px solid var(--dsw-alias-border-l2)}",
1016
+ ".pbb-limitRow:first-child{border-top:0;padding-top:2px}",
1017
+ ".pbb-limitControl{display:flex;align-items:center;gap:8px;flex:none}",
1018
+ ".pbb-numberInput{width:72px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;padding:5px 8px}",
1019
+ ".pbb-numberInput:focus{outline:2px solid var(--dsw-alias-state-success-primary,#22a06b);outline-offset:1px}",
1020
+ ".pbb-numberInput:disabled{opacity:.5}",
1021
+ ".pbb-targetList{display:flex;flex-wrap:wrap;gap:6px}",
1022
+ ".pbb-targetButton{font:inherit;font-size:11px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:none;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:6px 9px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
1023
+ ".pbb-targetButton:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}",
1024
+ ".pbb-targetButton:disabled{cursor:default;opacity:.5}",
1025
+ ".pbb-targetSelected{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-state-success-primary,#22a06b);background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#22a06b) 10%,transparent)}",
1026
+ ".pbb-qrPanel{display:flex;flex-direction:column;align-items:center;gap:9px;padding:14px 0 4px;border-top:1px solid var(--dsw-alias-border-l2)}",
1027
+ ".pbb-qrImage{width:224px;height:224px;max-width:100%;background:#fff;border-radius:10px;padding:8px;box-sizing:border-box}",
1028
+ ".pbb-pairCodeBlock{width:min(100%,460px);display:flex;flex-direction:column;gap:6px}",
1029
+ ".pbb-pairCodeLabel{font-size:11px;color:var(--dsw-alias-label-tertiary);text-align:center}",
1030
+ ".pbb-pairCodeRow{display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap}",
1031
+ ".pbb-pairCode{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;line-height:1.4;letter-spacing:.03em;color:var(--dsw-alias-label-primary);word-break:break-all;text-align:center;user-select:all}",
1032
+ ".pbb-qrHint{max-width:460px;text-align:center;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-tertiary);margin:0}",
1033
+ ".pbb-tableWrap{width:100%;overflow-x:auto}",
1034
+ ".pbb-table{width:100%;border-collapse:collapse;font-size:12px;table-layout:fixed}",
1035
+ ".pbb-table th{color:var(--dsw-alias-label-tertiary);text-align:left;font-weight:500;padding:7px 8px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
1036
+ ".pbb-table td{padding:9px 8px;border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);vertical-align:middle}",
1037
+ ".pbb-table tbody tr:last-child td{border-bottom:0}",
1038
+ ".pbb-table th:first-child{width:48%}",
1039
+ ".pbb-table th:nth-child(2){width:32%}",
1040
+ ".pbb-tableActionHead,.pbb-tableActionCell{text-align:right;width:142px!important}",
1041
+ ".pbb-tableActionCell{white-space:nowrap}",
1042
+ ".pbb-tableActionCell>.pbb-action+.pbb-action{margin-left:5px}",
1043
+ ".pbb-deviceNameRow{display:flex;align-items:center;gap:6px;min-width:0}",
1044
+ ".pbb-deviceName{display:block;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
1045
+ ".pbb-deviceMeta{display:flex;gap:4px;align-items:center;margin-top:3px;color:var(--dsw-alias-label-tertiary);font-size:10px;min-width:0}",
1046
+ ".pbb-deviceMeta code{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
1047
+ ".pbb-deviceRenameForm{display:flex;flex-direction:column;gap:5px;min-width:0}",
1048
+ ".pbb-deviceRenameControls{display:flex;align-items:center;gap:7px;min-width:0}",
1049
+ ".pbb-deviceRenameControls>.pbb-deviceNameInput{flex:1;width:auto;min-width:0}",
1050
+ ".pbb-deviceNameInput{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;padding:6px 8px}",
1051
+ ".pbb-deviceNameInput:focus{outline:2px solid var(--dsw-alias-state-success-primary,#22a06b);outline-offset:1px}",
1052
+ ".pbb-deviceNameInput:disabled{opacity:.5}",
1053
+ ".pbb-deviceRenameActions{display:flex;align-items:center;gap:5px;flex:none}",
1054
+ ".pbb-deviceIconAction{width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;padding:0;border-radius:8px;box-shadow:none}",
1055
+ ".pbb-deviceRenameActions .pbb-buttonPrimary{color:#fff;border-color:transparent;background:var(--dsw-alias-state-success-primary,#3b82f6)}",
1056
+ ".pbb-deviceRenameActions .pbb-buttonPrimary:hover:not(:disabled){color:#fff;background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#3b82f6) 86%,#000)}",
1057
+ ".pbb-deviceRenameActions .pbb-deviceIconAction:not(.pbb-buttonPrimary){background:var(--dsw-alias-bg-module-platform);border-color:transparent;color:var(--dsw-alias-label-secondary)}",
1058
+ ".pbb-deviceRenameActions .pbb-deviceIconAction:not(.pbb-buttonPrimary):hover:not(:disabled){background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-label-primary)}",
1059
+ ".pbb-controlIcon{display:block;pointer-events:none}",
1060
+ ".pbb-inlineSpinner{width:12px;height:12px;box-sizing:border-box;border:1.5px solid currentColor;border-right-color:transparent;border-radius:50%;animation:pbb-spin .7s linear infinite}",
1061
+ ".pbb-lastSeen{color:var(--dsw-alias-label-secondary);font-size:11px;overflow-wrap:anywhere}",
1062
+ ".pbb-disclosure{padding:0}",
1063
+ ".pbb-disclosure>summary{list-style:none;display:flex;align-items:center;gap:12px;padding:13px 18px;cursor:pointer;color:var(--dsw-alias-label-primary)}",
1064
+ ".pbb-disclosure>summary::-webkit-details-marker{display:none}",
1065
+ ".pbb-disclosure>summary::after{content:\"›\";color:var(--dsw-alias-label-tertiary);font-size:17px;line-height:1;transform:rotate(90deg);transition:transform .15s ease}",
1066
+ ".pbb-disclosure[open]>summary::after{transform:rotate(-90deg)}",
1067
+ ".pbb-disclosureTitle{font-size:12px;font-weight:500;flex:1}",
1068
+ ".pbb-disclosureMeta{font-size:11px;color:var(--dsw-alias-label-tertiary);max-width:46%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
1069
+ ".pbb-disclosureBody{display:flex;flex-direction:column;gap:12px;padding:2px 18px 16px}",
1070
+ ".pbb-testGrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}",
1071
+ ".pbb-testItem{display:flex;align-items:center;gap:12px;padding:11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform)}",
1072
+ ".pbb-testTitle{font-size:12px;font-weight:500;color:var(--dsw-alias-label-primary)}",
1073
+ ".pbb-testDescription{font-size:10px;color:var(--dsw-alias-label-tertiary);line-height:1.45}",
1074
+ ".pbb-runtimeList{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;overflow:hidden}",
1075
+ ".pbb-runtimeList>.pbb-runtimeRow,.pbb-runtimeRow{display:flex;align-items:center;gap:10px;padding:9px 10px;border-top:1px solid var(--dsw-alias-border-l2)}",
1076
+ ".pbb-runtimeList>.pbb-runtimeRow:first-child{border-top:0}",
1077
+ ".pbb-runtimeList>.pbb-runtimeRow>.pbb-row{flex:1;min-width:0}",
1078
+ ".pbb-updateRow{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-success-primary,#22a06b) 35%,var(--dsw-alias-border-l2));border-radius:8px;color:var(--dsw-alias-label-primary);font-size:12px}",
1079
+ ".pbb-diagnostics>summary{color:var(--dsw-alias-label-secondary)}",
1080
+ "@media (max-width:620px){.pbb-connectionGrid,.pbb-testGrid{grid-template-columns:1fr}.pbb-cardHeader{align-items:flex-start}.pbb-masterRow{align-items:flex-start}.pbb-lastSeen{display:none}.pbb-table th:nth-child(2){width:1px}.pbb-table th:first-child{width:auto}.pbb-tableActionHead,.pbb-tableActionCell{width:132px!important}.pbb-limitRow{align-items:flex-start;flex-direction:column}.pbb-limitControl{align-self:flex-end}}"
921
1081
  ].join("\n");
922
1082
  /**
923
1083
  * Client bundle id, identical to the `window.__ModuleLoader__.load({ id })`
@@ -932,7 +1092,7 @@ window.__ModuleLoader__.load({
932
1092
  * materialization; this sheet is injected from `apply()`, which runs after
933
1093
  * that point, so it would never be attributed to us. Stamping `data-plugin`
934
1094
  * ourselves lets the module system's `removeOwnedStyles(id)` delete the sheet
935
- * when the plugin is unloaded at runtime — the rc.1 Plugin Manager and client
1095
+ * when the plugin is unloaded at runtime — the rc.2 Plugin Manager and client
936
1096
  * entry reconciler both support live removal, and an
937
1097
  * orphaned sheet would otherwise keep styling a page we no longer own.
938
1098
  */
@@ -2366,8 +2526,8 @@ window.__ModuleLoader__.load({
2366
2526
  return AlphanumericData.getBitsLength(this.data.length);
2367
2527
  };
2368
2528
  AlphanumericData.prototype.write = function write(bitBuffer) {
2369
- let i;
2370
- for (i = 0; i + 2 <= this.data.length; i += 2) {
2529
+ let i = 0;
2530
+ for (; i + 2 <= this.data.length; i += 2) {
2371
2531
  let value = ALPHA_NUM_CHARS.indexOf(this.data[i]) * 45;
2372
2532
  value += ALPHA_NUM_CHARS.indexOf(this.data[i + 1]);
2373
2533
  bitBuffer.put(value, 11);
@@ -2418,8 +2578,8 @@ window.__ModuleLoader__.load({
2418
2578
  return KanjiData.getBitsLength(this.data.length);
2419
2579
  };
2420
2580
  KanjiData.prototype.write = function(bitBuffer) {
2421
- let i;
2422
- for (i = 0; i < this.data.length; i++) {
2581
+ let i = 0;
2582
+ for (; i < this.data.length; i++) {
2423
2583
  let value = Utils.toSJIS(this.data[i]);
2424
2584
  if (value >= 33088 && value <= 40956) value -= 33088;
2425
2585
  else if (value >= 57408 && value <= 60351) value -= 49472;
@@ -3516,6 +3676,29 @@ window.__ModuleLoader__.load({
3516
3676
  strokeLinejoin: "round",
3517
3677
  "aria-hidden": true
3518
3678
  }, (0, react.createElement)("path", { d: "M2.5 4.5h11" }), (0, react.createElement)("path", { d: "M6.5 2.5h3" }), (0, react.createElement)("path", { d: "M4 4.5l.6 8.2a1 1 0 0 0 1 .9h4.8a1 1 0 0 0 1-.9l.6-8.2" }), (0, react.createElement)("path", { d: "M6.6 7v4.2" }), (0, react.createElement)("path", { d: "M9.4 7v4.2" }));
3679
+ const CheckIcon = () => (0, react.createElement)("svg", {
3680
+ className: "pbb-controlIcon",
3681
+ viewBox: "0 0 16 16",
3682
+ width: 14,
3683
+ height: 14,
3684
+ fill: "none",
3685
+ stroke: "currentColor",
3686
+ strokeWidth: 1.8,
3687
+ strokeLinecap: "round",
3688
+ strokeLinejoin: "round",
3689
+ "aria-hidden": true
3690
+ }, (0, react.createElement)("path", { d: "m3 8.3 3.1 3.1L13 4.8" }));
3691
+ const CloseIcon = () => (0, react.createElement)("svg", {
3692
+ className: "pbb-controlIcon",
3693
+ viewBox: "0 0 16 16",
3694
+ width: 14,
3695
+ height: 14,
3696
+ fill: "none",
3697
+ stroke: "currentColor",
3698
+ strokeWidth: 1.6,
3699
+ strokeLinecap: "round",
3700
+ "aria-hidden": true
3701
+ }, (0, react.createElement)("path", { d: "m4.5 4.5 7 7m0-7-7 7" }));
3519
3702
  async function writeClipboard(t, value) {
3520
3703
  try {
3521
3704
  await navigator.clipboard.writeText(value);
@@ -3596,6 +3779,7 @@ window.__ModuleLoader__.load({
3596
3779
  const [selectedPairingHost, setSelectedPairingHost] = (0, react.useState)(null);
3597
3780
  const [pairingPanel, dispatchPairingPanel] = (0, react.useReducer)(pairingPanelReduce, initialPairingPanelState);
3598
3781
  const qrRequestId = (0, react.useRef)(0);
3782
+ const [troubleshootingOpen, setTroubleshootingOpen] = (0, react.useState)(false);
3599
3783
  const [relayTestBusy, setRelayTestBusy] = (0, react.useState)(false);
3600
3784
  const [relayTestResult, setRelayTestResult] = (0, react.useState)(null);
3601
3785
  const [relayTestError, setRelayTestError] = (0, react.useState)("");
@@ -3603,7 +3787,10 @@ window.__ModuleLoader__.load({
3603
3787
  const [pushTestResult, setPushTestResult] = (0, react.useState)(null);
3604
3788
  const [pushTestError, setPushTestError] = (0, react.useState)("");
3605
3789
  const [deviceBusy, setDeviceBusy] = (0, react.useState)(null);
3790
+ const [deviceBusyAction, setDeviceBusyAction] = (0, react.useState)(null);
3606
3791
  const [deviceMessage, setDeviceMessage] = (0, react.useState)("");
3792
+ const [editingDeviceId, setEditingDeviceId] = (0, react.useState)(null);
3793
+ const [deviceNameDraft, setDeviceNameDraft] = (0, react.useState)("");
3607
3794
  (0, react.useEffect)(() => {
3608
3795
  if (typeof props.refresh !== "function") return;
3609
3796
  props.refresh();
@@ -3616,6 +3803,7 @@ window.__ModuleLoader__.load({
3616
3803
  setPushTestError(translateWith(props.t, "push.staleHost"));
3617
3804
  return;
3618
3805
  }
3806
+ setTroubleshootingOpen(true);
3619
3807
  setPushTestBusy(true);
3620
3808
  setPushTestError("");
3621
3809
  props.testPush().then((result) => {
@@ -3632,6 +3820,7 @@ window.__ModuleLoader__.load({
3632
3820
  setRelayTestError(translateWith(props.t, "push.staleHostRelay"));
3633
3821
  return;
3634
3822
  }
3823
+ setTroubleshootingOpen(true);
3635
3824
  setRelayTestBusy(true);
3636
3825
  setRelayTestError("");
3637
3826
  props.testRelay().then((result) => {
@@ -3702,6 +3891,7 @@ window.__ModuleLoader__.load({
3702
3891
  if (typeof props.refresh !== "function") diag.push(translateWith(props.t, "diag.missingRefresh"));
3703
3892
  if (typeof props.beginPairing !== "function") diag.push(translateWith(props.t, "diag.missingReveal"));
3704
3893
  if (typeof props.revokeDevice !== "function") diag.push(translateWith(props.t, "diag.missingRotate"));
3894
+ if (typeof props.setDeviceName !== "function") diag.push(translateWith(props.t, "diag.missingRename"));
3705
3895
  if (typeof props.testRelay !== "function") diag.push(translateWith(props.t, "diag.missingTestRelay"));
3706
3896
  if (typeof props.testPush !== "function") diag.push(translateWith(props.t, "diag.missingTestPush"));
3707
3897
  if (typeof props.setDeepPilotEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetEnabled"));
@@ -3844,23 +4034,27 @@ window.__ModuleLoader__.load({
3844
4034
  const advancedRows = [];
3845
4035
  if (report !== null) {
3846
4036
  primaryRows.push((0, react.createElement)("div", {
3847
- className: "pbb-field",
4037
+ className: "pbb-stat",
3848
4038
  key: "conn"
3849
- }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "panel.activeConnections")), (0, react.createElement)("span", { className: "pbb-value" }, String(report.activeConnections)))), (0, react.createElement)("div", {
3850
- className: "pbb-field",
4039
+ }, (0, react.createElement)("span", { className: "pbb-statLabel" }, translateWith(props.t, "panel.activeConnections")), (0, react.createElement)("span", { className: "pbb-statValue" }, String(report.activeConnections))), (0, react.createElement)("div", {
4040
+ className: "pbb-stat",
3851
4041
  key: "identity"
3852
- }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "panel.identity")), (0, react.createElement)("code", { className: "pbb-token " + (report.pairingReady ? "pbb-ok" : "pbb-bad") }, report.pairingReady ? translateWith(props.t, "panel.identityReady") : translateWith(props.t, "panel.identityNotReady")))));
4042
+ }, (0, react.createElement)("span", { className: "pbb-statLabel" }, translateWith(props.t, "panel.identity")), (0, react.createElement)("code", { className: "pbb-token " + (report.pairingReady ? "pbb-ok" : "pbb-bad") }, report.pairingReady ? translateWith(props.t, "panel.identityReady") : translateWith(props.t, "panel.identityNotReady"))));
4043
+ if (report.local.phase === "online" && report.local.tlsFingerprint) advancedRows.push((0, react.createElement)("div", {
4044
+ className: "pbb-runtimeRow",
4045
+ key: "tls"
4046
+ }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "local.tlsFingerprint")), (0, react.createElement)("code", { className: "pbb-token" }, report.local.tlsFingerprint))));
3853
4047
  advancedRows.push((0, react.createElement)("div", {
3854
- className: "pbb-field",
4048
+ className: "pbb-runtimeRow",
3855
4049
  key: "proto"
3856
4050
  }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.protocolVersion")), (0, react.createElement)("span", { className: "pbb-value" }, "v" + String(report.protocolVersion)))), (0, react.createElement)("div", {
3857
- className: "pbb-field",
4051
+ className: "pbb-runtimeRow",
3858
4052
  key: "server"
3859
4053
  }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.serverVersion")), (0, react.createElement)("span", { className: "pbb-value" }, report.serverVersion))), (0, react.createElement)("div", {
3860
- className: "pbb-field",
4054
+ className: "pbb-runtimeRow",
3861
4055
  key: "path"
3862
4056
  }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.identityPath")), (0, react.createElement)("span", { className: "pbb-value" }, report.identityPath))), (0, react.createElement)("div", {
3863
- className: "pbb-field",
4057
+ className: "pbb-runtimeRow",
3864
4058
  key: "buffer"
3865
4059
  }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.bufferMax")), (0, react.createElement)("span", { className: "pbb-value" }, String(report.historyBufferMax) + translateWith(props.t, "advanced.frames")))));
3866
4060
  }
@@ -3868,29 +4062,144 @@ window.__ModuleLoader__.load({
3868
4062
  if (typeof props.revokeDevice !== "function") return;
3869
4063
  if (typeof window !== "undefined" && !window.confirm(translateWith(props.t, "devices.revokeConfirm", { name }))) return;
3870
4064
  setDeviceBusy(deviceId);
4065
+ setDeviceBusyAction("revoke");
3871
4066
  setDeviceMessage("");
3872
4067
  props.revokeDevice(deviceId).then(() => {
3873
4068
  setDeviceMessage(translateWith(props.t, "devices.revoked"));
3874
4069
  }, (error) => {
3875
4070
  setDeviceMessage(translateWith(props.t, "devices.revokeFailed") + (error instanceof Error ? error.message : String(error)));
3876
- }).finally(() => setDeviceBusy(null));
4071
+ }).finally(() => {
4072
+ setDeviceBusy(null);
4073
+ setDeviceBusyAction(null);
4074
+ });
4075
+ };
4076
+ const deviceLabel = (device) => (device.customName ?? device.deviceName).trim() || translateWith(props.t, "devices.unnamed");
4077
+ const beginDeviceRename = (device) => {
4078
+ setEditingDeviceId(device.deviceId);
4079
+ setDeviceNameDraft(device.customName ?? device.deviceName);
4080
+ setDeviceMessage("");
4081
+ };
4082
+ const cancelDeviceRename = () => {
4083
+ setEditingDeviceId(null);
4084
+ setDeviceNameDraft("");
4085
+ setDeviceMessage("");
4086
+ };
4087
+ const saveDeviceName = (deviceId) => {
4088
+ if (typeof props.setDeviceName !== "function") return;
4089
+ const normalized = deviceNameDraft.trim();
4090
+ if (normalized.length > 64) {
4091
+ setDeviceMessage(translateWith(props.t, "devices.nameInvalid"));
4092
+ return;
4093
+ }
4094
+ setDeviceBusy(deviceId);
4095
+ setDeviceBusyAction("rename");
4096
+ setDeviceMessage("");
4097
+ props.setDeviceName(deviceId, normalized === "" ? null : normalized).then(() => {
4098
+ setDeviceMessage(normalized === "" ? translateWith(props.t, "devices.nameCleared") : translateWith(props.t, "devices.renamed"));
4099
+ setEditingDeviceId(null);
4100
+ setDeviceNameDraft("");
4101
+ }, (error) => {
4102
+ setDeviceMessage(translateWith(props.t, "devices.renameFailed") + (error instanceof Error ? error.message : String(error)));
4103
+ }).finally(() => {
4104
+ setDeviceBusy(null);
4105
+ setDeviceBusyAction(null);
4106
+ });
3877
4107
  };
3878
4108
  const visibleDevices = report?.devices.filter((device) => device.revokedAt === void 0) ?? [];
3879
- const deviceTable = visibleDevices.length > 0 ? (0, react.createElement)("table", { className: "pbb-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, (0, react.createElement)("th", null, translateWith(props.t, "devices.col.name")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.fingerprint")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.lastSeen")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.actions")))), (0, react.createElement)("tbody", null, visibleDevices.map((d) => (0, react.createElement)("tr", { key: d.deviceId }, (0, react.createElement)("td", null, d.deviceName, (0, react.createElement)("div", { className: "pbb-diag" }, d.appVersion)), (0, react.createElement)("td", null, (0, react.createElement)("code", { className: "pbb-token" }, d.fingerprint.slice(0, 12))), (0, react.createElement)("td", null, new Date(d.lastSeenTs).toLocaleString()), (0, react.createElement)("td", null, (0, react.createElement)("button", {
3880
- type: "button",
3881
- className: "pbb-action pbb-actionDangerGhost",
3882
- disabled: deviceBusy === d.deviceId,
3883
- "aria-label": deviceBusy === d.deviceId ? translateWith(props.t, "devices.deleting", { name: d.deviceName }) : translateWith(props.t, "devices.revokeAria", { name: d.deviceName }),
3884
- onClick: () => revokeDevice(d.deviceId, d.deviceName)
3885
- }, deviceBusy === d.deviceId ? null : TrashIcon(), deviceBusy === d.deviceId ? (0, react.createElement)("span", { className: "pbb-actionBusy" }, translateWith(props.t, "devices.deleting")) : translateWith(props.t, "devices.revoke"))))))) : (0, react.createElement)("p", { className: "pbb-empty" }, translateWith(props.t, "devices.empty"));
4109
+ const deviceTable = visibleDevices.length > 0 ? (0, react.createElement)("div", { className: "pbb-tableWrap" }, (0, react.createElement)("table", { className: "pbb-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, (0, react.createElement)("th", null, translateWith(props.t, "devices.col.name")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.lastSeen")), (0, react.createElement)("th", { className: "pbb-tableActionHead" }, translateWith(props.t, "devices.col.actions")))), (0, react.createElement)("tbody", null, visibleDevices.map((d) => {
4110
+ const editing = editingDeviceId === d.deviceId;
4111
+ const busy = deviceBusy === d.deviceId;
4112
+ const label = deviceLabel(d);
4113
+ return (0, react.createElement)("tr", {
4114
+ key: d.deviceId,
4115
+ "aria-busy": busy
4116
+ }, (0, react.createElement)("td", null, editing ? (0, react.createElement)("form", {
4117
+ className: "pbb-deviceRenameForm",
4118
+ onSubmit: (event) => {
4119
+ event.preventDefault();
4120
+ saveDeviceName(d.deviceId);
4121
+ }
4122
+ }, (0, react.createElement)("div", { className: "pbb-deviceRenameControls" }, (0, react.createElement)("input", {
4123
+ className: "pbb-deviceNameInput",
4124
+ type: "text",
4125
+ value: deviceNameDraft,
4126
+ maxLength: 64,
4127
+ autoFocus: true,
4128
+ disabled: busy,
4129
+ placeholder: translateWith(props.t, "devices.namePlaceholder"),
4130
+ "aria-label": translateWith(props.t, "devices.nameLabel"),
4131
+ onChange: (event) => setDeviceNameDraft(event.currentTarget.value),
4132
+ onKeyDown: (event) => {
4133
+ if (event.key === "Enter") {
4134
+ event.preventDefault();
4135
+ saveDeviceName(d.deviceId);
4136
+ } else if (event.key === "Escape") {
4137
+ event.preventDefault();
4138
+ cancelDeviceRename();
4139
+ }
4140
+ }
4141
+ }), (0, react.createElement)("div", { className: "pbb-deviceRenameActions" }, (0, react.createElement)("button", {
4142
+ type: "submit",
4143
+ className: "pbb-action pbb-deviceIconAction pbb-buttonPrimary",
4144
+ disabled: busy || deviceNameDraft.length > 64,
4145
+ title: busy ? translateWith(props.t, "devices.nameSaving") : translateWith(props.t, "devices.nameSave"),
4146
+ "aria-label": busy ? translateWith(props.t, "devices.nameSaving") : translateWith(props.t, "devices.nameSave")
4147
+ }, busy ? (0, react.createElement)("span", {
4148
+ className: "pbb-inlineSpinner",
4149
+ "aria-hidden": true
4150
+ }) : CheckIcon()), (0, react.createElement)("button", {
4151
+ type: "button",
4152
+ className: "pbb-action pbb-deviceIconAction",
4153
+ disabled: busy,
4154
+ title: translateWith(props.t, "devices.nameCancel"),
4155
+ "aria-label": translateWith(props.t, "devices.nameCancel"),
4156
+ onClick: cancelDeviceRename
4157
+ }, CloseIcon()))), deviceNameDraft.length > 64 ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "devices.nameInvalid")) : null) : (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "pbb-deviceNameRow" }, (0, react.createElement)("span", { className: "pbb-deviceName" }, label), d.customName ? (0, react.createElement)("span", { className: "pbb-badge" }, translateWith(props.t, "devices.customBadge")) : null), (0, react.createElement)("span", { className: "pbb-deviceMeta" }, d.appVersion, " · ", (0, react.createElement)("code", null, d.fingerprint.slice(0, 12))))), (0, react.createElement)("td", { className: "pbb-lastSeen" }, new Date(d.lastSeenTs).toLocaleString()), (0, react.createElement)("td", { className: "pbb-tableActionCell" }, !editing ? (0, react.createElement)("button", {
4158
+ type: "button",
4159
+ className: "pbb-action",
4160
+ disabled: busy || editingDeviceId !== null || typeof props.setDeviceName !== "function",
4161
+ "aria-label": translateWith(props.t, "devices.renameAria", { name: label }),
4162
+ onClick: () => beginDeviceRename(d)
4163
+ }, translateWith(props.t, "devices.rename")) : null, (0, react.createElement)("button", {
4164
+ type: "button",
4165
+ className: "pbb-action pbb-actionDangerGhost",
4166
+ disabled: busy,
4167
+ "aria-label": busy && deviceBusyAction === "revoke" ? translateWith(props.t, "devices.deleting", { name: label }) : translateWith(props.t, "devices.revokeAria", { name: label }),
4168
+ onClick: () => revokeDevice(d.deviceId, label)
4169
+ }, busy && deviceBusyAction === "revoke" ? null : TrashIcon(), busy && deviceBusyAction === "revoke" ? (0, react.createElement)("span", { className: "pbb-actionBusy" }, translateWith(props.t, "devices.deleting")) : translateWith(props.t, "devices.revoke"))));
4170
+ })))) : (0, react.createElement)("p", { className: "pbb-empty" }, translateWith(props.t, "devices.empty"));
3886
4171
  const switchTitle = translateWith(props.t, "master.title");
3887
4172
  const switchDesc = switchReady ? enabled ? translateWith(props.t, "master.on") : translateWith(props.t, "master.off") : translateWith(props.t, "master.loading");
3888
- return (0, react.createElement)("div", { className: "pbb-section" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("h2", { className: "pbb-title" }, "DeepPilot"), (0, react.createElement)("div", { style: { flex: "1" } }), (0, react.createElement)("button", {
4173
+ const localMeta = report === null ? null : LOCAL_PHASE_META[report.local.phase];
4174
+ const remoteMeta = report === null ? null : REMOTE_PHASE_META[report.remote.phase];
4175
+ const connectionNeedsAttention = localMeta?.dot.includes("pbb-dotBad") === true || remoteMeta?.dot.includes("pbb-dotBad") === true;
4176
+ const connectionOnline = localMeta?.dot.includes("pbb-dotOk") === true || remoteMeta?.dot.includes("pbb-dotOk") === true;
4177
+ const serviceStatus = !switchReady ? translateWith(props.t, "status.loading") : !enabled ? translateWith(props.t, "status.off") : connectionNeedsAttention ? translateWith(props.t, "status.attention") : connectionOnline ? translateWith(props.t, "status.ready") : translateWith(props.t, "status.loading");
4178
+ const serviceDot = !enabled ? "" : connectionNeedsAttention ? " pbb-dotBad" : connectionOnline ? " pbb-dotOk" : " pbb-dotWarn";
4179
+ const renderConnectionTile = (key, title, description, checked, ready, dot, statusLabel, onToggle, extra = null) => (0, react.createElement)("div", {
4180
+ className: "pbb-connectionTile",
4181
+ key
4182
+ }, (0, react.createElement)("div", { className: "pbb-connectionTileHeader" }, (0, react.createElement)("span", { className: "pbb-switchTitle pbb-dotRow" }, (0, react.createElement)("span", {
4183
+ className: "pbb-dot" + dot,
4184
+ role: "img",
4185
+ "aria-label": statusLabel,
4186
+ title: statusLabel
4187
+ }), title), (0, react.createElement)("button", {
4188
+ type: "button",
4189
+ role: "switch",
4190
+ "aria-checked": checked,
4191
+ "aria-label": title,
4192
+ disabled: !ready,
4193
+ className: "pbb-switch" + (checked ? " pbb-switchOn" : ""),
4194
+ onClick: onToggle
4195
+ })), (0, react.createElement)("span", { className: "pbb-connectionState" }, statusLabel), (0, react.createElement)("p", { className: "pbb-connectionDescription" }, description), extra);
4196
+ return (0, react.createElement)("div", { className: "pbb-section" }, (0, react.createElement)("header", { className: "pbb-pageHeader" }, (0, react.createElement)("div", null, (0, react.createElement)("h2", { className: "pbb-title" }, "DeepPilot"), (0, react.createElement)("p", { className: "pbb-intro" }, translateWith(props.t, "meta.intro"))), (0, react.createElement)("button", {
4197
+ type: "button",
3889
4198
  className: "pbb-refresh",
3890
4199
  onClick: () => {
3891
4200
  if (typeof props.refresh === "function") props.refresh();
3892
4201
  }
3893
- }, translateWith(props.t, "meta.refresh"))), (0, react.createElement)("p", { className: "pbb-intro" }, translateWith(props.t, "meta.intro")), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-switchRow" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("span", { className: "pbb-switchTitle" }, switchTitle), (0, react.createElement)("span", { className: "pbb-switchDesc" }, switchDesc)), (0, react.createElement)("button", {
4202
+ }, translateWith(props.t, "meta.refresh"))), (0, react.createElement)("div", { className: "pbb-card pbb-primaryCard" }, (0, react.createElement)("div", { className: "pbb-cardHeader" }, (0, react.createElement)("div", null, (0, react.createElement)("h3", { className: "pbb-cardTitle" }, translateWith(props.t, "connection.title")), (0, react.createElement)("p", { className: "pbb-cardDescription" }, translateWith(props.t, "connection.description"))), (0, react.createElement)("span", { className: "pbb-statusBadge" }, (0, react.createElement)("span", { className: "pbb-dot" + serviceDot }), serviceStatus)), (0, react.createElement)("div", { className: "pbb-masterRow" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("span", { className: "pbb-switchTitle" }, switchTitle), (0, react.createElement)("span", { className: "pbb-switchDesc" }, switchDesc)), (0, react.createElement)("button", {
3894
4203
  type: "button",
3895
4204
  role: "switch",
3896
4205
  "aria-checked": enabled,
@@ -3900,42 +4209,16 @@ window.__ModuleLoader__.load({
3900
4209
  onClick: () => {
3901
4210
  if (typeof props.setDeepPilotEnabled === "function") props.setDeepPilotEnabled(!enabled);
3902
4211
  }
3903
- })), (0, react.createElement)("div", { className: "pbb-switchRow" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("span", { className: "pbb-switchTitle pbb-dotRow" }, (0, react.createElement)("span", {
3904
- className: "pbb-dot" + (report !== null ? LOCAL_PHASE_META[report.local.phase].dot : ""),
3905
- role: "img",
3906
- "aria-label": report !== null ? translateWith(props.t, LOCAL_PHASE_META[report.local.phase].labelKey) : translateWith(props.t, "phase.unknown"),
3907
- title: report !== null ? translateWith(props.t, LOCAL_PHASE_META[report.local.phase].labelKey) : void 0
3908
- }), translateWith(props.t, "local.title")), (0, react.createElement)("span", { className: "pbb-switchDesc" }, localSwitchReady ? localEnabled ? translateWith(props.t, "local.on", { port: localPort }) : translateWith(props.t, "local.off") : translateWith(props.t, "master.loading")), report !== null && report.local.message && report.local.phase === "error" ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, report.local.message) : null, report !== null && report.local.tlsIdentityRegenerated === true ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "local.tlsRegenerated")) : null, report !== null && report.local.phase === "online" && report.local.tlsFingerprint ? (0, react.createElement)("p", { className: "pbb-diag" }, translateWith(props.t, "local.tlsFingerprint"), " ", (0, react.createElement)("code", { className: "pbb-token" }, report.local.tlsFingerprint)) : null), (0, react.createElement)("button", {
3909
- type: "button",
3910
- role: "switch",
3911
- "aria-checked": localEnabled,
3912
- "aria-label": translateWith(props.t, "local.title"),
3913
- disabled: !localSwitchReady,
3914
- className: "pbb-switch" + (localEnabled ? " pbb-switchOn" : ""),
3915
- onClick: () => {
3916
- if (typeof props.setDeepPilotLocalEnabled === "function") props.setDeepPilotLocalEnabled(!localEnabled);
3917
- }
3918
- })), (0, react.createElement)("div", { className: "pbb-switchRow" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("span", { className: "pbb-switchTitle pbb-dotRow" }, (0, react.createElement)("span", {
3919
- className: "pbb-dot" + (report !== null ? REMOTE_PHASE_META[report.remote.phase].dot : ""),
3920
- role: "img",
3921
- "aria-label": report !== null ? translateWith(props.t, REMOTE_PHASE_META[report.remote.phase].labelKey) : translateWith(props.t, "phase.unknown"),
3922
- title: report !== null ? translateWith(props.t, REMOTE_PHASE_META[report.remote.phase].labelKey) : void 0
3923
- }), translateWith(props.t, "remote.title")), (0, react.createElement)("span", { className: "pbb-switchDesc" }, remoteSwitchReady ? remoteEnabled ? translateWith(props.t, "remote.on") : translateWith(props.t, "remote.off") : translateWith(props.t, "master.loading")), report !== null && report.remote.phase === "login_required" && typeof report.remote.authURL === "string" && report.remote.authURL.startsWith("https://") ? (0, react.createElement)("div", { className: "pbb-rowAction" }, (0, react.createElement)("a", {
3924
- className: "pbb-action",
4212
+ })), (0, react.createElement)("div", { className: "pbb-connectionGrid" }, renderConnectionTile("local", translateWith(props.t, "local.title"), localSwitchReady ? localEnabled ? translateWith(props.t, "local.on", { port: localPort }) : translateWith(props.t, "local.off") : translateWith(props.t, "master.loading"), localEnabled, localSwitchReady, localMeta?.dot ?? "", localMeta === null ? translateWith(props.t, "phase.unknown") : translateWith(props.t, localMeta.labelKey), () => {
4213
+ if (typeof props.setDeepPilotLocalEnabled === "function") props.setDeepPilotLocalEnabled(!localEnabled);
4214
+ }, (0, react.createElement)(react.Fragment, null, report !== null && report.local.message && report.local.phase === "error" ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, report.local.message) : null, report !== null && report.local.tlsIdentityRegenerated === true ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "local.tlsRegenerated")) : null)), renderConnectionTile("remote", translateWith(props.t, "remote.title"), remoteSwitchReady ? remoteEnabled ? translateWith(props.t, "remote.on") : translateWith(props.t, "remote.off") : translateWith(props.t, "master.loading"), remoteEnabled, remoteSwitchReady, remoteMeta?.dot ?? "", remoteMeta === null ? translateWith(props.t, "phase.unknown") : translateWith(props.t, remoteMeta.labelKey), () => {
4215
+ if (typeof props.setDeepPilotRemoteEnabled === "function") props.setDeepPilotRemoteEnabled(!remoteEnabled);
4216
+ }, (0, react.createElement)(react.Fragment, null, report !== null && report.remote.phase === "login_required" && typeof report.remote.authURL === "string" && report.remote.authURL.startsWith("https://") ? (0, react.createElement)("div", { className: "pbb-rowAction" }, (0, react.createElement)("a", {
4217
+ className: "pbb-action pbb-buttonPrimary",
3925
4218
  href: report.remote.authURL,
3926
4219
  target: "_blank",
3927
4220
  rel: "noreferrer"
3928
- }, translateWith(props.t, "remote.openAuth"))) : null, report !== null && report.remote.message && (report.remote.phase === "error" || report.remote.phase === "unavailable") ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, report.remote.message) : null), (0, react.createElement)("button", {
3929
- type: "button",
3930
- role: "switch",
3931
- "aria-checked": remoteEnabled,
3932
- "aria-label": translateWith(props.t, "remote.title"),
3933
- disabled: !remoteSwitchReady,
3934
- className: "pbb-switch" + (remoteEnabled ? " pbb-switchOn" : ""),
3935
- onClick: () => {
3936
- if (typeof props.setDeepPilotRemoteEnabled === "function") props.setDeepPilotRemoteEnabled(!remoteEnabled);
3937
- }
3938
- })), (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "remote.advancedSettings")), (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-limitRow pbb-limitNested" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("label", {
4221
+ }, translateWith(props.t, "remote.openAuth"))) : null, report !== null && report.remote.message && (report.remote.phase === "error" || report.remote.phase === "unavailable") ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, report.remote.message) : null))), report === null ? null : (0, react.createElement)("div", { className: "pbb-statGrid" }, primaryRows), (0, react.createElement)("details", { className: "pbb-inlineDetails" }, (0, react.createElement)("summary", null, translateWith(props.t, "connection.optionsSummary")), (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-limitRow pbb-limitNested" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("label", {
3939
4222
  className: "pbb-switchTitle",
3940
4223
  htmlFor: "deeppilot-local-port"
3941
4224
  }, translateWith(props.t, "local.portTitle")), (0, react.createElement)("span", { className: "pbb-switchDesc" }, translateWith(props.t, "local.portDescription")), !localPortValid ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "local.portInvalid")) : localPortMessage ? (0, react.createElement)("p", { className: "pbb-diag" + (localPortMessage.startsWith(translateWith(props.t, "local.portFailed")) ? " pbb-diagBad" : "") }, localPortMessage) : null), (0, react.createElement)("div", { className: "pbb-limitControl" }, (0, react.createElement)("input", {
@@ -3992,18 +4275,18 @@ window.__ModuleLoader__.load({
3992
4275
  href: "https://tailscale.com/docs/features/tailscale-funnel",
3993
4276
  target: "_blank",
3994
4277
  rel: "noreferrer"
3995
- }, translateWith(props.t, "help.funnelDocs"))))))), report === null ? null : (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "pair.qrPanelTitle")), pairingTarget === null ? (0, react.createElement)("span", { className: "pbb-value pbb-bad" }, translateWith(props.t, "pair.noAddress")) : (0, react.createElement)("span", { className: "pbb-tokenActions" }, (0, react.createElement)("span", { className: "pbb-badge" }, pairingTarget.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan")), (0, react.createElement)("button", {
4278
+ }, translateWith(props.t, "help.funnelDocs"))))))), report === null ? null : (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-cardHeader" }, (0, react.createElement)("div", null, (0, react.createElement)("h3", { className: "pbb-cardTitle" }, translateWith(props.t, "pair.qrPanelTitle")), (0, react.createElement)("p", { className: "pbb-cardDescription" }, translateWith(props.t, "pair.description"))), pairingTarget === null ? (0, react.createElement)("span", { className: "pbb-statusBadge pbb-bad" }, translateWith(props.t, "pair.noAddress")) : (0, react.createElement)("button", {
3996
4279
  type: "button",
3997
- className: "pbb-action",
4280
+ className: "pbb-action pbb-buttonPrimary",
3998
4281
  disabled: panel.busy || !report.pairingReady,
3999
4282
  onClick: () => {
4000
4283
  if (panel.open) hidePairingQR();
4001
4284
  else showPairingQR(pairingTarget);
4002
4285
  }
4003
- }, translateWith(props.t, panel.action)))), pairingTarget === null ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "pair.noAddressHelp")) : null, pairingTargets.length < 2 ? null : (0, react.createElement)("div", { className: "pbb-targetList" }, pairingTargets.map((target) => (0, react.createElement)("button", {
4286
+ }, translateWith(props.t, panel.action))), (0, react.createElement)("div", { className: "pbb-pairingBody" }, pairingTarget === null ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "pair.noAddressHelp")) : null, pairingTargets.length < 2 ? null : (0, react.createElement)("div", { className: "pbb-targetList" }, pairingTargets.map((target) => (0, react.createElement)("button", {
4004
4287
  type: "button",
4005
4288
  key: target.host,
4006
- className: "pbb-action" + (target.host === pairingTarget?.host ? " pbb-actionSelected" : ""),
4289
+ className: "pbb-targetButton" + (target.host === pairingTarget?.host ? " pbb-targetSelected" : ""),
4007
4290
  disabled: panel.busy,
4008
4291
  onClick: () => {
4009
4292
  setSelectedPairingHost(target.host);
@@ -4021,15 +4304,19 @@ window.__ModuleLoader__.load({
4021
4304
  alt: translateWith(props.t, "pair.qrAlt")
4022
4305
  }), panel.link === null ? null : (0, react.createElement)("div", { className: "pbb-pairCodeBlock" }, (0, react.createElement)("span", { className: "pbb-pairCodeLabel" }, translateWith(props.t, "pair.infoLabel")), (0, react.createElement)("div", { className: "pbb-pairCodeRow" }, (0, react.createElement)("code", { className: "pbb-pairCode" }, panel.link), (0, react.createElement)("button", {
4023
4306
  type: "button",
4024
- className: "pbb-action",
4307
+ className: "pbb-action pbb-buttonPrimary",
4025
4308
  onClick: copyPairingInfo,
4026
4309
  "data-testid": "pairingInfoCopy"
4027
- }, translateWith(props.t, "panel.tokenAction.copy")))), (0, react.createElement)("p", { className: "pbb-qrHint" }, translateWith(props.t, "pair.qrHint", { kind: panel.target?.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan") }))) : null)), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, primaryRows, advancedRows.length > 0 ? (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "advanced.summary")), (0, react.createElement)("div", { className: "pbb-helpBody" }, advancedRows)) : null)), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "push.relayTitle")), (0, react.createElement)("span", { className: "pbb-tokenActions" }, (0, react.createElement)("button", {
4310
+ }, translateWith(props.t, "panel.tokenAction.copy")))), (0, react.createElement)("p", { className: "pbb-qrHint" }, translateWith(props.t, "pair.qrHint", { kind: panel.target?.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan") }))) : null)), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-cardHeader" }, (0, react.createElement)("div", null, (0, react.createElement)("h3", { className: "pbb-cardTitle" }, translateWith(props.t, "devices.title")), (0, react.createElement)("p", { className: "pbb-cardDescription" }, translateWith(props.t, "devices.description"))), (0, react.createElement)("span", { className: "pbb-countBadge" }, String(visibleDevices.length))), (0, react.createElement)("div", { className: "pbb-cardBody" }, deviceMessage ? (0, react.createElement)("p", { className: "pbb-diag" + (deviceMessage.startsWith(translateWith(props.t, "devices.renameFailed")) || deviceMessage.startsWith(translateWith(props.t, "devices.revokeFailed")) ? " pbb-diagBad" : "") }, deviceMessage) : null, deviceTable)), (0, react.createElement)("details", {
4311
+ className: "pbb-card pbb-disclosure",
4312
+ open: troubleshootingOpen,
4313
+ onToggle: (event) => setTroubleshootingOpen(event.currentTarget.open)
4314
+ }, (0, react.createElement)("summary", null, (0, react.createElement)("span", { className: "pbb-disclosureTitle" }, translateWith(props.t, "troubleshooting.summary")), (0, react.createElement)("span", { className: "pbb-disclosureMeta" }, relayTestResult !== null ? relayTestResult.overall === "ok" ? translateWith(props.t, "push.relayOk") : translateWith(props.t, "push.relayBad") : pushTestResult !== null ? pushTestResult.overall === "sent" ? translateWith(props.t, "push.pushSent") : translateWith(props.t, "push.pushFailed") : translateWith(props.t, "troubleshooting.title"))), (0, react.createElement)("div", { className: "pbb-disclosureBody" }, (0, react.createElement)("div", { className: "pbb-sectionIntro" }, (0, react.createElement)("h3", { className: "pbb-cardTitle" }, translateWith(props.t, "troubleshooting.title")), (0, react.createElement)("p", { className: "pbb-cardDescription" }, translateWith(props.t, "troubleshooting.description"))), (0, react.createElement)("div", { className: "pbb-testGrid" }, (0, react.createElement)("div", { className: "pbb-testItem" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("span", { className: "pbb-testTitle" }, translateWith(props.t, "push.relayTitle")), (0, react.createElement)("span", { className: "pbb-testDescription" }, translateWith(props.t, "push.relayDefault"))), (0, react.createElement)("button", {
4028
4315
  type: "button",
4029
4316
  className: "pbb-action",
4030
4317
  disabled: relayTestBusy,
4031
4318
  onClick: runRelayTest
4032
- }, relayTestBusy ? translateWith(props.t, "push.relayTesting") : translateWith(props.t, "push.testRelay")), (0, react.createElement)("button", {
4319
+ }, relayTestBusy ? translateWith(props.t, "push.relayTesting") : translateWith(props.t, "push.testRelay"))), (0, react.createElement)("div", { className: "pbb-testItem" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("span", { className: "pbb-testTitle" }, translateWith(props.t, "push.testPush")), (0, react.createElement)("span", { className: "pbb-testDescription" }, translateWith(props.t, "push.pushDefault"))), (0, react.createElement)("button", {
4033
4320
  type: "button",
4034
4321
  className: "pbb-action",
4035
4322
  disabled: pushTestBusy,
@@ -4040,11 +4327,15 @@ window.__ModuleLoader__.load({
4040
4327
  }, (step.ok ? "✓ " : "✗ ") + (step.id === "health" ? translateWith(props.t, "push.relayStep.health") : translateWith(props.t, "push.relayStep.enroll")) + (step.latencyMs !== void 0 ? ` (${String(step.latencyMs)}ms)` : "") + " — " + step.message))), pushTestError ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, pushTestError) : null, pushTestResult === null ? (0, react.createElement)("p", { className: "pbb-diag" }, translateWith(props.t, "push.pushDefault")) : (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("code", { className: "pbb-token " + (pushTestResult.overall === "sent" ? "pbb-ok" : pushTestResult.overall === "failed" ? "pbb-bad" : "") }, pushTestResult.overall === "sent" ? translateWith(props.t, "push.pushSent") : pushTestResult.overall === "failed" ? translateWith(props.t, "push.pushFailed") : pushTestResult.overall === "no-targets" ? translateWith(props.t, "push.pushNoTargets") : translateWith(props.t, "push.pushNotEnabled"))), pushTestResult.message ? (0, react.createElement)("p", { className: "pbb-diag" }, pushTestResult.message) : null, pushTestResult.results.map((r, index) => (0, react.createElement)("p", {
4041
4328
  className: "pbb-diag",
4042
4329
  key: String(index)
4043
- }, (r.outcome === "sent" ? "✓ " : "✗ ") + r.name + " [" + r.environment + "] — " + r.outcome + (r.reason ? "(" + r.reason + ")" : "") + (r.tokenFingerprint ? " token:" + r.tokenFingerprint + "…" : "")))))), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "devices.title")), (0, react.createElement)("span", { className: "pbb-badge" }, String(visibleDevices.length))), deviceMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, deviceMessage) : null, deviceTable)), report === null ? null : (0, react.createElement)("div", { className: "pbb-versionFooter" }, (0, react.createElement)("span", null, "DeepPilot v" + report.pluginVersion), report.updateAvailable === true ? (0, react.createElement)("a", {
4330
+ }, (r.outcome === "sent" ? "✓ " : "✗ ") + r.name + " [" + r.environment + "] — " + r.outcome + (r.reason ? "(" + r.reason + ")" : "") + (r.tokenFingerprint ? " token:" + r.tokenFingerprint + "…" : "")))))), report === null ? null : (0, react.createElement)("details", { className: "pbb-card pbb-disclosure" }, (0, react.createElement)("summary", null, (0, react.createElement)("span", { className: "pbb-disclosureTitle" }, translateWith(props.t, "system.summary")), (0, react.createElement)("span", { className: "pbb-disclosureMeta" }, "v" + report.pluginVersion)), (0, react.createElement)("div", { className: "pbb-disclosureBody" }, (0, react.createElement)("div", { className: "pbb-sectionIntro" }, (0, react.createElement)("h3", { className: "pbb-cardTitle" }, translateWith(props.t, "system.title")), (0, react.createElement)("p", { className: "pbb-cardDescription" }, translateWith(props.t, "system.description"))), (0, react.createElement)("div", { className: "pbb-runtimeList" }, (0, react.createElement)("div", { className: "pbb-runtimeRow" }, (0, react.createElement)("span", { className: "pbb-label" }, "DeepPilot"), (0, react.createElement)("span", { className: "pbb-value" }, "v" + report.pluginVersion)), advancedRows, report.updateAvailable === true ? (0, react.createElement)("div", { className: "pbb-updateRow" }, (0, react.createElement)("span", null, translateWith(props.t, "update.badge")), (0, react.createElement)("a", {
4331
+ className: "pbb-action pbb-buttonPrimary",
4044
4332
  href: typeof report.releaseUrl === "string" && /^https:\/\//.test(report.releaseUrl) ? report.releaseUrl : "https://github.com/Mars-Sea/dsh-deeppilot/releases",
4045
4333
  target: "_blank",
4046
4334
  rel: "noreferrer"
4047
- }, translateWith(props.t, "update.badge")) : null), diag.length > 0 ? (0, react.createElement)("p", { className: "pbb-diag" + (failed ? " pbb-diagBad" : "") }, translateWith(props.t, "diag.prefix") + diag.join(" | ")) : null);
4335
+ }, translateWith(props.t, "update.badge"))) : null))), diag.length > 0 ? (0, react.createElement)("details", {
4336
+ className: "pbb-card pbb-disclosure pbb-diagnostics",
4337
+ open: failed
4338
+ }, (0, react.createElement)("summary", null, translateWith(props.t, "diag.summary")), (0, react.createElement)("div", { className: "pbb-disclosureBody" }, (0, react.createElement)("p", { className: "pbb-diag" + (failed ? " pbb-diagBad" : "") }, translateWith(props.t, "diag.prefix") + diag.join(" | ")))) : null);
4048
4339
  }
4049
4340
  //#endregion
4050
4341
  //#region src/client/config-form.ts
@@ -4094,7 +4385,7 @@ window.__ModuleLoader__.load({
4094
4385
  };
4095
4386
  }
4096
4387
  /**
4097
- * Bind the rc.1 form if its service is already provisioned. Callers that
4388
+ * Bind the rc.2 form if its service is already provisioned. Callers that
4098
4389
  * apply first retry inside `ctx.inject(['configForms'], …)`.
4099
4390
  */
4100
4391
  function bindConfigForm(ctx) {
@@ -4221,6 +4512,14 @@ window.__ModuleLoader__.load({
4221
4512
  if (!result.ok || result.value !== true) throw new Error(result.ok ? t(ctx, "devices.revokeFailed") : result.error.message ?? t(ctx, "devices.revokeFailed"));
4222
4513
  await controller.refresh();
4223
4514
  };
4515
+ const setDeviceName = async (deviceId, customName) => {
4516
+ if (namespace === void 0) throw new Error(mountError !== void 0 ? t(ctx, "diag.mountFailedShort") + mountError : t(ctx, "diag.remoteUnmounted"));
4517
+ if (typeof namespace.setDeviceName !== "function") throw new Error(t(ctx, "devices.renameStaleHost"));
4518
+ const result = await namespace.setDeviceName(deviceId, customName);
4519
+ if (!result.ok) throw new Error(result.error.message ?? t(ctx, "devices.renameFailed"));
4520
+ await controller.refresh();
4521
+ return result.value;
4522
+ };
4224
4523
  const sendTestPush = async () => {
4225
4524
  if (namespace === void 0) throw new Error(mountError !== void 0 ? t(ctx, "diag.mountFailedShort") + mountError : t(ctx, "diag.remoteUnmounted"));
4226
4525
  if (typeof namespace.testPush !== "function") throw new Error(t(ctx, "push.staleHostPush"));
@@ -4491,6 +4790,7 @@ window.__ModuleLoader__.load({
4491
4790
  },
4492
4791
  beginPairing,
4493
4792
  revokeDevice,
4793
+ setDeviceName,
4494
4794
  testRelay: testRelayConnection,
4495
4795
  testPush: sendTestPush,
4496
4796
  setDeepPilotEnabled,