dsh-deeppilot 0.8.1 → 0.8.3

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,28 +302,11 @@ window.__ModuleLoader__.load({
290
302
  if (typeof value !== "boolean") reject("boolean");
291
303
  return value;
292
304
  } };
293
- /**
294
- * One strict boundary codec carrying both generation shapes of the supported
295
- * `0.1.x` host family.
296
- *
297
- * Through `0.1.6-alpha.1` a descriptor published the schema value itself and
298
- * every consumer parsed through `codec.schema.parse(value)`. `0.1.6-alpha.2`
299
- * switched to a lazily materialized process-realm factory: `TypertRegistry`
300
- * now rejects a strict codec without `create()`, and the Gateway parses
301
- * through `codec.create().parse(value)`.
302
- *
303
- * Publishing both keys — `create()` returning the same hand-written,
304
- * dependency-free codec — keeps one package valid across the entire declared
305
- * peer range instead of forking the wire contract per host generation. The
306
- * cast is required because each generation's `TypertCodec` declares only its
307
- * own key, so a fresh object literal carrying both fails excess-property
308
- * checking against either one.
309
- */
305
+ /** The rc.2 Gateway materializes each strict codec through `create()`. */
310
306
  function strictCodec(typeSymbol, schema) {
311
307
  return {
312
308
  mode: "strict",
313
309
  typeSymbol,
314
- schema,
315
310
  create: () => schema
316
311
  };
317
312
  }
@@ -350,6 +345,25 @@ window.__ModuleLoader__.load({
350
345
  }],
351
346
  result: strictCodec(`${REPORT_REMOTE_PACKAGE}#Boolean`, booleanSchema)
