dsh-deeppilot 0.9.0 → 0.9.2

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
@@ -263,7 +263,6 @@ window.__ModuleLoader__.load({
263
263
  pairingReady: bool(s, "pairingReady", "pairingReady"),
264
264
  activeConnections: int(s, "activeConnections", "activeConnections"),
265
265
  historyBufferMax: int(s, "historyBufferMax", "historyBufferMax"),
266
- debug: bool(s, "debug", "debug"),
267
266
  lanAddresses,
268
267
  local: parseLocal(s.local),
269
268
  remote: parseRemote(s.remote),
@@ -457,6 +456,8 @@ window.__ModuleLoader__.load({
457
456
  "troubleshooting.summary": "测试与故障排查",
458
457
  "troubleshooting.title": "诊断工具",
459
458
  "troubleshooting.description": "仅在排查连接或推送问题时使用。",
459
+ "troubleshooting.debugTitle": "调试日志",
460
+ "troubleshooting.debugDescription": "打开后在 DSH host 的终端输出里打印握手、推送等诊断信息;不会打印配对令牌、APNs token 或消息内容。改动立即生效,无需重启。",
460
461
  "system.summary": "系统信息",
461
462
  "system.title": "运行信息",
462
463
  "system.description": "插件、协议与连接端点的技术信息。",
@@ -630,6 +631,7 @@ window.__ModuleLoader__.load({
630
631
  "diag.missingLocalPortHook": "useDeepPilotLocalPort hook 缺失",
631
632
  "diag.missingRemoteEnabledHook": "useDeepPilotRemoteEnabled hook 缺失",
632
633
  "diag.missingRemoteLimitHook": "useDeepPilotRemoteConnectionLimit hook 缺失",
634
+ "diag.missingDebugHook": "useDeepPilotDebug hook 缺失",
633
635
  "diag.missingRefresh": "refresh 回调缺失",
634
636
  "diag.missingReveal": "beginPairing 回调缺失",
635
637
  "diag.missingRotate": "revokeDevice 回调缺失",
@@ -641,6 +643,7 @@ window.__ModuleLoader__.load({
641
643
  "diag.missingSetLocalPort": "setDeepPilotLocalPort 回调缺失",
642
644
  "diag.missingSetRemote": "setDeepPilotRemoteEnabled 回调缺失",
643
645
  "diag.missingSetRemoteLimit": "setDeepPilotRemoteConnectionLimit 回调缺失",
646
+ "diag.missingSetDebug": "setDeepPilotDebug 回调缺失",
644
647
  "diag.renderError": "渲染异常: ",
645
648
  "diag.prefix": "diag: ",
646
649
  "clipboard.rejected": "浏览器拒绝了剪贴板写入",
@@ -671,6 +674,8 @@ window.__ModuleLoader__.load({
671
674
  "troubleshooting.summary": "Tests and troubleshooting",
672
675
  "troubleshooting.title": "Diagnostic tools",
673
676
  "troubleshooting.description": "Use these only when diagnosing connection or push issues.",
677
+ "troubleshooting.debugTitle": "Debug logging",
678
+ "troubleshooting.debugDescription": "When on, prints handshake and push diagnostics to the DSH host’s terminal output; never prints pairing tokens, APNs tokens, or message content. Takes effect immediately, no restart needed.",
674
679
  "system.summary": "System information",
675
680
  "system.title": "Runtime information",
676
681
  "system.description": "Technical details about the plugin, protocol, and endpoints.",
@@ -844,6 +849,7 @@ window.__ModuleLoader__.load({
844
849
  "diag.missingLocalPortHook": "useDeepPilotLocalPort hook missing",
845
850
  "diag.missingRemoteEnabledHook": "useDeepPilotRemoteEnabled hook missing",
846
851
  "diag.missingRemoteLimitHook": "useDeepPilotRemoteConnectionLimit hook missing",
852
+ "diag.missingDebugHook": "useDeepPilotDebug hook missing",
847
853
  "diag.missingRefresh": "refresh callback missing",
848
854
  "diag.missingReveal": "beginPairing callback missing",
849
855
  "diag.missingRotate": "revokeDevice callback missing",
@@ -855,6 +861,7 @@ window.__ModuleLoader__.load({
855
861
  "diag.missingSetLocalPort": "setDeepPilotLocalPort callback missing",
856
862
  "diag.missingSetRemote": "setDeepPilotRemoteEnabled callback missing",
857
863
  "diag.missingSetRemoteLimit": "setDeepPilotRemoteConnectionLimit callback missing",
864
+ "diag.missingSetDebug": "setDeepPilotDebug callback missing",
858
865
  "diag.renderError": "Render error: ",
859
866
  "diag.prefix": "diag: ",
860
867
  "clipboard.rejected": "Clipboard write rejected by the browser",
@@ -3660,7 +3667,330 @@ window.__ModuleLoader__.load({
3660
3667
  return typeof value === "number" && Number.isInteger(value) && value >= 1024 && value <= 65535 ? value : DEFAULT_LOCAL_PORT;
3661
3668
  }
3662
3669
  //#endregion
3670
+ //#region src/client/settings-props.ts
3671
+ const MISSING_REPORT_HOOK = () => ({
3672
+ status: "loading",
3673
+ report: null,
3674
+ message: ""
3675
+ });
3676
+ const MISSING_ENABLED_HOOK = () => ({
3677
+ status: "unavailable",
3678
+ enabled: true
3679
+ });
3680
+ const MISSING_LOCAL_ENABLED_HOOK = () => ({
3681
+ status: "unavailable",
3682
+ enabled: true
3683
+ });
3684
+ const MISSING_LOCAL_PORT_HOOK = () => ({
3685
+ status: "unavailable",
3686
+ value: DEFAULT_LOCAL_PORT
3687
+ });
3688
+ const MISSING_REMOTE_ENABLED_HOOK = () => ({
3689
+ status: "unavailable",
3690
+ enabled: false
3691
+ });
3692
+ const MISSING_REMOTE_LIMIT_HOOK = () => ({
3693
+ status: "unavailable",
3694
+ value: 8
3695
+ });
3696
+ const MISSING_DEBUG_HOOK = () => ({
3697
+ status: "unavailable",
3698
+ enabled: false
3699
+ });
3700
+ /** 诊断行的 key,集中在此便于对账(tests/settings-props.test.ts 逐条断言)。 */
3701
+ const DIAG_KEY = {
3702
+ reportHook: "diag.missingReportHook",
3703
+ enabledHook: "diag.missingEnabledHook",
3704
+ localEnabledHook: "diag.missingLocalEnabledHook",
3705
+ localPortHook: "diag.missingLocalPortHook",
3706
+ remoteEnabledHook: "diag.missingRemoteEnabledHook",
3707
+ remoteLimitHook: "diag.missingRemoteLimitHook",
3708
+ debugHook: "diag.missingDebugHook",
3709
+ refresh: "diag.missingRefresh",
3710
+ reveal: "diag.missingReveal",
3711
+ rotate: "diag.missingRotate",
3712
+ rename: "diag.missingRename",
3713
+ testRelay: "diag.missingTestRelay",
3714
+ testPush: "diag.missingTestPush",
3715
+ setEnabled: "diag.missingSetEnabled",
3716
+ setLocal: "diag.missingSetLocal",
3717
+ setLocalPort: "diag.missingSetLocalPort",
3718
+ setRemote: "diag.missingSetRemote",
3719
+ setRemoteLimit: "diag.missingSetRemoteLimit",
3720
+ setDebug: "diag.missingSetDebug",
3721
+ settingsUnavailable: "diag.settingsUnavailable"
3722
+ };
3723
+ const CAPABILITY_BY_PROP = [
3724
+ ["refresh", "refresh"],
3725
+ ["beginPairing", "beginPairing"],
3726
+ ["revokeDevice", "revokeDevice"],
3727
+ ["setDeviceName", "setDeviceName"],
3728
+ ["testRelay", "testRelay"],
3729
+ ["testPush", "testPush"],
3730
+ ["setEnabled", "setDeepPilotEnabled"],
3731
+ ["setLocalEnabled", "setDeepPilotLocalEnabled"],
3732
+ ["setLocalPort", "setDeepPilotLocalPort"],
3733
+ ["setRemoteEnabled", "setDeepPilotRemoteEnabled"],
3734
+ ["setRemoteLimit", "setDeepPilotRemoteConnectionLimit"],
3735
+ ["setDebug", "setDeepPilotDebug"]
3736
+ ];
3737
+ const isFn = (value) => typeof value === "function";
3738
+ /**
3739
+ * 把宿主注入的原始对象归一化成页面可消费的面。
3740
+ *
3741
+ * 逐项复刻迁移前页面内的行为:hook 缺失换 fallback 并记诊断;函数缺失则该
3742
+ * 能力不进 `can` 并记诊断;诊断顺序与原先的 `diag[]` 完全一致(宿主过期时
3743
+ * 用户看到的技术诊断因此一字不差)。
3744
+ */
3745
+ function normalizeSettingsPageProps(raw) {
3746
+ const source = raw ?? {};
3747
+ const t = typeof source.t === "function" ? source.t : ((key) => key);
3748
+ const diagnostics = [];
3749
+ const can = /* @__PURE__ */ new Set();
3750
+ const useReport = isFn(source.useDeepPilotReport) ? source.useDeepPilotReport : MISSING_REPORT_HOOK;
3751
+ const useEnabled = isFn(source.useDeepPilotEnabled) ? source.useDeepPilotEnabled : MISSING_ENABLED_HOOK;
3752
+ const useLocalEnabled = isFn(source.useDeepPilotLocalEnabled) ? source.useDeepPilotLocalEnabled : MISSING_LOCAL_ENABLED_HOOK;
3753
+ const useLocalPort = isFn(source.useDeepPilotLocalPort) ? source.useDeepPilotLocalPort : MISSING_LOCAL_PORT_HOOK;
3754
+ const useRemoteEnabled = isFn(source.useDeepPilotRemoteEnabled) ? source.useDeepPilotRemoteEnabled : MISSING_REMOTE_ENABLED_HOOK;
3755
+ const useRemoteLimit = isFn(source.useDeepPilotRemoteConnectionLimit) ? source.useDeepPilotRemoteConnectionLimit : MISSING_REMOTE_LIMIT_HOOK;
3756
+ const useDebug = isFn(source.useDeepPilotDebug) ? source.useDeepPilotDebug : MISSING_DEBUG_HOOK;
3757
+ if (!isFn(source.useDeepPilotReport)) diagnostics.push(t(DIAG_KEY.reportHook));
3758
+ if (!isFn(source.useDeepPilotEnabled)) diagnostics.push(t(DIAG_KEY.enabledHook));
3759
+ if (!isFn(source.refresh)) diagnostics.push(t(DIAG_KEY.refresh));
3760
+ if (!isFn(source.beginPairing)) diagnostics.push(t(DIAG_KEY.reveal));
3761
+ if (!isFn(source.revokeDevice)) diagnostics.push(t(DIAG_KEY.rotate));
3762
+ if (!isFn(source.setDeviceName)) diagnostics.push(t(DIAG_KEY.rename));
3763
+ if (!isFn(source.testRelay)) diagnostics.push(t(DIAG_KEY.testRelay));
3764
+ if (!isFn(source.testPush)) diagnostics.push(t(DIAG_KEY.testPush));
3765
+ if (!isFn(source.setDeepPilotEnabled)) diagnostics.push(t(DIAG_KEY.setEnabled));
3766
+ if (!isFn(source.useDeepPilotLocalEnabled)) diagnostics.push(t(DIAG_KEY.localEnabledHook));
3767
+ if (!isFn(source.useDeepPilotLocalPort)) diagnostics.push(t(DIAG_KEY.localPortHook));
3768
+ if (!isFn(source.setDeepPilotLocalEnabled)) diagnostics.push(t(DIAG_KEY.setLocal));
3769
+ if (!isFn(source.setDeepPilotLocalPort)) diagnostics.push(t(DIAG_KEY.setLocalPort));
3770
+ if (!isFn(source.useDeepPilotRemoteEnabled)) diagnostics.push(t(DIAG_KEY.remoteEnabledHook));
3771
+ if (!isFn(source.setDeepPilotRemoteEnabled)) diagnostics.push(t(DIAG_KEY.setRemote));
3772
+ if (!isFn(source.useDeepPilotRemoteConnectionLimit)) diagnostics.push(t(DIAG_KEY.remoteLimitHook));
3773
+ if (!isFn(source.setDeepPilotRemoteConnectionLimit)) diagnostics.push(t(DIAG_KEY.setRemoteLimit));
3774
+ if (!isFn(source.useDeepPilotDebug)) diagnostics.push(t(DIAG_KEY.debugHook));
3775
+ if (!isFn(source.setDeepPilotDebug)) diagnostics.push(t(DIAG_KEY.setDebug));
3776
+ for (const [capability, prop] of CAPABILITY_BY_PROP) if (isFn(source[prop])) can.add(capability);
3777
+ return {
3778
+ props: {
3779
+ useDeepPilotReport: useReport,
3780
+ useDeepPilotEnabled: useEnabled,
3781
+ useDeepPilotLocalEnabled: useLocalEnabled,
3782
+ useDeepPilotLocalPort: useLocalPort,
3783
+ useDeepPilotRemoteEnabled: useRemoteEnabled,
3784
+ useDeepPilotRemoteConnectionLimit: useRemoteLimit,
3785
+ useDeepPilotDebug: useDebug,
3786
+ t,
3787
+ refresh: isFn(source.refresh) ? source.refresh : void 0,
3788
+ beginPairing: isFn(source.beginPairing) ? source.beginPairing : void 0,
3789
+ revokeDevice: isFn(source.revokeDevice) ? source.revokeDevice : void 0,
3790
+ setDeviceName: isFn(source.setDeviceName) ? source.setDeviceName : void 0,
3791
+ testRelay: isFn(source.testRelay) ? source.testRelay : void 0,
3792
+ testPush: isFn(source.testPush) ? source.testPush : void 0,
3793
+ setDeepPilotEnabled: isFn(source.setDeepPilotEnabled) ? source.setDeepPilotEnabled : void 0,
3794
+ setDeepPilotLocalEnabled: isFn(source.setDeepPilotLocalEnabled) ? source.setDeepPilotLocalEnabled : void 0,
3795
+ setDeepPilotLocalPort: isFn(source.setDeepPilotLocalPort) ? source.setDeepPilotLocalPort : void 0,
3796
+ setDeepPilotRemoteEnabled: isFn(source.setDeepPilotRemoteEnabled) ? source.setDeepPilotRemoteEnabled : void 0,
3797
+ setDeepPilotRemoteConnectionLimit: isFn(source.setDeepPilotRemoteConnectionLimit) ? source.setDeepPilotRemoteConnectionLimit : void 0,
3798
+ setDeepPilotDebug: isFn(source.setDeepPilotDebug) ? source.setDeepPilotDebug : void 0
3799
+ },
3800
+ can,
3801
+ diagnostics,
3802
+ reportHookMissing: !isFn(source.useDeepPilotReport)
3803
+ };
3804
+ }
3805
+ //#endregion
3806
+ //#region src/client/settings-view-models.ts
3807
+ /**
3808
+ * 设置页的 view model:把原先住在渲染函数里的三块逻辑变成可单测的纯模块。
3809
+ *
3810
+ * 迁移前 `settings-page.ts` 是一个 1059 行的组件,funnel phase → {dot, label}、
3811
+ 端口/连接数草稿的编辑与合法性、serviceStatus 派生全在渲染函数里,没有任何
3812
+ 直接测试。这里按 pairing-panel.ts 的形状把它们拆成 state / action / reduce /
3813
+ view 四件套——shape 与该先例一致,但**不继承它唯一的泄漏**:view model 只返回
3814
+ 语义值,i18n key 的映射留在页面(措辞归页面,测试不跟着措辞漂)。
3815
+ */
3816
+ const REMOTE_PHASE_META = {
3817
+ disabled: {
3818
+ dot: "",
3819
+ label: "disabled"
3820
+ },
3821
+ starting: {
3822
+ dot: " pbb-dotWarn",
3823
+ label: "starting"
3824
+ },
3825
+ login_required: {
3826
+ dot: " pbb-dotWarn",
3827
+ label: "login_required"
3828
+ },
3829
+ online: {
3830
+ dot: " pbb-dotOk",
3831
+ label: "online"
3832
+ },
3833
+ error: {
3834
+ dot: " pbb-dotBad",
3835
+ label: "error"
3836
+ },
3837
+ unavailable: {
3838
+ dot: " pbb-dotBad",
3839
+ label: "unavailable"
3840
+ },
3841
+ stopped: {
3842
+ dot: "",
3843
+ label: "stopped"
3844
+ }
3845
+ };
3846
+ const LOCAL_PHASE_META = {
3847
+ disabled: {
3848
+ dot: "",
3849
+ label: "disabled"
3850
+ },
3851
+ starting: {
3852
+ dot: " pbb-dotWarn",
3853
+ label: "starting"
3854
+ },
3855
+ online: {
3856
+ dot: " pbb-dotOk",
3857
+ label: "online"
3858
+ },
3859
+ error: {
3860
+ dot: " pbb-dotBad",
3861
+ label: "error"
3862
+ },
3863
+ stopped: {
3864
+ dot: "",
3865
+ label: "stopped"
3866
+ }
3867
+ };
3868
+ /** 从点位反推「需要注意 / 已在线」——原先页面用字符串 includes 猜类名。 */
3869
+ function dotState(dot) {
3870
+ return {
3871
+ attention: dot?.includes("pbb-dotBad") === true,
3872
+ online: dot?.includes("pbb-dotOk") === true
3873
+ };
3874
+ }
3875
+ function serviceStatus(input) {
3876
+ const local = dotState(input.localDot);
3877
+ const remote = dotState(input.remoteDot);
3878
+ const attention = local.attention || remote.attention;
3879
+ const online = local.online || remote.online;
3880
+ return {
3881
+ kind: !input.switchReady ? "loading" : !input.enabled ? "off" : attention ? "attention" : online ? "ready" : "loading",
3882
+ dot: !input.enabled ? "" : attention ? " pbb-dotBad" : online ? " pbb-dotOk" : " pbb-dotWarn"
3883
+ };
3884
+ }
3885
+ const LOCAL_PORT_RANGE = {
3886
+ min: MIN_LOCAL_PORT,
3887
+ max: MAX_LOCAL_PORT,
3888
+ fallback: DEFAULT_LOCAL_PORT
3889
+ };
3890
+ const REMOTE_LIMIT_RANGE = {
3891
+ min: 1,
3892
+ max: 16,
3893
+ fallback: 8
3894
+ };
3895
+ function initialNumericDraft(range) {
3896
+ return {
3897
+ draft: String(range.fallback),
3898
+ message: "",
3899
+ failed: false
3900
+ };
3901
+ }
3902
+ function numericDraftReduce(state, action, range) {
3903
+ switch (action.type) {
3904
+ case "edit": return {
3905
+ ...state,
3906
+ draft: action.draft
3907
+ };
3908
+ case "sync": return {
3909
+ ...state,
3910
+ draft: String(action.value),
3911
+ message: "",
3912
+ failed: false
3913
+ };
3914
+ case "applied": return {
3915
+ ...state,
3916
+ message: "",
3917
+ failed: false
3918
+ };
3919
+ case "succeeded": return {
3920
+ ...state,
3921
+ message: action.message,
3922
+ failed: false
3923
+ };
3924
+ case "failed": return {
3925
+ ...state,
3926
+ message: action.message,
3927
+ failed: true
3928
+ };
3929
+ }
3930
+ }
3931
+ function numericDraftView(state, current, range) {
3932
+ const value = Number(state.draft);
3933
+ const valid = Number.isInteger(value) && value >= range.min && value <= range.max;
3934
+ return {
3935
+ value,
3936
+ valid,
3937
+ unchanged: valid && value === current
3938
+ };
3939
+ }
3940
+ /** 已撤销的设备不展示。 */
3941
+ function visibleDevices(report) {
3942
+ return (report?.devices ?? []).filter((device) => device.revokedAt === void 0);
3943
+ }
3944
+ /** 设备展示名:自定义名优先,都没有则空串(兜底文案归页面)。 */
3945
+ function deviceLabel(device) {
3946
+ return (device.customName ?? device.deviceName).trim();
3947
+ }
3948
+ /** 渲染一块连接磁贴。纯数据函数:九参进,元素树出。 */
3949
+ function renderConnectionTile(key, title, description, checked, ready, dot, statusLabel, onToggle, extra = null) {
3950
+ return (0, react.createElement)("div", {
3951
+ className: "pbb-connectionTile",
3952
+ key
3953
+ }, (0, react.createElement)("div", { className: "pbb-connectionTileHeader" }, (0, react.createElement)("span", { className: "pbb-switchTitle pbb-dotRow" }, (0, react.createElement)("span", {
3954
+ className: "pbb-dot" + dot,
3955
+ role: "img",
3956
+ "aria-label": statusLabel,
3957
+ title: statusLabel
3958
+ }), title), (0, react.createElement)("button", {
3959
+ type: "button",
3960
+ role: "switch",
3961
+ "aria-checked": checked,
3962
+ "aria-label": title,
3963
+ disabled: !ready,
3964
+ className: "pbb-switch" + (checked ? " pbb-switchOn" : ""),
3965
+ onClick: onToggle
3966
+ })), (0, react.createElement)("span", { className: "pbb-connectionState" }, statusLabel), (0, react.createElement)("p", { className: "pbb-connectionDescription" }, description), extra);
3967
+ }
3968
+ //#endregion
3663
3969
  //#region src/client/settings-page.ts
3970
+ /** phase 语义标签 → i18n key。措辞归页面,view model 只认语义。 */
3971
+ const REMOTE_LABEL_KEY = {
3972
+ disabled: "phase.disabled",
3973
+ starting: "phase.starting",
3974
+ login_required: "phase.login_required",
3975
+ online: "phase.online",
3976
+ error: "phase.error",
3977
+ unavailable: "phase.unavailable",
3978
+ stopped: "phase.stopped"
3979
+ };
3980
+ const LOCAL_LABEL_KEY = {
3981
+ disabled: "local.phaseDisabled",
3982
+ starting: "local.phaseStarting",
3983
+ online: "local.phaseOnline",
3984
+ error: "local.phaseError",
3985
+ stopped: "local.phaseStopped"
3986
+ };
3987
+ /** 服务状态 → i18n key。 */
3988
+ const SERVICE_STATUS_KEY = {
3989
+ loading: "status.loading",
3990
+ off: "status.off",
3991
+ attention: "status.attention",
3992
+ ready: "status.ready"
3993
+ };
3664
3994
  /** Inline trash icon for the compact destructive row action. Kept as a tiny
3665
3995
  * element (no icon dependency) so the client bundle stays dependency-free
3666
3996
  * apart from React. */
@@ -3714,68 +4044,19 @@ window.__ModuleLoader__.load({
3714
4044
  textarea.remove();
3715
4045
  if (!copied) throw new Error(t("clipboard.rejected"));
3716
4046
  }
3717
- /** Visual state of the embedded Funnel: a colored dot plus its spoken label.
3718
- * The label is a function because the surrounding constant lives at module
3719
- * scope where the locale-bound t() is not in scope; the page resolves the
3720
- * label at render time via t(props.t, ...). */
3721
- const REMOTE_PHASE_META = {
3722
- disabled: {
3723
- dot: "",
3724
- labelKey: "phase.disabled"
3725
- },
3726
- starting: {
3727
- dot: " pbb-dotWarn",
3728
- labelKey: "phase.starting"
3729
- },
3730
- login_required: {
3731
- dot: " pbb-dotWarn",
3732
- labelKey: "phase.login_required"
3733
- },
3734
- online: {
3735
- dot: " pbb-dotOk",
3736
- labelKey: "phase.online"
3737
- },
3738
- error: {
3739
- dot: " pbb-dotBad",
3740
- labelKey: "phase.error"
3741
- },
3742
- unavailable: {
3743
- dot: " pbb-dotBad",
3744
- labelKey: "phase.unavailable"
3745
- },
3746
- stopped: {
3747
- dot: "",
3748
- labelKey: "phase.stopped"
3749
- }
3750
- };
3751
- const LOCAL_PHASE_META = {
3752
- disabled: {
3753
- dot: "",
3754
- labelKey: "local.phaseDisabled"
3755
- },
3756
- starting: {
3757
- dot: " pbb-dotWarn",
3758
- labelKey: "local.phaseStarting"
3759
- },
3760
- online: {
3761
- dot: " pbb-dotOk",
3762
- labelKey: "local.phaseOnline"
3763
- },
3764
- error: {
3765
- dot: " pbb-dotBad",
3766
- labelKey: "local.phaseError"
3767
- },
3768
- stopped: {
3769
- dot: "",
3770
- labelKey: "local.phaseStopped"
3771
- }
3772
- };
3773
- /** Slot component: hooks come from the slot renderer, named use<Key>. */
3774
- function DeepPilotSettingsPage(props) {
3775
- const [remoteLimitDraft, setRemoteLimitDraft] = (0, react.useState)(String(8));
3776
- const [remoteLimitMessage, setRemoteLimitMessage] = (0, react.useState)("");
3777
- const [localPortDraft, setLocalPortDraft] = (0, react.useState)(String(DEFAULT_LOCAL_PORT));
3778
- const [localPortMessage, setLocalPortMessage] = (0, react.useState)("");
4047
+ /**
4048
+ * Slot component: hooks come from the slot renderer, named use<Key>.
4049
+ *
4050
+ * 组件的接口是 `SettingsPageProps`(见 settings-props.ts):宿主注入面与页面
4051
+ * 共享同一份类型。这里只做一次归一化,之后全程消费类型化对象——页面里不再
4052
+ * 出现任何 typeof 守卫,也不再自己拼诊断行。
4053
+ */
4054
+ function DeepPilotSettingsPage(rawProps) {
4055
+ const surface = normalizeSettingsPageProps(rawProps);
4056
+ const props = surface.props;
4057
+ const can = (capability) => surface.can.has(capability);
4058
+ const [remoteLimitDraft, dispatchRemoteLimitDraft] = (0, react.useReducer)((state, action) => numericDraftReduce(state, action, REMOTE_LIMIT_RANGE), initialNumericDraft(REMOTE_LIMIT_RANGE));
4059
+ const [localPortDraft, dispatchLocalPortDraft] = (0, react.useReducer)((state, action) => numericDraftReduce(state, action, LOCAL_PORT_RANGE), initialNumericDraft(LOCAL_PORT_RANGE));
3779
4060
  const [selectedPairingHost, setSelectedPairingHost] = (0, react.useState)(null);
3780
4061
  const [pairingPanel, dispatchPairingPanel] = (0, react.useReducer)(pairingPanelReduce, initialPairingPanelState);
3781
4062
  const qrRequestId = (0, react.useRef)(0);
@@ -3792,14 +4073,15 @@ window.__ModuleLoader__.load({
3792
4073
  const [editingDeviceId, setEditingDeviceId] = (0, react.useState)(null);
3793
4074
  const [deviceNameDraft, setDeviceNameDraft] = (0, react.useState)("");
3794
4075
  (0, react.useEffect)(() => {
3795
- if (typeof props.refresh !== "function") return;
3796
- props.refresh();
3797
- const timer = globalThis.setInterval(() => props.refresh(), 3e3);
4076
+ if (!can("refresh")) return;
4077
+ const refresh = props.refresh;
4078
+ refresh();
4079
+ const timer = globalThis.setInterval(() => refresh(), 3e3);
3798
4080
  return () => globalThis.clearInterval(timer);
3799
4081
  }, [props.refresh]);
3800
4082
  const sendPushTest = () => {
3801
4083
  if (pushTestBusy) return;
3802
- if (typeof props.testPush !== "function") {
4084
+ if (!can("testPush")) {
3803
4085
  setPushTestError(translateWith(props.t, "push.staleHost"));
3804
4086
  return;
3805
4087
  }
@@ -3816,7 +4098,7 @@ window.__ModuleLoader__.load({
3816
4098
  };
3817
4099
  const runRelayTest = () => {
3818
4100
  if (relayTestBusy) return;
3819
- if (typeof props.testRelay !== "function") {
4101
+ if (!can("testRelay")) {
3820
4102
  setRelayTestError(translateWith(props.t, "push.staleHostRelay"));
3821
4103
  return;
3822
4104
  }
@@ -3832,15 +4114,15 @@ window.__ModuleLoader__.load({
3832
4114
  });
3833
4115
  };
3834
4116
  (0, react.useEffect)(() => {
3835
- if (!remoteLimitMessage) return;
3836
- const timer = globalThis.setTimeout(() => setRemoteLimitMessage(""), 4e3);
4117
+ if (remoteLimitDraft.message === "") return;
4118
+ const timer = globalThis.setTimeout(() => dispatchRemoteLimitDraft({ type: "applied" }), 4e3);
3837
4119
  return () => globalThis.clearTimeout(timer);
3838
- }, [remoteLimitMessage]);
4120
+ }, [remoteLimitDraft.message]);
3839
4121
  (0, react.useEffect)(() => {
3840
- if (!localPortMessage) return;
3841
- const timer = globalThis.setTimeout(() => setLocalPortMessage(""), 4e3);
4122
+ if (localPortDraft.message === "") return;
4123
+ const timer = globalThis.setTimeout(() => dispatchLocalPortDraft({ type: "applied" }), 4e3);
3842
4124
  return () => globalThis.clearTimeout(timer);
3843
- }, [localPortMessage]);
4125
+ }, [localPortDraft.message]);
3844
4126
  (0, react.useEffect)(() => {
3845
4127
  if (pairingPanel.grant === null) return;
3846
4128
  const remaining = Math.max(0, pairingPanel.grant.expiresAt - Date.now());
@@ -3857,104 +4139,123 @@ window.__ModuleLoader__.load({
3857
4139
  const timer = globalThis.setTimeout(() => dispatchPairingPanel({ type: "dismissNotice" }), 2500);
3858
4140
  return () => globalThis.clearTimeout(timer);
3859
4141
  }, [pairingPanel.notice]);
3860
- const diag = [];
3861
- let report = null;
3862
- let enabled = true;
3863
- let switchReady = false;
3864
- let localEnabled = true;
3865
- let localSwitchReady = false;
3866
- let localPort = DEFAULT_LOCAL_PORT;
3867
- let localPortReady = false;
3868
- let remoteEnabled = false;
3869
- let remoteSwitchReady = false;
3870
- let remoteConnectionLimit = 8;
3871
- let remoteConnectionLimitReady = false;
3872
- let failed = false;
3873
- try {
3874
- if (typeof props.useDeepPilotReport !== "function") {
3875
- diag.push(translateWith(props.t, "diag.missingReportHook"));
4142
+ const diag = surface.diagnostics;
4143
+ let failed = surface.reportHookMissing;
4144
+ const readHooks = () => {
4145
+ const reportState = props.useDeepPilotReport((s) => s);
4146
+ hooks.report = reportState.report;
4147
+ if (reportState.status === "error" && reportState.message) {
4148
+ diag.push(reportState.message);
3876
4149
  failed = true;
3877
- } else {
3878
- const state = props.useDeepPilotReport((s) => s);
3879
- report = state.report;
3880
- if (state.status === "error" && state.message) {
3881
- diag.push(state.message);
3882
- failed = true;
3883
- }
3884
4150
  }
3885
- if (typeof props.useDeepPilotEnabled === "function") {
3886
- const state = props.useDeepPilotEnabled((s) => s);
3887
- enabled = state.enabled;
3888
- switchReady = state.status === "ready";
3889
- if (state.status === "unavailable") diag.push(translateWith(props.t, "diag.settingsUnavailable"));
3890
- } else diag.push(translateWith(props.t, "diag.missingEnabledHook"));
3891
- if (typeof props.refresh !== "function") diag.push(translateWith(props.t, "diag.missingRefresh"));
3892
- if (typeof props.beginPairing !== "function") diag.push(translateWith(props.t, "diag.missingReveal"));
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"));
3895
- if (typeof props.testRelay !== "function") diag.push(translateWith(props.t, "diag.missingTestRelay"));
3896
- if (typeof props.testPush !== "function") diag.push(translateWith(props.t, "diag.missingTestPush"));
3897
- if (typeof props.setDeepPilotEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetEnabled"));
3898
- if (typeof props.useDeepPilotLocalEnabled === "function") {
3899
- const state = props.useDeepPilotLocalEnabled((s) => s);
3900
- localEnabled = state.enabled;
3901
- localSwitchReady = state.status === "ready";
3902
- } else diag.push(translateWith(props.t, "diag.missingLocalEnabledHook"));
3903
- if (typeof props.useDeepPilotLocalPort === "function") {
3904
- const state = props.useDeepPilotLocalPort((s) => s);
3905
- localPort = state.value;
3906
- localPortReady = state.status === "ready";
3907
- } else diag.push(translateWith(props.t, "diag.missingLocalPortHook"));
3908
- if (typeof props.setDeepPilotLocalEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetLocal"));
3909
- if (typeof props.setDeepPilotLocalPort !== "function") diag.push(translateWith(props.t, "diag.missingSetLocalPort"));
3910
- if (typeof props.useDeepPilotRemoteEnabled === "function") {
3911
- const state = props.useDeepPilotRemoteEnabled((s) => s);
3912
- remoteEnabled = state.enabled;
3913
- remoteSwitchReady = state.status === "ready";
3914
- } else diag.push(translateWith(props.t, "diag.missingRemoteEnabledHook"));
3915
- if (typeof props.setDeepPilotRemoteEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetRemote"));
3916
- if (typeof props.useDeepPilotRemoteConnectionLimit === "function") {
3917
- const state = props.useDeepPilotRemoteConnectionLimit((s) => s);
3918
- remoteConnectionLimit = state.value;
3919
- remoteConnectionLimitReady = state.status === "ready";
3920
- } else diag.push(translateWith(props.t, "diag.missingRemoteLimitHook"));
3921
- if (typeof props.setDeepPilotRemoteConnectionLimit !== "function") diag.push(translateWith(props.t, "diag.missingSetRemoteLimit"));
4151
+ const enabledState = props.useDeepPilotEnabled((s) => s);
4152
+ hooks.enabled = enabledState.enabled;
4153
+ hooks.switchReady = enabledState.status === "ready";
4154
+ if (enabledState.status === "unavailable") diag.push(translateWith(props.t, "diag.settingsUnavailable"));
4155
+ const localEnabledState = props.useDeepPilotLocalEnabled((s) => s);
4156
+ hooks.localEnabled = localEnabledState.enabled;
4157
+ hooks.localSwitchReady = localEnabledState.status === "ready";
4158
+ const localPortState = props.useDeepPilotLocalPort((s) => s);
4159
+ hooks.localPort = localPortState.value;
4160
+ hooks.localPortReady = localPortState.status === "ready";
4161
+ const remoteEnabledState = props.useDeepPilotRemoteEnabled((s) => s);
4162
+ hooks.remoteEnabled = remoteEnabledState.enabled;
4163
+ hooks.remoteSwitchReady = remoteEnabledState.status === "ready";
4164
+ const remoteLimitState = props.useDeepPilotRemoteConnectionLimit((s) => s);
4165
+ hooks.remoteConnectionLimit = remoteLimitState.value;
4166
+ hooks.remoteConnectionLimitReady = remoteLimitState.status === "ready";
4167
+ const debugState = props.useDeepPilotDebug((s) => s);
4168
+ hooks.debugEnabled = debugState.enabled;
4169
+ hooks.debugSwitchReady = debugState.status === "ready";
4170
+ };
4171
+ const hooks = {
4172
+ report: null,
4173
+ enabled: true,
4174
+ switchReady: false,
4175
+ localEnabled: true,
4176
+ localSwitchReady: false,
4177
+ localPort: DEFAULT_LOCAL_PORT,
4178
+ localPortReady: false,
4179
+ remoteEnabled: false,
4180
+ remoteSwitchReady: false,
4181
+ remoteConnectionLimit: 8,
4182
+ remoteConnectionLimitReady: false,
4183
+ debugEnabled: false,
4184
+ debugSwitchReady: false
4185
+ };
4186
+ try {
4187
+ readHooks();
3922
4188
  } catch (error) {
3923
4189
  diag.push(translateWith(props.t, "diag.renderError") + (error instanceof Error ? error.message : String(error)));
3924
4190
  failed = true;
3925
4191
  }
4192
+ const report = hooks.report;
4193
+ const enabled = hooks.enabled;
4194
+ const switchReady = hooks.switchReady;
4195
+ const localEnabled = hooks.localEnabled;
4196
+ const localSwitchReady = hooks.localSwitchReady;
4197
+ const localPort = hooks.localPort;
4198
+ const localPortReady = hooks.localPortReady;
4199
+ const remoteEnabled = hooks.remoteEnabled;
4200
+ const remoteSwitchReady = hooks.remoteSwitchReady;
4201
+ const remoteConnectionLimit = hooks.remoteConnectionLimit;
4202
+ const remoteConnectionLimitReady = hooks.remoteConnectionLimitReady;
4203
+ const debugEnabled = hooks.debugEnabled;
4204
+ const debugSwitchReady = hooks.debugSwitchReady;
3926
4205
  (0, react.useEffect)(() => {
3927
- setRemoteLimitDraft(String(remoteConnectionLimit));
4206
+ dispatchRemoteLimitDraft({
4207
+ type: "sync",
4208
+ value: remoteConnectionLimit
4209
+ });
3928
4210
  }, [remoteConnectionLimit]);
3929
4211
  (0, react.useEffect)(() => {
3930
- setLocalPortDraft(String(localPort));
4212
+ dispatchLocalPortDraft({
4213
+ type: "sync",
4214
+ value: localPort
4215
+ });
3931
4216
  }, [localPort]);
3932
- const parsedLocalPort = Number(localPortDraft);
3933
- const localPortValid = Number.isInteger(parsedLocalPort) && parsedLocalPort >= 1024 && parsedLocalPort <= 65535;
4217
+ const localPortView = numericDraftView(localPortDraft, localPort, LOCAL_PORT_RANGE);
4218
+ const remoteLimitView = numericDraftView(remoteLimitDraft, remoteConnectionLimit, REMOTE_LIMIT_RANGE);
3934
4219
  const applyLocalPort = () => {
3935
- if (!localPortValid || typeof props.setDeepPilotLocalPort !== "function") {
3936
- setLocalPortMessage(translateWith(props.t, "local.portInvalid"));
4220
+ if (!localPortView.valid || !can("setLocalPort")) {
4221
+ dispatchLocalPortDraft({
4222
+ type: "failed",
4223
+ message: translateWith(props.t, "local.portInvalid")
4224
+ });
3937
4225
  return;
3938
4226
  }
3939
- setLocalPortMessage("");
3940
- props.setDeepPilotLocalPort(parsedLocalPort).then(() => {
3941
- setLocalPortMessage(translateWith(props.t, "local.portApplied"));
4227
+ dispatchLocalPortDraft({ type: "applied" });
4228
+ Promise.resolve(props.setDeepPilotLocalPort(localPortView.value)).then(() => {
4229
+ dispatchLocalPortDraft({
4230
+ type: "succeeded",
4231
+ message: translateWith(props.t, "local.portApplied")
4232
+ });
3942
4233
  }, (error) => {
3943
- setLocalPortMessage(translateWith(props.t, "local.portFailed") + (error instanceof Error ? error.message : String(error)));
4234
+ dispatchLocalPortDraft({
4235
+ type: "failed",
4236
+ message: translateWith(props.t, "local.portFailed") + (error instanceof Error ? error.message : String(error))
4237
+ });
3944
4238
  });
3945
4239
  };
3946
- const parsedRemoteLimit = Number(remoteLimitDraft);
3947
- const remoteLimitValid = Number.isInteger(parsedRemoteLimit) && parsedRemoteLimit >= 1 && parsedRemoteLimit <= 16;
3948
4240
  const applyRemoteLimit = () => {
3949
- if (!remoteLimitValid || typeof props.setDeepPilotRemoteConnectionLimit !== "function") {
3950
- setRemoteLimitMessage(translateWith(props.t, "remote.limitInvalid"));
4241
+ if (!remoteLimitView.valid || !can("setRemoteLimit")) {
4242
+ dispatchRemoteLimitDraft({
4243
+ type: "failed",
4244
+ message: translateWith(props.t, "remote.limitInvalid")
4245
+ });
3951
4246
  return;
3952
4247
  }
3953
- setRemoteLimitMessage("");
3954
- props.setDeepPilotRemoteConnectionLimit(parsedRemoteLimit).then(() => {
3955
- setRemoteLimitMessage(translateWith(props.t, "remote.limitApplied"));
4248
+ dispatchRemoteLimitDraft({ type: "applied" });
4249
+ Promise.resolve(props.setDeepPilotRemoteConnectionLimit(remoteLimitView.value)).then(() => {
4250
+ dispatchRemoteLimitDraft({
4251
+ type: "succeeded",
4252
+ message: translateWith(props.t, "remote.limitApplied")
4253
+ });
3956
4254
  }, (error) => {
3957
- setRemoteLimitMessage(translateWith(props.t, "remote.limitFailed") + (error instanceof Error ? error.message : String(error)));
4255
+ dispatchRemoteLimitDraft({
4256
+ type: "failed",
4257
+ message: translateWith(props.t, "remote.limitFailed") + (error instanceof Error ? error.message : String(error))
4258
+ });
3958
4259
  });
3959
4260
  };
3960
4261
  const pairingTargets = report === null ? [] : selectPairingTargets(report.local, report.remote);
@@ -3969,7 +4270,7 @@ window.__ModuleLoader__.load({
3969
4270
  * switching between LAN and public always mints a fresh one.
3970
4271
  */
3971
4272
  const showPairingQR = (target) => {
3972
- if (typeof props.beginPairing !== "function") return;
4273
+ if (!can("beginPairing")) return;
3973
4274
  const requestId = qrRequestId.current + 1;
3974
4275
  qrRequestId.current = requestId;
3975
4276
  dispatchPairingPanel({
@@ -4059,7 +4360,7 @@ window.__ModuleLoader__.load({
4059
4360
  }, (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")))));
4060
4361
  }
4061
4362
  const revokeDevice = (deviceId, name) => {
4062
- if (typeof props.revokeDevice !== "function") return;
4363
+ if (!can("revokeDevice")) return;
4063
4364
  if (typeof window !== "undefined" && !window.confirm(translateWith(props.t, "devices.revokeConfirm", { name }))) return;
4064
4365
  setDeviceBusy(deviceId);
4065
4366
  setDeviceBusyAction("revoke");
@@ -4073,7 +4374,7 @@ window.__ModuleLoader__.load({
4073
4374
  setDeviceBusyAction(null);
4074
4375
  });
4075
4376
  };
4076
- const deviceLabel = (device) => (device.customName ?? device.deviceName).trim() || translateWith(props.t, "devices.unnamed");
4377
+ const deviceLabel$1 = (device) => deviceLabel(device) || translateWith(props.t, "devices.unnamed");
4077
4378
  const beginDeviceRename = (device) => {
4078
4379
  setEditingDeviceId(device.deviceId);
4079
4380
  setDeviceNameDraft(device.customName ?? device.deviceName);
@@ -4085,7 +4386,7 @@ window.__ModuleLoader__.load({
4085
4386
  setDeviceMessage("");
4086
4387
  };
4087
4388
  const saveDeviceName = (deviceId) => {
4088
- if (typeof props.setDeviceName !== "function") return;
4389
+ if (!can("setDeviceName")) return;
4089
4390
  const normalized = deviceNameDraft.trim();
4090
4391
  if (normalized.length > 64) {
4091
4392
  setDeviceMessage(translateWith(props.t, "devices.nameInvalid"));
@@ -4105,11 +4406,11 @@ window.__ModuleLoader__.load({
4105
4406
  setDeviceBusyAction(null);
4106
4407
  });
4107
4408
  };
4108
- const visibleDevices = report?.devices.filter((device) => device.revokedAt === void 0) ?? [];
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) => {
4409
+ const visibleDevices$1 = visibleDevices(report);
4410
+ const deviceTable = visibleDevices$1.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$1.map((d) => {
4110
4411
  const editing = editingDeviceId === d.deviceId;
4111
4412
  const busy = deviceBusy === d.deviceId;
4112
- const label = deviceLabel(d);
4413
+ const label = deviceLabel$1(d);
4113
4414
  return (0, react.createElement)("tr", {
4114
4415
  key: d.deviceId,
4115
4416
  "aria-busy": busy
@@ -4154,10 +4455,10 @@ window.__ModuleLoader__.load({
4154
4455
  title: translateWith(props.t, "devices.nameCancel"),
4155
4456
  "aria-label": translateWith(props.t, "devices.nameCancel"),
4156
4457
  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", {
4458
+ }, 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 ?? 0).toLocaleString()), (0, react.createElement)("td", { className: "pbb-tableActionCell" }, !editing ? (0, react.createElement)("button", {
4158
4459
  type: "button",
4159
4460
  className: "pbb-action",
4160
- disabled: busy || editingDeviceId !== null || typeof props.setDeviceName !== "function",
4461
+ disabled: busy || editingDeviceId !== null || !can("setDeviceName"),
4161
4462
  "aria-label": translateWith(props.t, "devices.renameAria", { name: label }),
4162
4463
  onClick: () => beginDeviceRename(d)
4163
4464
  }, translateWith(props.t, "devices.rename")) : null, (0, react.createElement)("button", {
@@ -4172,34 +4473,21 @@ window.__ModuleLoader__.load({
4172
4473
  const switchDesc = switchReady ? enabled ? translateWith(props.t, "master.on") : translateWith(props.t, "master.off") : translateWith(props.t, "master.loading");
4173
4474
  const localMeta = report === null ? null : LOCAL_PHASE_META[report.local.phase];
4174
4475
  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);
4476
+ const service = serviceStatus({
4477
+ enabled,
4478
+ switchReady,
4479
+ localDot: localMeta?.dot,
4480
+ remoteDot: remoteMeta?.dot
4481
+ });
4482
+ const serviceStatusText = translateWith(props.t, SERVICE_STATUS_KEY[service.kind]);
4483
+ const serviceDot = service.dot;
4196
4484
  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
4485
  type: "button",
4198
4486
  className: "pbb-refresh",
4199
4487
  onClick: () => {
4200
- if (typeof props.refresh === "function") props.refresh();
4488
+ if (can("refresh")) props.refresh();
4201
4489
  }
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", {
4490
+ }, 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 }), serviceStatusText)), (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", {
4203
4491
  type: "button",
4204
4492
  role: "switch",
4205
4493
  "aria-checked": enabled,
@@ -4207,12 +4495,12 @@ window.__ModuleLoader__.load({
4207
4495
  disabled: !switchReady,
4208
4496
  className: "pbb-switch" + (enabled ? " pbb-switchOn" : ""),
4209
4497
  onClick: () => {
4210
- if (typeof props.setDeepPilotEnabled === "function") props.setDeepPilotEnabled(!enabled);
4498
+ if (can("setEnabled")) props.setDeepPilotEnabled(!enabled);
4211
4499
  }
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);
4500
+ })), (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, LOCAL_LABEL_KEY[localMeta.label]), () => {
4501
+ if (can("setLocalEnabled")) props.setDeepPilotLocalEnabled(!localEnabled);
4502
+ }, (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, REMOTE_LABEL_KEY[remoteMeta.label]), () => {
4503
+ if (can("setRemoteEnabled")) props.setDeepPilotRemoteEnabled(!remoteEnabled);
4216
4504
  }, (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
4505
  className: "pbb-action pbb-buttonPrimary",
4218
4506
  href: report.remote.authURL,
@@ -4221,18 +4509,21 @@ window.__ModuleLoader__.load({
4221
4509
  }, 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", {
4222
4510
  className: "pbb-switchTitle",
4223
4511
  htmlFor: "deeppilot-local-port"
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", {
4512
+ }, translateWith(props.t, "local.portTitle")), (0, react.createElement)("span", { className: "pbb-switchDesc" }, translateWith(props.t, "local.portDescription")), !localPortView.valid ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "local.portInvalid")) : localPortDraft.message ? (0, react.createElement)("p", { className: "pbb-diag" + (localPortDraft.failed ? " pbb-diagBad" : "") }, localPortDraft.message) : null), (0, react.createElement)("div", { className: "pbb-limitControl" }, (0, react.createElement)("input", {
4225
4513
  id: "deeppilot-local-port",
4226
4514
  className: "pbb-numberInput",
4227
4515
  type: "number",
4228
- min: MIN_LOCAL_PORT,
4229
- max: MAX_LOCAL_PORT,
4516
+ min: LOCAL_PORT_RANGE.min,
4517
+ max: LOCAL_PORT_RANGE.max,
4230
4518
  step: 1,
4231
4519
  inputMode: "numeric",
4232
- value: localPortDraft,
4520
+ value: localPortDraft.draft,
4233
4521
  disabled: !localPortReady,
4234
4522
  "aria-label": translateWith(props.t, "local.portTitle"),
4235
- onChange: (event) => setLocalPortDraft(event.currentTarget.value),
4523
+ onChange: (event) => dispatchLocalPortDraft({
4524
+ type: "edit",
4525
+ draft: event.currentTarget.value
4526
+ }),
4236
4527
  onKeyDown: (event) => {
4237
4528
  if (event.key === "Enter") {
4238
4529
  event.preventDefault();
@@ -4242,23 +4533,26 @@ window.__ModuleLoader__.load({
4242
4533
  }), (0, react.createElement)("button", {
4243
4534
  type: "button",
4244
4535
  className: "pbb-action",
4245
- disabled: !localPortReady || !localPortValid || parsedLocalPort === localPort,
4536
+ disabled: !localPortReady || !localPortView.valid || localPortView.unchanged,
4246
4537
  onClick: applyLocalPort
4247
4538
  }, translateWith(props.t, "remote.limitApply")))), (0, react.createElement)("div", { className: "pbb-limitRow pbb-limitNested" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("label", {
4248
4539
  className: "pbb-switchTitle",
4249
4540
  htmlFor: "deeppilot-funnel-source-limit"
4250
- }, translateWith(props.t, "remote.limitTitle")), (0, react.createElement)("span", { className: "pbb-switchDesc" }, translateWith(props.t, "remote.limitDescription")), !remoteLimitValid ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "remote.limitInvalid")) : remoteLimitMessage ? (0, react.createElement)("p", { className: "pbb-diag" + (remoteLimitMessage.startsWith(translateWith(props.t, "remote.limitFailed")) ? " pbb-diagBad" : "") }, remoteLimitMessage) : null), (0, react.createElement)("div", { className: "pbb-limitControl" }, (0, react.createElement)("input", {
4541
+ }, translateWith(props.t, "remote.limitTitle")), (0, react.createElement)("span", { className: "pbb-switchDesc" }, translateWith(props.t, "remote.limitDescription")), !remoteLimitView.valid ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "remote.limitInvalid")) : remoteLimitDraft.message ? (0, react.createElement)("p", { className: "pbb-diag" + (remoteLimitDraft.failed ? " pbb-diagBad" : "") }, remoteLimitDraft.message) : null), (0, react.createElement)("div", { className: "pbb-limitControl" }, (0, react.createElement)("input", {
4251
4542
  id: "deeppilot-funnel-source-limit",
4252
4543
  className: "pbb-numberInput",
4253
4544
  type: "number",
4254
4545
  min: 1,
4255
- max: 16,
4546
+ max: REMOTE_LIMIT_RANGE.max,
4256
4547
  step: 1,
4257
4548
  inputMode: "numeric",
4258
- value: remoteLimitDraft,
4549
+ value: remoteLimitDraft.draft,
4259
4550
  disabled: !remoteConnectionLimitReady,
4260
4551
  "aria-label": translateWith(props.t, "remote.limitTitle"),
4261
- onChange: (event) => setRemoteLimitDraft(event.currentTarget.value),
4552
+ onChange: (event) => dispatchRemoteLimitDraft({
4553
+ type: "edit",
4554
+ draft: event.currentTarget.value
4555
+ }),
4262
4556
  onKeyDown: (event) => {
4263
4557
  if (event.key === "Enter") {
4264
4558
  event.preventDefault();
@@ -4268,7 +4562,7 @@ window.__ModuleLoader__.load({
4268
4562
  }), (0, react.createElement)("button", {
4269
4563
  type: "button",
4270
4564
  className: "pbb-action",
4271
- disabled: !remoteConnectionLimitReady || !remoteLimitValid || parsedRemoteLimit === remoteConnectionLimit,
4565
+ disabled: !remoteConnectionLimitReady || !remoteLimitView.valid || remoteLimitView.unchanged,
4272
4566
  onClick: applyRemoteLimit
4273
4567
  }, translateWith(props.t, "remote.limitApply")))))), (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "help.remoteTitle")), (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.recommended")), (0, react.createElement)("ol", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.step1")), (0, react.createElement)("li", null, translateWith(props.t, "help.step2")), (0, react.createElement)("li", null, translateWith(props.t, "help.step3")), (0, react.createElement)("li", null, translateWith(props.t, "help.step4"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.funnelHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.httpsTitle")), (0, react.createElement)("ol", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep1")), (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep2")), (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep3"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.httpsHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.allowTitle")), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.allowBody")), (0, react.createElement)("code", { className: "pbb-helpCode" }, "\"nodeAttrs\": [\n {\n \"target\": [\"autogroup:member\"],\n \"attr\": [\"funnel\"],\n },\n],"), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.allowHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.faqTitle")), (0, react.createElement)("ul", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.faq1")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq2")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq3")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq4")))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.securityTitle")), (0, react.createElement)("ul", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.security1")), (0, react.createElement)("li", null, translateWith(props.t, "help.security2")), (0, react.createElement)("li", null, translateWith(props.t, "help.security3")), (0, react.createElement)("li", null, translateWith(props.t, "help.security4")), (0, react.createElement)("li", null, translateWith(props.t, "help.security5"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.docsPrefix"), (0, react.createElement)("a", {
4274
4568
  className: "pbb-helpLink",
@@ -4307,7 +4601,7 @@ window.__ModuleLoader__.load({
4307
4601
  className: "pbb-action pbb-buttonPrimary",
4308
4602
  onClick: copyPairingInfo,
4309
4603
  "data-testid": "pairingInfoCopy"
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", {
4604
+ }, 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$1.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
4605
  className: "pbb-card pbb-disclosure",
4312
4606
  open: troubleshootingOpen,
4313
4607
  onToggle: (event) => setTroubleshootingOpen(event.currentTarget.open)
@@ -4321,7 +4615,17 @@ window.__ModuleLoader__.load({
4321
4615
  className: "pbb-action",
4322
4616
  disabled: pushTestBusy,
4323
4617
  onClick: sendPushTest
4324
- }, pushTestBusy ? translateWith(props.t, "push.pushSending") : translateWith(props.t, "push.testPush")))), relayTestError ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, relayTestError) : null, relayTestResult === null ? (0, react.createElement)("p", { className: "pbb-diag" }, translateWith(props.t, "push.relayDefault")) : (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, relayTestResult.url || translateWith(props.t, "push.relayUrlEmpty")), (0, react.createElement)("code", { className: "pbb-token " + (relayTestResult.overall === "ok" ? "pbb-ok" : "pbb-bad") }, relayTestResult.overall === "ok" ? translateWith(props.t, "push.relayOk") : translateWith(props.t, "push.relayBad"))), relayTestResult.steps.map((step, index) => (0, react.createElement)("p", {
4618
+ }, pushTestBusy ? translateWith(props.t, "push.pushSending") : translateWith(props.t, "push.testPush"))), (0, react.createElement)("div", { className: "pbb-testItem" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("span", { className: "pbb-testTitle" }, translateWith(props.t, "troubleshooting.debugTitle")), (0, react.createElement)("span", { className: "pbb-testDescription" }, translateWith(props.t, "troubleshooting.debugDescription"))), (0, react.createElement)("button", {
4619
+ type: "button",
4620
+ role: "switch",
4621
+ "aria-checked": debugEnabled,
4622
+ "aria-label": translateWith(props.t, "troubleshooting.debugTitle"),
4623
+ disabled: !debugSwitchReady,
4624
+ className: "pbb-switch" + (debugEnabled ? " pbb-switchOn" : ""),
4625
+ onClick: () => {
4626
+ if (can("setDebug")) props.setDeepPilotDebug(!debugEnabled);
4627
+ }
4628
+ }))), relayTestError ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, relayTestError) : null, relayTestResult === null ? (0, react.createElement)("p", { className: "pbb-diag" }, translateWith(props.t, "push.relayDefault")) : (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, relayTestResult.url || translateWith(props.t, "push.relayUrlEmpty")), (0, react.createElement)("code", { className: "pbb-token " + (relayTestResult.overall === "ok" ? "pbb-ok" : "pbb-bad") }, relayTestResult.overall === "ok" ? translateWith(props.t, "push.relayOk") : translateWith(props.t, "push.relayBad"))), relayTestResult.steps.map((step, index) => (0, react.createElement)("p", {
4325
4629
  className: "pbb-diag",
4326
4630
  key: String(index)
4327
4631
  }, (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", {
@@ -4671,6 +4975,47 @@ window.__ModuleLoader__.load({
4671
4975
  throw error;
4672
4976
  }
4673
4977
  };
4978
+ const debugStore = createSnapshotStore({
4979
+ status: "loading",
4980
+ enabled: false
4981
+ });
4982
+ const adoptDebug = () => {
4983
+ if (scope === void 0) return;
4984
+ const snap = scope.getSnapshot();
4985
+ if (snap.status === "ready") debugStore.set({
4986
+ status: "ready",
4987
+ enabled: snap.value?.diagnostics?.debug === true
4988
+ });
4989
+ else if (snap.status === "unavailable") debugStore.set({
4990
+ status: "unavailable",
4991
+ enabled: false
4992
+ });
4993
+ };
4994
+ scopeAdoptions.push(adoptDebug);
4995
+ const lastConfirmedDebug = () => {
4996
+ const snap = scope?.getSnapshot();
4997
+ if (snap?.status === "ready") return snap.value?.diagnostics?.debug === true;
4998
+ return debugStore.getSnapshot().enabled;
4999
+ };
5000
+ const setDeepPilotDebug = (value) => {
5001
+ const previous = lastConfirmedDebug();
5002
+ debugStore.set({
5003
+ status: "ready",
5004
+ enabled: value
5005
+ });
5006
+ if (scope === void 0) return;
5007
+ const currentDiagnostics = scope.getSnapshot().value?.diagnostics ?? {};
5008
+ scope.set("diagnostics", {
5009
+ ...currentDiagnostics,
5010
+ debug: value
5011
+ }).then(() => {}, (error) => {
5012
+ debugStore.set({
5013
+ status: "ready",
5014
+ enabled: previous
5015
+ });
5016
+ console.error("[deeppilot] failed to persist diagnostics.debug=" + String(value) + ": " + (error instanceof Error ? error.message : String(error)));
5017
+ });
5018
+ };
4674
5019
  const remoteEnabledStore = createSnapshotStore({
4675
5020
  status: "loading",
4676
5021
  enabled: false
@@ -4783,7 +5128,8 @@ window.__ModuleLoader__.load({
4783
5128
  deepPilotLocalEnabled: localEnabledStore,
4784
5129
  deepPilotLocalPort: localPortStore,
4785
5130
  deepPilotRemoteEnabled: remoteEnabledStore,
4786
- deepPilotRemoteConnectionLimit: remoteConnectionLimitStore
5131
+ deepPilotRemoteConnectionLimit: remoteConnectionLimitStore,
5132
+ deepPilotDebug: debugStore
4787
5133
  },
4788
5134
  refresh: () => {
4789
5135
  controller.refresh();
@@ -4798,6 +5144,7 @@ window.__ModuleLoader__.load({
4798
5144
  setDeepPilotLocalPort,
4799
5145
  setDeepPilotRemoteEnabled,
4800
5146
  setDeepPilotRemoteConnectionLimit,
5147
+ setDeepPilotDebug,
4801
5148
  t: (key, vars) => t(ctx, key, vars)
4802
5149
  })
4803
5150
  }, DeepPilotSettingsPage));