352
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
+ },
353
367
  {
354
368
  id: `${REPORT_REMOTE_PACKAGE}#${SET_DEVICE_SCOPES_ENDPOINT}`,
355
369
  service: "deeppilotReport",
@@ -431,6 +445,21 @@ window.__ModuleLoader__.load({
431
445
  "master.loading": "正在读取配置…",
432
446
  "master.on": "已开启:接受手机连接。",
433
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": "插件、协议与连接端点的技术信息。",
434
463
  "local.title": "局域网连接",
435
464
  "local.on": "已开启:手机可通过独立端口 {port} 以 TLS 加密连接。",
436
465
  "local.off": "已关闭:不接受局域网连接。",
@@ -535,6 +564,20 @@ window.__ModuleLoader__.load({
535
564
  "devices.col.lastSeen": "最近在线",
536
565
  "devices.col.fingerprint": "密钥指纹",
537
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",
538
581
  "devices.revoke": "删除",
539
582
  "devices.revokeAria": "删除设备“{name}”",
540
583
  "devices.deleting": "删除中…",
@@ -570,16 +613,17 @@ window.__ModuleLoader__.load({
570
613
  "help.security1": "Funnel 公网地址可从互联网访问,但连接必须使用已配对设备的 P-256 私钥签名;不要分享仍在有效期内的配对二维码。",
571
614
  "help.security2": "丢失设备时可在设备列表中单独删除;该设备会立即断开,不影响其他手机。",
572
615
  "help.security3": "Funnel 和局域网监听器都只提供 /phone、/phone/pair 和 /phone/health,不会暴露完整 DSH Web/API。",
573
- "help.security4": "局域网连接使用插件独立的 3098 端口和明文 WebSocket。只在可信网络中使用,不要把 3098 转发到公网。",
616
+ "help.security4": "局域网连接使用插件独立的 3098 端口并启用 TLS。只在可信网络中使用,不要把该端口转发到公网。",
574
617
  "help.security5": "关闭远程连接开关会停止 Funnel,但不会影响可用的局域网连接。",
575
618
  "help.funnelDocs": "Tailscale Funnel 官方文档",
576
619
  "help.docsPrefix": "更多信息:",
620
+ "diag.summary": "技术诊断",
577
621
  "diag.remoteUnavailable": "remote 服务不可用",
578
622
  "diag.remoteUnmounted": "report remote 未挂载",
579
623
  "diag.mountFailed": "报告远程未挂载(remote mount 失败)— 请确认宿主包含 typert 组合",
580
624
  "diag.mountFailedShort": "remote mount 失败: ",
581
625
  "diag.callFailed": "report remote 调用失败",
582
- "diag.settingsUnavailable": "设置命名空间不可用(settingsScope 未提供)",
626
+ "diag.settingsUnavailable": "设置表单不可用(configForms 未提供)",
583
627
  "diag.missingReportHook": "useDeepPilotReport hook 缺失",
584
628
  "diag.missingEnabledHook": "useDeepPilotEnabled hook 缺失",
585
629
  "diag.missingLocalEnabledHook": "useDeepPilotLocalEnabled hook 缺失",
@@ -589,6 +633,7 @@ window.__ModuleLoader__.load({
589
633
  "diag.missingRefresh": "refresh 回调缺失",
590
634
  "diag.missingReveal": "beginPairing 回调缺失",
591
635
  "diag.missingRotate": "revokeDevice 回调缺失",
636
+ "diag.missingRename": "setDeviceName 回调缺失",
592
637
  "diag.missingTestRelay": "testRelay 回调缺失",
593
638
  "diag.missingTestPush": "testPush 回调缺失",
594
639
  "diag.missingSetEnabled": "setDeepPilotEnabled 回调缺失",
@@ -614,6 +659,21 @@ window.__ModuleLoader__.load({
614
659
  "master.loading": "Loading settings…",
615
660
  "master.on": "On: accepting phone connections.",
616
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.",
617
677
  "local.title": "LAN connection",
618
678
  "local.on": "On: phones connect over TLS through the independent port {port}.",
619
679
  "local.off": "Off: LAN connections are not accepted.",
@@ -718,6 +778,20 @@ window.__ModuleLoader__.load({
718
778
  "devices.col.lastSeen": "Last seen",
719
779
  "devices.col.fingerprint": "Key fingerprint",
720
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",
721
795
  "devices.revoke": "Delete",
722
796
  "devices.revokeAria": "Delete device “{name}”",
723
797
  "devices.deleting": "Deleting…",
@@ -753,16 +827,17 @@ window.__ModuleLoader__.load({
753
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.",
754
828
  "help.security2": "If a device is lost, delete only that device from the list. It disconnects immediately; other phones stay paired.",
755
829
  "help.security3": "Funnel and the LAN listener expose only /phone, /phone/pair, and /phone/health, never the full DSH Web/API surface.",
756
- "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.",
757
831
  "help.security5": "Disabling the remote switch stops Funnel but does not affect any LAN connection you already have.",
758
832
  "help.funnelDocs": "Tailscale Funnel docs",
759
833
  "help.docsPrefix": "More info: ",
834
+ "diag.summary": "Technical diagnostics",
760
835
  "diag.remoteUnavailable": "remote service unavailable",
761
836
  "diag.remoteUnmounted": "report remote not mounted",
762
837
  "diag.mountFailed": "Report remote not mounted (mount failed) — confirm the host includes the typert composition",
763
838
  "diag.mountFailedShort": "remote mount failed: ",
764
839
  "diag.callFailed": "report remote call failed",
765
- "diag.settingsUnavailable": "Settings namespace unavailable (settingsScope not provided)",
840
+ "diag.settingsUnavailable": "Settings form unavailable (configForms not provided)",
766
841
  "diag.missingReportHook": "useDeepPilotReport hook missing",
767
842
  "diag.missingEnabledHook": "useDeepPilotEnabled hook missing",
768
843
  "diag.missingLocalEnabledHook": "useDeepPilotLocalEnabled hook missing",
@@ -772,6 +847,7 @@ window.__ModuleLoader__.load({
772
847
  "diag.missingRefresh": "refresh callback missing",
773
848
  "diag.missingReveal": "beginPairing callback missing",
774
849
  "diag.missingRotate": "revokeDevice callback missing",
850
+ "diag.missingRename": "setDeviceName callback missing",
775
851
  "diag.missingTestRelay": "testRelay callback missing",
776
852
  "diag.missingTestPush": "testPush callback missing",
777
853
  "diag.missingSetEnabled": "setDeepPilotEnabled callback missing",
@@ -854,87 +930,154 @@ window.__ModuleLoader__.load({
854
930
  //#endregion
855
931
  //#region src/client/styles.ts
856
932
  const CSS = [
857
- ".pbb-section{max-width:720px;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:12px}",
858
- ".pbb-title{margin:0;font-size:18px;font-weight:600}",
859
- ".pbb-intro{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5;margin:0}",
860
- ".pbb-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:4px 16px}",
861
- ".pbb-field{display:flex;flex-direction:column;gap:6px;padding:12px 0}",
862
- ".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}",
863
947
  ".pbb-row{display:flex;align-items:center;gap:8px}",
864
- ".pbb-label{flex:1;font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary);min-width:0}",
865
- ".pbb-value{font-size:13px;color:var(--dsw-alias-label-secondary);word-break:break-all;text-align:right}",
866
- ".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}",
867
953
  ".pbb-ok{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary));font-weight:500}",
868
954
  ".pbb-bad{color:var(--dsw-alias-label-error);font-weight:500}",
869
- ".pbb-table{width:100%;border-collapse:collapse;font-size:12px}",
870
- ".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)}",
871
- ".pbb-table td{padding:6px 8px;border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}",
872
- ".pbb-empty{color:var(--dsw-alias-label-tertiary);font-size:12px;margin:0}",
873
- ".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}",
874
956
  ".pbb-diagBad{color:var(--dsw-alias-label-error)}",
875
- ".pbb-refresh{font:inherit;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:none;border:none;padding:0}",
876
- ".pbb-refresh:hover:not(:disabled){color:var(--dsw-alias-label-primary)}",
877
- ".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}",
878
958
  ".pbb-tokenRow{flex-wrap:wrap}",
879
- ".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}",
880
960
  ".pbb-tokenActions{display:flex;align-items:center;gap:8px}",
881
- ".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}",
882
962
  ".pbb-action:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform)}",
883
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)}",
884
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)}",
885
- ".pbb-targetList{display:flex;flex-wrap:wrap;gap:6px;padding-top:4px}",
886
- ".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}",
887
- ".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)}",
888
- ".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)}",
889
970
  ".pbb-actionDangerGhost:focus-visible{outline:2px solid rgba(220,38,38,.32);outline-offset:1px}",
890
971
  ".pbb-actionDangerGhost:disabled{cursor:default;opacity:.62}",
891
972
  ".pbb-actionIcon{flex:none;display:inline-block;vertical-align:-2px;margin-right:5px;pointer-events:none}",
892
973
  ".pbb-actionBusy{display:inline-flex;align-items:center;gap:6px}",
893
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}",
894
975
  "@keyframes pbb-spin{to{transform:rotate(360deg)}}",
895
- ".pbb-qrPanel{display:flex;flex-direction:column;align-items:center;gap:9px;padding:12px 0 4px}",
896
- ".pbb-qrImage{width:240px;height:240px;max-width:100%;background:#fff;border-radius:10px;padding:8px;box-sizing:border-box}",
897
- ".pbb-pairCodeBlock{width:min(100%,420px);display:flex;flex-direction:column;gap:5px}",
898
- ".pbb-pairCodeLabel{font-size:11px;color:var(--dsw-alias-label-tertiary);text-align:center}",
899
- ".pbb-pairCodeRow{display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap}",
900
- ".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}",
901
- ".pbb-qrHint{max-width:420px;text-align:center;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-tertiary);margin:0}",
902
- ".pbb-switchRow{padding:12px 0;display:flex;align-items:center;gap:12px}",
903
- ".pbb-switchRow+.pbb-field{border-top:1px solid var(--dsw-alias-border-l2)}",
904
- ".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}",
905
978
  ".pbb-switchTitle{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}",
906
- ".pbb-dotRow{display:flex;align-items:center;gap:7px}",
907
- ".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}",
908
991
  ".pbb-dotOk{background:var(--dsw-alias-state-success-primary,#22a06b)}",
909
992
  ".pbb-dotWarn{background:var(--dsw-alias-state-warning-primary,#e2b203)}",
910
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}",
911
996
  ".pbb-rowAction{display:flex;gap:8px;margin-top:4px}",
912
- ".pbb-switchDesc{font-size:11px;color:var(--dsw-alias-label-tertiary);line-height:1.4}",
913
- ".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}",
914
- ".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}",
915
- ".pbb-switchOn{background:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}",
916
- ".pbb-switchOn::after{transform:translateX(16px);background:#fff}",
917
- ".pbb-switch:disabled{opacity:.5;cursor:default}",
918
- ".pbb-limitRow{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 0;display:flex;align-items:center;gap:12px}",
919
- ".pbb-limitNested{border-top:0;padding:0}",
920
- ".pbb-limitControl{display:flex;align-items:center;gap:8px;flex:none}",
921
- ".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}",
922
- ".pbb-numberInput:focus{outline:2px solid var(--dsw-alias-state-success-primary,#22a06b);outline-offset:1px}",
923
- ".pbb-numberInput:disabled{opacity:.5}",
924
- ".pbb-help{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 0}",
925
- ".pbb-help summary{cursor:pointer;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;user-select:none}",
926
- ".pbb-help summary:hover{color:var(--dsw-alias-label-secondary)}",
927
- ".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}",
928
1008
  ".pbb-helpSection{display:flex;flex-direction:column;gap:6px}",
929
1009
  ".pbb-helpHeading{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary)}",
930
- ".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}",
931
1011
  ".pbb-helpList li+li{margin-top:4px}",
932
- ".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}",
933
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}",
934
1014
  ".pbb-helpLink{color:var(--dsw-alias-label-secondary);text-decoration:underline;text-underline-offset:2px}",
935
- ".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}",
936
- ".pbb-versionFooter a{color:var(--dsw-alias-state-success-primary,#22a06b);text-decoration:none}",
937
- ".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}}"
938
1081
  ].join("\n");
939
1082
  /**
940
1083
  * Client bundle id, identical to the `window.__ModuleLoader__.load({ id })`
@@ -949,8 +1092,8 @@ window.__ModuleLoader__.load({
949
1092
  * materialization; this sheet is injected from `apply()`, which runs after
950
1093
  * that point, so it would never be attributed to us. Stamping `data-plugin`
951
1094
  * ourselves lets the module system's `removeOwnedStyles(id)` delete the sheet
952
- * when the plugin is unloaded at runtime — the Plugin Manager and the client
953
- * entry reconciler both support live removal from 0.1.6-alpha.2 on, and an
1095
+ * when the plugin is unloaded at runtime — the rc.2 Plugin Manager and client
1096
+ * entry reconciler both support live removal, and an
954
1097
  * orphaned sheet would otherwise keep styling a page we no longer own.
955
1098
  */
956
1099
  function injectCss() {
@@ -2383,8 +2526,8 @@ window.__ModuleLoader__.load({
2383
2526
  return AlphanumericData.getBitsLength(this.data.length);
2384
2527
  };
2385
2528
  AlphanumericData.prototype.write = function write(bitBuffer) {
2386
- let i;
2387
- for (i = 0; i + 2 <= this.data.length; i += 2) {
2529
+ let i = 0;
2530
+ for (; i + 2 <= this.data.length; i += 2) {
2388
2531
  let value = ALPHA_NUM_CHARS.indexOf(this.data[i]) * 45;
2389
2532
  value += ALPHA_NUM_CHARS.indexOf(this.data[i + 1]);
2390
2533
  bitBuffer.put(value, 11);
@@ -2435,8 +2578,8 @@ window.__ModuleLoader__.load({
2435
2578
  return KanjiData.getBitsLength(this.data.length);
2436
2579
  };
2437
2580
  KanjiData.prototype.write = function(bitBuffer) {
2438
- let i;
2439
- for (i = 0; i < this.data.length; i++) {
2581
+ let i = 0;
2582
+ for (; i < this.data.length; i++) {
2440
2583
  let value = Utils.toSJIS(this.data[i]);
2441
2584
  if (value >= 33088 && value <= 40956) value -= 33088;
2442
2585
  else if (value >= 57408 && value <= 60351) value -= 49472;
@@ -3533,6 +3676,29 @@ window.__ModuleLoader__.load({
3533
3676
  strokeLinejoin: "round",
3534
3677
  "aria-hidden": true
3535
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" }));
3536
3702
  async function writeClipboard(t, value) {
3537
3703
  try {
3538
3704
  await navigator.clipboard.writeText(value);
@@ -3613,6 +3779,7 @@ window.__ModuleLoader__.load({
3613
3779
  const [selectedPairingHost, setSelectedPairingHost] = (0, react.useState)(null);
3614
3780
  const [pairingPanel, dispatchPairingPanel] = (0, react.useReducer)(pairingPanelReduce, initialPairingPanelState);
3615
3781
  const qrRequestId = (0, react.useRef)(0);
3782
+ const [troubleshootingOpen, setTroubleshootingOpen] = (0, react.useState)(false);
3616
3783
  const [relayTestBusy, setRelayTestBusy] = (0, react.useState)(false);
3617
3784
  const [relayTestResult, setRelayTestResult] = (0, react.useState)(null);
3618
3785
  const [relayTestError, setRelayTestError] = (0, react.useState)("");
@@ -3620,7 +3787,10 @@ window.__ModuleLoader__.load({
3620
3787
  const [pushTestResult, setPushTestResult] = (0, react.useState)(null);
3621
3788
  const [pushTestError, setPushTestError] = (0, react.useState)("");
3622
3789
  const [deviceBusy, setDeviceBusy] = (0, react.useState)(null);
3790
+ const [deviceBusyAction, setDeviceBusyAction] = (0, react.useState)(null);
3623
3791
  const [deviceMessage, setDeviceMessage] = (0, react.useState)("");
3792
+ const [editingDeviceId, setEditingDeviceId] = (0, react.useState)(null);
3793
+ const [deviceNameDraft, setDeviceNameDraft] = (0, react.useState)("");
3624
3794
  (0, react.useEffect)(() => {
3625
3795
  if (typeof props.refresh !== "function") return;
3626
3796
  props.refresh();
@@ -3633,6 +3803,7 @@ window.__ModuleLoader__.load({
3633
3803
  setPushTestError(translateWith(props.t, "push.staleHost"));
3634
3804
  return;
3635
3805
  }
3806
+ setTroubleshootingOpen(true);
3636
3807
  setPushTestBusy(true);
3637
3808
  setPushTestError("");
3638
3809
  props.testPush().then((result) => {
@@ -3649,6 +3820,7 @@ window.__ModuleLoader__.load({
3649
3820
  setRelayTestError(translateWith(props.t, "push.staleHostRelay"));
3650
3821
  return;
3651
3822
  }
3823
+ setTroubleshootingOpen(true);
3652
3824
  setRelayTestBusy(true);
3653
3825
  setRelayTestError("");
3654
3826
  props.testRelay().then((result) => {
@@ -3719,6 +3891,7 @@ window.__ModuleLoader__.load({
3719
3891
  if (typeof props.refresh !== "function") diag.push(translateWith(props.t, "diag.missingRefresh"));
3720
3892
  if (typeof props.beginPairing !== "function") diag.push(translateWith(props.t, "diag.missingReveal"));
3721
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"));
3722
3895
  if (typeof props.testRelay !== "function") diag.push(translateWith(props.t, "diag.missingTestRelay"));
3723
3896
  if (typeof props.testPush !== "function") diag.push(translateWith(props.t, "diag.missingTestPush"));
3724
3897
  if (typeof props.setDeepPilotEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetEnabled"));
@@ -3861,23 +4034,27 @@ window.__ModuleLoader__.load({
3861
4034
  const advancedRows = [];
3862
4035
  if (report !== null) {
3863
4036
  primaryRows.push((0, react.createElement)("div", {
3864
- className: "pbb-field",
4037
+ className: "pbb-stat",
3865
4038
  key: "conn"
3866
- }, (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", {
3867
- 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",
3868
4041
  key: "identity"
3869
- }, (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))));
3870
4047
  advancedRows.push((0, react.createElement)("div", {
3871
- className: "pbb-field",
4048
+ className: "pbb-runtimeRow",
3872
4049
  key: "proto"
3873
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", {
3874
- className: "pbb-field",
4051
+ className: "pbb-runtimeRow",
3875
4052
  key: "server"
3876
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", {
3877
- className: "pbb-field",
4054
+ className: "pbb-runtimeRow",
3878
4055
  key: "path"
3879
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", {
3880
- className: "pbb-field",
4057
+ className: "pbb-runtimeRow",
3881
4058
  key: "buffer"
3882
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")))));
3883
4060
  }
@@ -3885,29 +4062,144 @@ window.__ModuleLoader__.load({
3885
4062
  if (typeof props.revokeDevice !== "function") return;
3886
4063
  if (typeof window !== "undefined" && !window.confirm(translateWith(props.t, "devices.revokeConfirm", { name }))) return;
3887
4064
  setDeviceBusy(deviceId);
4065
+ setDeviceBusyAction("revoke");
3888
4066
  setDeviceMessage("");
3889
4067
  props.revokeDevice(deviceId).then(() => {
3890
4068
  setDeviceMessage(translateWith(props.t, "devices.revoked"));
3891
4069
  }, (error) => {
3892
4070
  setDeviceMessage(translateWith(props.t, "devices.revokeFailed") + (error instanceof Error ? error.message : String(error)));
3893
- }).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
+ });
3894
4107
  };
3895
4108
  const visibleDevices = report?.devices.filter((device) => device.revokedAt === void 0) ?? [];
3896
- 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", {
3897
- type: "button",
3898
- className: "pbb-action pbb-actionDangerGhost",
3899
- disabled: deviceBusy === d.deviceId,
3900
- "aria-label": deviceBusy === d.deviceId ? translateWith(props.t, "devices.deleting", { name: d.deviceName }) : translateWith(props.t, "devices.revokeAria", { name: d.deviceName }),
3901
- onClick: () => revokeDevice(d.deviceId, d.deviceName)
3902
- }, 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"));
3903
4171
  const switchTitle = translateWith(props.t, "master.title");
3904
4172
  const switchDesc = switchReady ? enabled ? translateWith(props.t, "master.on") : translateWith(props.t, "master.off") : translateWith(props.t, "master.loading");
3905
- 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",
3906
4198
  className: "pbb-refresh",
3907
4199
  onClick: () => {
3908
4200
  if (typeof props.refresh === "function") props.refresh();
3909
4201
  }
3910
- }, 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", {
3911
4203
  type: "button",
3912
4204
  role: "switch",
3913
4205
  "aria-checked": enabled,
@@ -3917,42 +4209,16 @@ window.__ModuleLoader__.load({
3917
4209
  onClick: () => {
3918
4210
  if (typeof props.setDeepPilotEnabled === "function") props.setDeepPilotEnabled(!enabled);
3919
4211
  }
3920
- })), (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", {
3921
- className: "pbb-dot" + (report !== null ? LOCAL_PHASE_META[report.local.phase].dot : ""),
3922
- role: "img",
3923
- "aria-label": report !== null ? translateWith(props.t, LOCAL_PHASE_META[report.local.phase].labelKey) : translateWith(props.t, "phase.unknown"),
3924
- title: report !== null ? translateWith(props.t, LOCAL_PHASE_META[report.local.phase].labelKey) : void 0
3925
- }), 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", {
3926
- type: "button",
3927
- role: "switch",
3928
- "aria-checked": localEnabled,
3929
- "aria-label": translateWith(props.t, "local.title"),
3930
- disabled: !localSwitchReady,
3931
- className: "pbb-switch" + (localEnabled ? " pbb-switchOn" : ""),
3932
- onClick: () => {
3933
- if (typeof props.setDeepPilotLocalEnabled === "function") props.setDeepPilotLocalEnabled(!localEnabled);
3934
- }
3935
- })), (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", {
3936
- className: "pbb-dot" + (report !== null ? REMOTE_PHASE_META[report.remote.phase].dot : ""),
3937
- role: "img",
3938
- "aria-label": report !== null ? translateWith(props.t, REMOTE_PHASE_META[report.remote.phase].labelKey) : translateWith(props.t, "phase.unknown"),
3939
- title: report !== null ? translateWith(props.t, REMOTE_PHASE_META[report.remote.phase].labelKey) : void 0
3940
- }), 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", {
3941
- 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",
3942
4218
  href: report.remote.authURL,
3943
4219
  target: "_blank",
3944
4220
  rel: "noreferrer"
3945
- }, 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", {
3946
- type: "button",
3947
- role: "switch",
3948
- "aria-checked": remoteEnabled,
3949
- "aria-label": translateWith(props.t, "remote.title"),
3950
- disabled: !remoteSwitchReady,
3951
- className: "pbb-switch" + (remoteEnabled ? " pbb-switchOn" : ""),
3952
- onClick: () => {
3953
- if (typeof props.setDeepPilotRemoteEnabled === "function") props.setDeepPilotRemoteEnabled(!remoteEnabled);
3954
- }
3955
- })), (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", {
3956
4222
  className: "pbb-switchTitle",
3957
4223
  htmlFor: "deeppilot-local-port"
3958
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", {
@@ -4009,18 +4275,18 @@ window.__ModuleLoader__.load({
4009
4275
  href: "https://tailscale.com/docs/features/tailscale-funnel",
4010
4276
  target: "_blank",
4011
4277
  rel: "noreferrer"
4012
- }, 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", {
4013
4279
  type: "button",
4014
- className: "pbb-action",
4280
+ className: "pbb-action pbb-buttonPrimary",
4015
4281
  disabled: panel.busy || !report.pairingReady,
4016
4282
  onClick: () => {
4017
4283
  if (panel.open) hidePairingQR();
4018
4284
  else showPairingQR(pairingTarget);
4019
4285
  }
4020
- }, 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", {
4021
4287
  type: "button",
4022
4288
  key: target.host,
4023
- className: "pbb-action" + (target.host === pairingTarget?.host ? " pbb-actionSelected" : ""),
4289
+ className: "pbb-targetButton" + (target.host === pairingTarget?.host ? " pbb-targetSelected" : ""),
4024
4290
  disabled: panel.busy,
4025
4291
  onClick: () => {
4026
4292
  setSelectedPairingHost(target.host);
@@ -4038,15 +4304,19 @@ window.__ModuleLoader__.load({
4038
4304
  alt: translateWith(props.t, "pair.qrAlt")
4039
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", {
4040
4306
  type: "button",
4041
- className: "pbb-action",
4307
+ className: "pbb-action pbb-buttonPrimary",
4042
4308
  onClick: copyPairingInfo,
4043
4309
  "data-testid": "pairingInfoCopy"
4044
- }, 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", {
4045
4315
  type: "button",
4046
4316
  className: "pbb-action",
4047
4317
  disabled: relayTestBusy,
4048
4318
  onClick: runRelayTest
4049
- }, 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", {
4050
4320
  type: "button",
4051
4321
  className: "pbb-action",
4052
4322
  disabled: pushTestBusy,
@@ -4057,26 +4327,23 @@ window.__ModuleLoader__.load({
4057
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", {
4058
4328
  className: "pbb-diag",
4059
4329
  key: String(index)
4060
- }, (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",
4061
4332
  href: typeof report.releaseUrl === "string" && /^https:\/\//.test(report.releaseUrl) ? report.releaseUrl : "https://github.com/Mars-Sea/dsh-deeppilot/releases",
4062
4333
  target: "_blank",
4063
4334
  rel: "noreferrer"
4064
- }, 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);
4065
4339
  }
4066
4340
  //#endregion
4067
- //#region src/client/settings-scope.ts
4068
- /** Read one optional settings seam without an inject declaration on this entry. */
4069
- function readSeam(ctx, name) {
4070
- if (typeof ctx.get === "function") return ctx.get(name) ?? void 0;
4071
- return ctx[name];
4072
- }
4341
+ //#region src/client/config-form.ts
4073
4342
  /**
4074
4343
  * Deep-unwrap live-update references in a snapshot section.
4075
4344
  *
4076
- * If a host parses section values through a volatile-capable schema, leaves
4077
- * can arrive as `{ get() }` references; the settings page compares plain
4078
- * values, so unwrap defensively at this single boundary regardless of which
4079
- * schemastery instance produced the snapshot.
4345
+ * Volatile fields can arrive as `{ get() }` references. The settings page
4346
+ * compares plain values, so unwrap them at this boundary.
4080
4347
  */
4081
4348
  function plainSection(value) {
4082
4349
  if (value === null || typeof value !== "object") return value;
@@ -4086,7 +4353,7 @@ window.__ModuleLoader__.load({
4086
4353
  return plain;
4087
4354
  }
4088
4355
  /**
4089
- * Adapt one 0.1.7 config form to the settings-scope face.
4356
+ * Adapt one config form to the settings page's local contract.
4090
4357
  *
4091
4358
  * The form resolves writes as `Promise<boolean>` (false = the Host rejected or
4092
4359
  * could not recover the write) where the scope face signals failure by
@@ -4094,7 +4361,7 @@ window.__ModuleLoader__.load({
4094
4361
  * a `false` must be translated into one.
4095
4362
  *
4096
4363
  * @param forms - the 0.1.7 configForms service, if the host provides it.
4097
- * @param entryId - profile entry id; identical to the legacy namespace here.
4364
+ * @param entryId - profile entry id.
4098
4365
  * @returns the adapted scope, or undefined when the service cannot resolve it.
4099
4366
  */
4100
4367
  function adaptConfigForm(forms, entryId) {
@@ -4118,18 +4385,11 @@ window.__ModuleLoader__.load({
4118
4385
  };
4119
4386
  }
4120
4387
  /**
4121
- * Bind the settings scope for whichever host generation is running.
4122
- *
4123
- * Prefers the legacy settingsScope (present ≤ 0.1.6); falls back to the 0.1.7
4124
- * config form when its service is already provisioned. Safe to call from any
4125
- * context — including one that declares neither service, which on a Cordis
4126
- * context is the difference between `undefined` and a thrown
4127
- * `cannot get property "settingsScope" without inject`. Callers that apply
4128
- * before `configForms` is provided re-run this inside the optional
4129
- * `ctx.inject(['configForms'], …)` callback (see client index.ts).
4388
+ * Bind the rc.2 form if its service is already provisioned. Callers that
4389
+ * apply first retry inside `ctx.inject(['configForms'], …)`.
4130
4390
  */
4131
- function bindSettingsScope(ctx) {
4132
- return readSeam(ctx, "settingsScope")?.bind({ namespace: "deeppilot" }) ?? adaptConfigForm(readSeam(ctx, "configForms"), "deeppilot");
4391
+ function bindConfigForm(ctx) {
4392
+ return adaptConfigForm(ctx.get("configForms"), "deeppilot");
4133
4393
  }
4134
4394
  //#endregion
4135
4395
  //#region src/client/index.ts
@@ -4187,13 +4447,9 @@ window.__ModuleLoader__.load({
4187
4447
  /**
4188
4448
  * Services this client entry cannot start without.
4189
4449
  *
4190
- * The settings seam is deliberately NOT here: DSH ≤ 0.1.6 provides
4191
- * `settingsScope`, 0.1.7 removed it in favor of `configForms`, and a name no
4192
- * running host provides keeps the whole entry pending forever — the 0.1.7 boot
4193
- * reported exactly `dsh-deeppilot: pending (waiting for service:
4194
- * settingsScope)` while every other service resolved and the entry never
4195
- * applied. Both settings seams are optional injections inside `apply`
4196
- * instead; see the scope binding below.
4450
+ * `configForms` is provided by the settings-page fiber, which may activate
4451
+ * after this entry. It is attached through an optional injection below so
4452
+ * the entry can activate before the settings page does.
4197
4453
  */
4198
4454
  const inject = [
4199
4455
  "slots",
@@ -4256,6 +4512,14 @@ window.__ModuleLoader__.load({
4256
4512
  if (!result.ok || result.value !== true) throw new Error(result.ok ? t(ctx, "devices.revokeFailed") : result.error.message ?? t(ctx, "devices.revokeFailed"));
4257
4513
  await controller.refresh();
4258
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
+ };
4259
4523
  const sendTestPush = async () => {
4260
4524
  if (namespace === void 0) throw new Error(mountError !== void 0 ? t(ctx, "diag.mountFailedShort") + mountError : t(ctx, "diag.remoteUnmounted"));
4261
4525
  if (typeof namespace.testPush !== "function") throw new Error(t(ctx, "push.staleHostPush"));
@@ -4274,9 +4538,9 @@ window.__ModuleLoader__.load({
4274
4538
  status: "loading",
4275
4539
  enabled: true
4276
4540
  });
4277
- let scope = bindSettingsScope(anyCtx);
4278
- /** Adopters registered before attachScope runs, so a late-bound scope (0.1.7
4279
- * configForms provisioned after this plugin applies) still subscribes all. */
4541
+ let scope = bindConfigForm(anyCtx);
4542
+ /** Adopters registered before attachScope runs, so a late-bound config form
4543
+ * still subscribes all. */
4280
4544
  const scopeAdoptions = [];
4281
4545
  const adoptEnabled = () => {
4282
4546
  if (scope === void 0) return;
@@ -4450,12 +4714,9 @@ window.__ModuleLoader__.load({
4450
4714
  if (scope === void 0) {
4451
4715
  const bindScopeFrom = (sub) => {
4452
4716
  if (scope !== void 0) return;
4453
- scope = bindSettingsScope(sub);
4717
+ scope = bindConfigForm(sub);
4454
4718
  attachScope();
4455
4719
  };
4456
- ctx.inject(["settingsScope"], (sub) => {
4457
- bindScopeFrom(sub);
4458
- });
4459
4720
  ctx.inject(["configForms"], (sub) => {
4460
4721
  bindScopeFrom(sub);
4461
4722
  });
@@ -4529,6 +4790,7 @@ window.__ModuleLoader__.load({
4529
4790
  },
4530
4791
  beginPairing,
4531
4792
  revokeDevice,
4793
+ setDeviceName,
4532
4794
  testRelay: testRelayConnection,
4533
4795
  testPush: sendTestPush,
4534
4796
  setDeepPilotEnabled,