@linxin666/dsh-remote-web-ui 0.4.2 → 0.4.4

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.
Files changed (79) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +12 -10
  3. package/README.zh.md +12 -10
  4. package/lib/client.js +278 -599
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +396 -913
  7. package/lib/types/client/RemoteEntry.d.ts.map +1 -1
  8. package/lib/types/client/RemoteEntry.js +8 -9
  9. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  10. package/lib/types/client/RemoteSettingsCard.js +1 -1
  11. package/lib/types/client/index.d.ts +32 -3
  12. package/lib/types/client/index.d.ts.map +1 -1
  13. package/lib/types/client/index.js +51 -8
  14. package/lib/types/client/locales.d.ts +0 -84
  15. package/lib/types/client/locales.d.ts.map +1 -1
  16. package/lib/types/client/locales.js +0 -84
  17. package/lib/types/client/mobile-adapt.d.ts.map +1 -1
  18. package/lib/types/client/mobile-adapt.js +142 -8
  19. package/lib/types/client/settings-entry-form.d.ts +50 -0
  20. package/lib/types/client/settings-entry-form.d.ts.map +1 -0
  21. package/lib/types/client/settings-entry-form.js +98 -0
  22. package/lib/types/index.d.ts +99 -7
  23. package/lib/types/index.d.ts.map +1 -1
  24. package/lib/types/index.js +150 -149
  25. package/lib/types/lan-bind-plan.d.ts +14 -0
  26. package/lib/types/lan-bind-plan.d.ts.map +1 -1
  27. package/lib/types/lan-bind-plan.js +16 -0
  28. package/lib/types/lan-bind.d.ts +1 -1
  29. package/lib/types/lan-bind.js +1 -1
  30. package/lib/types/mount-once.d.ts +38 -7
  31. package/lib/types/mount-once.d.ts.map +1 -1
  32. package/lib/types/mount-once.js +96 -12
  33. package/lib/types/pairing.d.ts.map +1 -1
  34. package/lib/types/pairing.js +43 -0
  35. package/lib/types/remote-api.d.ts +13 -1
  36. package/lib/types/remote-api.d.ts.map +1 -1
  37. package/lib/types/remote-api.js +14 -1
  38. package/lib/types/tunnel.d.ts +60 -3
  39. package/lib/types/tunnel.d.ts.map +1 -1
  40. package/lib/types/tunnel.js +136 -3
  41. package/package.json +20 -20
  42. package/src/client/RemoteEntry.tsx +7 -9
  43. package/src/client/RemoteSettingsCard.tsx +0 -2
  44. package/src/client/index.ts +53 -9
  45. package/src/client/locales.ts +0 -84
  46. package/src/client/mobile-adapt.ts +144 -5
  47. package/src/client/remote.module.css +50 -200
  48. package/src/client/settings-entry-form.ts +142 -0
  49. package/src/index.ts +202 -164
  50. package/src/lan-bind-plan.ts +22 -0
  51. package/src/lan-bind.ts +1 -1
  52. package/src/mount-once.ts +107 -13
  53. package/src/pairing.ts +44 -1
  54. package/src/remote-api.ts +14 -1
  55. package/src/tunnel.ts +142 -3
  56. package/lib/types/client/UpdateEntry.d.ts +0 -14
  57. package/lib/types/client/UpdateEntry.d.ts.map +0 -1
  58. package/lib/types/client/UpdateEntry.js +0 -112
  59. package/lib/types/client/UpdatePanel.d.ts +0 -42
  60. package/lib/types/client/UpdatePanel.d.ts.map +0 -1
  61. package/lib/types/client/UpdatePanel.js +0 -101
  62. package/lib/types/client/update-api.d.ts +0 -29
  63. package/lib/types/client/update-api.d.ts.map +0 -1
  64. package/lib/types/client/update-api.js +0 -47
  65. package/lib/types/console-output.d.ts +0 -41
  66. package/lib/types/console-output.d.ts.map +0 -1
  67. package/lib/types/console-output.js +0 -89
  68. package/lib/types/update-routes.d.ts +0 -30
  69. package/lib/types/update-routes.d.ts.map +0 -1
  70. package/lib/types/update-routes.js +0 -47
  71. package/lib/types/update.d.ts +0 -282
  72. package/lib/types/update.d.ts.map +0 -1
  73. package/lib/types/update.js +0 -773
  74. package/src/client/UpdateEntry.tsx +0 -144
  75. package/src/client/UpdatePanel.tsx +0 -256
  76. package/src/client/update-api.ts +0 -49
  77. package/src/console-output.ts +0 -97
  78. package/src/update-routes.ts +0 -65
  79. package/src/update.ts +0 -915
package/lib/client.js CHANGED
@@ -1409,7 +1409,7 @@ window.__ModuleLoader__.load({
1409
1409
  }
1410
1410
  //#endregion
1411
1411
  //#region \0dsh-css:packages/dsh-remote-web-ui/src/client/remote.module.css.mjs
1412
- const css$1 = ".fThDlq_overlay{z-index:1000;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.fThDlq_mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}.fThDlq_trigger{width:36px;height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;transition:background-color .12s,color .12s,box-shadow .12s;display:inline-flex;position:relative}.fThDlq_trigger[data-update-available]{color:var(--dsw-alias-brand-primary)}.fThDlq_trigger[data-update-available]:after{box-sizing:border-box;border:2px solid var(--dsw-alias-bg-layer-1);background:var(--dsw-alias-brand-primary);content:\"\";pointer-events:none;border-radius:50%;width:8px;height:8px;position:absolute;top:4px;right:4px}.fThDlq_trigger[data-wide=wide]{border-radius:999px;flex:auto;justify-content:flex-start;gap:8px;width:auto;min-width:0;padding:0 10px}.fThDlq_trigger[data-update-available][data-wide=wide]{flex:none;gap:6px}.fThDlq_trigger[data-update-available][data-wide=wide]:after{display:none}.fThDlq_updateBadgeText{white-space:nowrap;font-size:13px;font-weight:500;line-height:1}.fThDlq_trigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_trigger:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_trigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_trigger:disabled{opacity:.5;cursor:default}.fThDlq_panel{z-index:1;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);width:560px;max-width:calc(100vw - 48px);max-height:calc(100vh - 48px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:24px;flex-direction:column;gap:14px;padding:24px;font-size:14px;line-height:22px;display:flex;position:relative;overflow:auto}.fThDlq_header{align-items:flex-start;gap:12px;display:flex}.fThDlq_heading{flex:1;min-width:0}.fThDlq_title{margin:0;font-size:18px;font-weight:600;line-height:26px}.fThDlq_subtitle{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px}.fThDlq_close{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;transition:background-color .12s,color .12s,box-shadow .12s;display:inline-flex}.fThDlq_close:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_close:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_close:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_close:disabled{opacity:.5;cursor:default}.fThDlq_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;align-items:center;gap:12px;padding:16px;display:flex}.fThDlq_cardHeader{justify-content:space-between;align-items:center;gap:12px;width:100%;display:flex}.fThDlq_cardTitle{font-weight:500}.fThDlq_badge{white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:6px;min-width:0;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex}.fThDlq_badge:before{content:\"\";background:currentColor;border-radius:50%;width:8px;height:8px}.fThDlq_badge-waiting{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-connected{color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-disconnected{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-stopped{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badgePublic{color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badges{flex:none;align-items:center;gap:6px;display:inline-flex}.fThDlq_qrWrap{background:var(--dsw-alias-bg-base);border-radius:12px;justify-content:center;align-items:center;padding:12px;display:flex}.fThDlq_qr{display:block}.fThDlq_expired{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_expiry{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px}.fThDlq_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_link{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-caption);font-family:var(--dsw-font-mono,ui-monospace, monospace);margin:0;font-size:12px;display:block;overflow:hidden}.fThDlq_pairLinks{flex-direction:column;gap:8px;display:flex}.fThDlq_pairLinkRow{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;align-items:center;gap:10px;min-width:0;padding:10px 12px;display:flex}.fThDlq_pairLinkText{flex:1;min-width:0}.fThDlq_pairLinkLabel{color:var(--dsw-alias-label-secondary);margin-bottom:3px;font-size:12px;display:block}.fThDlq_copyLink{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);min-height:30px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;flex:none;align-items:center;gap:5px;padding:0 10px;display:inline-flex}.fThDlq_oneTimeHint{color:var(--dsw-alias-label-caption);margin:0;font-size:12px}.fThDlq_stoppedHint{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_tunnelNote{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_tunnelFailed{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_actions{gap:8px;display:flex}.fThDlq_action{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);height:34px;color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;border-radius:10px;justify-content:center;align-items:center;gap:6px;padding:0 14px;font-size:13px;transition:background-color .12s,border-color .12s,box-shadow .12s;display:inline-flex}.fThDlq_action:hover:not(:disabled){background:var(--dsw-alias-button-floating-hover)}.fThDlq_action:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_action:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_action:disabled{opacity:.5;cursor:default}.fThDlq_banner{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;padding:16px}.fThDlq_bannerTitle{color:var(--dsw-alias-state-warn-primary);margin:0;font-weight:500}.fThDlq_bannerHint{color:var(--dsw-alias-label-secondary);margin:6px 0 0;font-size:13px}.fThDlq_fencePage{z-index:2000;box-sizing:border-box;background:var(--dsw-alias-bg-base);text-align:center;flex-direction:column;justify-content:center;align-items:center;padding:40px 24px;display:flex;position:fixed;inset:0;overflow:auto}.fThDlq_fenceCard{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:min(520px,100%);box-shadow:var(--dsw-shadow-lv3);text-align:center;border-radius:20px;margin-inline:auto;padding:36px 40px}.fThDlq_fenceMark{background:var(--dsw-alias-state-error-secondary);width:44px;height:44px;color:var(--dsw-alias-state-error-primary);border-radius:50%;place-items:center;margin-inline:auto;font-size:24px;line-height:1;display:grid}.fThDlq_fenceEyebrow{color:var(--dsw-alias-state-error-primary);margin:22px 0 8px;font-size:13px;font-weight:600}.fThDlq_fenceTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:24px;line-height:1.35}.fThDlq_fenceDetail{color:var(--dsw-alias-label-secondary);margin:12px 0 0;font-size:14px;line-height:1.65}.fThDlq_fenceSteps{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);text-align:left;border-radius:12px;margin:24px auto 0;padding:20px 20px 20px 42px;font-size:14px;line-height:1.65}.fThDlq_fenceSteps li+li{margin-top:8px}.fThDlq_fenceForm{width:100%;margin-top:20px}.fThDlq_fenceInputRow{gap:8px;width:100%;display:flex}.fThDlq_fenceInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:0;color:var(--dsw-alias-label-primary);font:inherit;border-radius:10px;flex:1;padding:10px 14px;font-size:13px;transition:border-color .12s,box-shadow .12s}.fThDlq_fenceInput:focus{border-color:var(--dsw-alias-brand-primary);outline:none;box-shadow:0 0 0 2px #0066ff26}.fThDlq_fencePairButton{border:1px solid var(--dsw-alias-button-primary-fill);background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;cursor:pointer;border-radius:10px;flex-shrink:0;padding:10px 18px;font-size:13px;font-weight:500;transition:filter .12s,opacity .12s}.fThDlq_fencePairButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill));border-color:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill))}.fThDlq_fencePairButton:disabled{opacity:.55;cursor:not-allowed}.fThDlq_fencePairButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.fThDlq_fenceError{color:var(--dsw-alias-state-error-primary);text-align:left;margin:10px 0 0;font-size:13px;line-height:1.4}.fThDlq_fenceRetry{border:1px solid var(--dsw-alias-border-l2);width:100%;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:10px;margin-top:12px;padding:10px 16px;font-weight:500;transition:background-color .12s,color .12s}.fThDlq_fenceRetry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_fenceRetry:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.fThDlq_fenceFootnote{color:var(--dsw-alias-label-tertiary);margin:14px 0 0;font-size:12px;line-height:1.55}.fThDlq_addresses{border:none;margin:12px 0 0;padding:0}.fThDlq_addresses legend{color:var(--dsw-alias-label-secondary);padding:0;font-size:13px}.fThDlq_address{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;cursor:pointer;border-radius:6px;align-items:center;gap:8px;margin-top:6px;padding:4px 6px;font-size:13px;transition:background-color .12s;display:flex}.fThDlq_address:hover{background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_address input:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);border-radius:50%;outline:none}.fThDlq_addressValue{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;overflow:hidden}.fThDlq_addressHint{color:var(--dsw-alias-label-tertiary);margin:6px 0 0;font-size:12px}.fThDlq_devices{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;gap:8px;padding:12px 16px 14px;display:flex}.fThDlq_devicesTitle{margin:0;font-size:13px;font-weight:500;line-height:20px}.fThDlq_devicesEmpty{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_deviceList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}.fThDlq_deviceRow{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.fThDlq_deviceMeta{flex-direction:column;gap:2px;min-width:0;display:flex}.fThDlq_deviceName{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.fThDlq_devicePresence{font-size:12px;line-height:18px}.fThDlq_deviceOnline{color:var(--dsw-alias-state-success-primary)}.fThDlq_deviceOffline{color:var(--dsw-alias-label-secondary)}.fThDlq_deviceSeen{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:12px}.fThDlq_deviceRevoke{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;padding:6px 10px;font-size:12px;transition:background-color .12s,color .12s}.fThDlq_deviceRevoke:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_deviceRevoke:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}@media (prefers-reduced-motion:reduce){.fThDlq_trigger,.fThDlq_close,.fThDlq_action,.fThDlq_address,.fThDlq_deviceRevoke{transition:none}}.fThDlq_entryRow{flex:none;align-items:center;gap:6px;min-width:0;display:flex}.fThDlq_entryRow[data-rail=rail]{flex-direction:column-reverse;gap:4px}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footArea]{flex-flow:wrap;align-items:center}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footArea]>:not([class*=settingsArea]):not([class*=footerActions]){flex:100%;min-width:0}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=settingsArea]{flex:auto;order:1;width:auto;min-width:0}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footerActions]{flex:none;order:2;align-items:center;width:auto;min-width:0}[data-dsh-frame][data-sidebar-collapsed] [class*=footerActions]:has([data-rail=rail]){flex-direction:column;align-items:center;gap:4px}.fThDlq_updateStatus{margin:0;font-weight:600}.fThDlq_updateDetail{color:var(--dsw-alias-label-secondary);margin:6px 0 0;font-size:13px}.fThDlq_updateError{color:var(--dsw-alias-text-danger,var(--dsw-alias-label-primary));margin:0;font-weight:600}.fThDlq_updateOutput{background:var(--dsw-alias-bg-layer-1);max-height:180px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-all;border-radius:10px;margin:10px 0 0;padding:10px 12px;font-size:12px;line-height:18px;overflow:auto}.fThDlq_updateList{flex-direction:column;gap:6px;margin:12px 0 0;padding:0;list-style:none;display:flex}.fThDlq_updateListItem{justify-content:space-between;align-items:baseline;gap:12px;font-size:13px;display:flex}.fThDlq_updateListName{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);overflow:hidden}.fThDlq_updateListVersions{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none}.fThDlq_updateNotes{margin-top:14px}.fThDlq_updateNotesTitle{margin:0 0 8px;font-size:14px;font-weight:600}.fThDlq_updateNoteSection{flex-direction:column;gap:4px;margin-top:10px;display:flex}.fThDlq_updateNoteHeading{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;font-weight:600}.fThDlq_updateNoteList{flex-direction:column;gap:4px;margin:0;padding-left:18px;font-size:13px;display:flex}.fThDlq_updateNoteItem{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}.fThDlq_updateVersions{margin-top:12px}.fThDlq_updateVersionsSummary{cursor:pointer;color:var(--dsw-alias-label-secondary);user-select:none;font-size:12px}.fThDlq_updateActions{justify-content:flex-end;margin-top:16px;display:flex}.fThDlq_updateRetry{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:10px;align-items:center;gap:6px;padding:8px 14px;font-size:13px;transition:background-color .12s,color .12s;display:inline-flex}.fThDlq_updateRetry:hover{background:var(--dsw-alias-interactive-bg-active)}@media (prefers-reduced-motion:reduce){.fThDlq_updateRetry{transition:none}}";
1412
+ const css$1 = ".fThDlq_overlay{z-index:1000;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.fThDlq_mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}.fThDlq_trigger{width:36px;height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;transition:background-color .12s,color .12s,box-shadow .12s;display:inline-flex;position:relative}.fThDlq_trigger[data-wide=wide]{border-radius:999px;flex:auto;justify-content:flex-start;gap:8px;width:auto;min-width:0;padding:0 10px}.fThDlq_trigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_trigger:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_trigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_trigger:disabled{opacity:.5;cursor:default}.fThDlq_panel{z-index:1;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);width:560px;max-width:calc(100vw - 48px);max-height:calc(100vh - 48px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:24px;flex-direction:column;gap:14px;padding:24px;font-size:14px;line-height:22px;display:flex;position:relative;overflow:auto}.fThDlq_header{align-items:flex-start;gap:12px;display:flex}.fThDlq_heading{flex:1;min-width:0}.fThDlq_title{margin:0;font-size:18px;font-weight:600;line-height:26px}.fThDlq_subtitle{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px}.fThDlq_close{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;transition:background-color .12s,color .12s,box-shadow .12s;display:inline-flex}.fThDlq_close:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_close:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_close:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_close:disabled{opacity:.5;cursor:default}.fThDlq_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;align-items:center;gap:12px;padding:16px;display:flex}.fThDlq_cardHeader{justify-content:space-between;align-items:center;gap:12px;width:100%;display:flex}.fThDlq_cardTitle{font-weight:500}.fThDlq_badge{white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:6px;min-width:0;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex}.fThDlq_badge:before{content:\"\";background:currentColor;border-radius:50%;width:8px;height:8px}.fThDlq_badge-waiting{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-connected{color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-disconnected{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-stopped{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badgePublic{color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badges{flex:none;align-items:center;gap:6px;display:inline-flex}.fThDlq_qrWrap{background:var(--dsw-alias-bg-base);border-radius:12px;justify-content:center;align-items:center;padding:12px;display:flex}.fThDlq_qr{display:block}.fThDlq_expired{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_expiry{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px}.fThDlq_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_link{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-caption);font-family:var(--dsw-font-mono,ui-monospace, monospace);margin:0;font-size:12px;display:block;overflow:hidden}.fThDlq_pairLinks{flex-direction:column;gap:8px;display:flex}.fThDlq_pairLinkRow{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;align-items:center;gap:10px;min-width:0;padding:10px 12px;display:flex}.fThDlq_pairLinkText{flex:1;min-width:0}.fThDlq_pairLinkLabel{color:var(--dsw-alias-label-secondary);margin-bottom:3px;font-size:12px;display:block}.fThDlq_copyLink{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);min-height:30px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;flex:none;align-items:center;gap:5px;padding:0 10px;display:inline-flex}.fThDlq_oneTimeHint{color:var(--dsw-alias-label-caption);margin:0;font-size:12px}.fThDlq_stoppedHint{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_tunnelNote{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_tunnelFailed{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_actions{gap:8px;display:flex}.fThDlq_action{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);height:34px;color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;border-radius:10px;justify-content:center;align-items:center;gap:6px;padding:0 14px;font-size:13px;transition:background-color .12s,border-color .12s,box-shadow .12s;display:inline-flex}.fThDlq_action:hover:not(:disabled){background:var(--dsw-alias-button-floating-hover)}.fThDlq_action:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_action:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_action:disabled{opacity:.5;cursor:default}.fThDlq_banner{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;padding:16px}.fThDlq_bannerTitle{color:var(--dsw-alias-state-warn-primary);margin:0;font-weight:500}.fThDlq_bannerHint{color:var(--dsw-alias-label-secondary);margin:6px 0 0;font-size:13px}.fThDlq_fencePage{z-index:2000;box-sizing:border-box;background:var(--dsw-alias-bg-base);text-align:center;flex-direction:column;justify-content:center;align-items:center;padding:40px 24px;display:flex;position:fixed;inset:0;overflow:auto}.fThDlq_fenceCard{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:min(520px,100%);box-shadow:var(--dsw-shadow-lv3);text-align:center;border-radius:20px;margin-inline:auto;padding:36px 40px}.fThDlq_fenceMark{background:var(--dsw-alias-state-error-secondary);width:44px;height:44px;color:var(--dsw-alias-state-error-primary);border-radius:50%;place-items:center;margin-inline:auto;font-size:24px;line-height:1;display:grid}.fThDlq_fenceEyebrow{color:var(--dsw-alias-state-error-primary);margin:22px 0 8px;font-size:13px;font-weight:600}.fThDlq_fenceTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:24px;line-height:1.35}.fThDlq_fenceDetail{color:var(--dsw-alias-label-secondary);margin:12px 0 0;font-size:14px;line-height:1.65}.fThDlq_fenceSteps{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);text-align:left;border-radius:12px;margin:24px auto 0;padding:20px 20px 20px 42px;font-size:14px;line-height:1.65}.fThDlq_fenceSteps li+li{margin-top:8px}.fThDlq_fenceForm{width:100%;margin-top:20px}.fThDlq_fenceInputRow{gap:8px;width:100%;display:flex}.fThDlq_fenceInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:0;color:var(--dsw-alias-label-primary);font:inherit;border-radius:10px;flex:1;padding:10px 14px;font-size:13px;transition:border-color .12s,box-shadow .12s}.fThDlq_fenceInput:focus{border-color:var(--dsw-alias-brand-primary);outline:none;box-shadow:0 0 0 2px #0066ff26}.fThDlq_fencePairButton{border:1px solid var(--dsw-alias-button-primary-fill);background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;cursor:pointer;border-radius:10px;flex-shrink:0;padding:10px 18px;font-size:13px;font-weight:500;transition:filter .12s,opacity .12s}.fThDlq_fencePairButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill));border-color:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill))}.fThDlq_fencePairButton:disabled{opacity:.55;cursor:not-allowed}.fThDlq_fencePairButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.fThDlq_fenceError{color:var(--dsw-alias-state-error-primary);text-align:left;margin:10px 0 0;font-size:13px;line-height:1.4}.fThDlq_fenceRetry{border:1px solid var(--dsw-alias-border-l2);width:100%;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:10px;margin-top:12px;padding:10px 16px;font-weight:500;transition:background-color .12s,color .12s}.fThDlq_fenceRetry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_fenceRetry:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.fThDlq_fenceFootnote{color:var(--dsw-alias-label-tertiary);margin:14px 0 0;font-size:12px;line-height:1.55}.fThDlq_addresses{border:none;margin:12px 0 0;padding:0}.fThDlq_addresses legend{color:var(--dsw-alias-label-secondary);padding:0;font-size:13px}.fThDlq_address{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;cursor:pointer;border-radius:6px;align-items:center;gap:8px;margin-top:6px;padding:4px 6px;font-size:13px;transition:background-color .12s;display:flex}.fThDlq_address:hover{background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_address input:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);border-radius:50%;outline:none}.fThDlq_addressValue{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;overflow:hidden}.fThDlq_addressHint{color:var(--dsw-alias-label-tertiary);margin:6px 0 0;font-size:12px}.fThDlq_devices{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;gap:8px;padding:12px 16px 14px;display:flex}.fThDlq_devicesTitle{margin:0;font-size:13px;font-weight:500;line-height:20px}.fThDlq_devicesEmpty{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_deviceList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}.fThDlq_deviceRow{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.fThDlq_deviceMeta{flex-direction:column;gap:2px;min-width:0;display:flex}.fThDlq_deviceName{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.fThDlq_devicePresence{font-size:12px;line-height:18px}.fThDlq_deviceOnline{color:var(--dsw-alias-state-success-primary)}.fThDlq_deviceOffline{color:var(--dsw-alias-label-secondary)}.fThDlq_deviceSeen{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:12px}.fThDlq_deviceRevoke{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;padding:6px 10px;font-size:12px;transition:background-color .12s,color .12s}.fThDlq_deviceRevoke:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_deviceRevoke:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}@media (prefers-reduced-motion:reduce){.fThDlq_trigger,.fThDlq_close,.fThDlq_action,.fThDlq_address,.fThDlq_deviceRevoke{transition:none}}.fThDlq_entryRow{flex:none;align-items:center;gap:6px;min-width:0;display:flex}.fThDlq_entryRow[data-rail=rail]{flex-direction:column-reverse;gap:4px}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footArea]{flex-flow:wrap;align-items:center}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footArea]>:not([class*=settingsArea]):not([class*=footerActions]){flex:100%;min-width:0}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footArea]>[class*=footerActions]{display:contents}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footerActions]>[data-slot=\"sidebar.footer.action\"]>:not([data-dsh-part=entry]):not([class*=entryRow]){flex:100%;order:1;min-width:0}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footerActions]>[data-slot=\"sidebar.footer.action\"]>[data-dsh-part=entry],[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footerActions]>[data-slot=\"sidebar.footer.action\"]>[class*=entryRow]{flex:none;order:4;min-width:0}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=settingsArea]{flex:auto;order:3;width:auto;min-width:0}[data-dsh-frame][data-sidebar-collapsed] [class*=footerActions]:has([data-rail=rail]){flex-direction:column;align-items:center;gap:4px}";
1413
1413
  const tagId$1 = "@linxin666/dsh-remote-web-ui/packages/dsh-remote-web-ui/src/client/remote.module.css";
1414
1414
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1415
1415
  const tag = document.createElement("style");
@@ -1488,26 +1488,7 @@ window.__ModuleLoader__.load({
1488
1488
  "title": "fThDlq_title",
1489
1489
  "trigger": "fThDlq_trigger",
1490
1490
  "tunnelFailed": "fThDlq_tunnelFailed",
1491
- "tunnelNote": "fThDlq_tunnelNote",
1492
- "updateActions": "fThDlq_updateActions",
1493
- "updateBadgeText": "fThDlq_updateBadgeText",
1494
- "updateDetail": "fThDlq_updateDetail",
1495
- "updateError": "fThDlq_updateError",
1496
- "updateList": "fThDlq_updateList",
1497
- "updateListItem": "fThDlq_updateListItem",
1498
- "updateListName": "fThDlq_updateListName",
1499
- "updateListVersions": "fThDlq_updateListVersions",
1500
- "updateNoteHeading": "fThDlq_updateNoteHeading",
1501
- "updateNoteItem": "fThDlq_updateNoteItem",
1502
- "updateNoteList": "fThDlq_updateNoteList",
1503
- "updateNoteSection": "fThDlq_updateNoteSection",
1504
- "updateNotes": "fThDlq_updateNotes",
1505
- "updateNotesTitle": "fThDlq_updateNotesTitle",
1506
- "updateOutput": "fThDlq_updateOutput",
1507
- "updateRetry": "fThDlq_updateRetry",
1508
- "updateStatus": "fThDlq_updateStatus",
1509
- "updateVersions": "fThDlq_updateVersions",
1510
- "updateVersionsSummary": "fThDlq_updateVersionsSummary"
1491
+ "tunnelNote": "fThDlq_tunnelNote"
1511
1492
  };
1512
1493
  //#endregion
1513
1494
  //#region src/client/RemotePanel.tsx
@@ -1861,481 +1842,15 @@ window.__ModuleLoader__.load({
1861
1842
  });
1862
1843
  }
1863
1844
  //#endregion
1864
- //#region src/client/update-api.ts
1865
- /**
1866
- * Error thrown when the update status probe fails: carries the HTTP status
1867
- * so the panel can tell "the update route is not mounted" (404 — the host
1868
- * process runs an older plugin build) apart from real network failures.
1869
- */
1870
- var UpdateStatusError = class extends Error {
1871
- /** HTTP status of the failed response (0 when the fetch never returned). */
1872
- status;
1873
- constructor(status) {
1874
- super("update status unavailable (HTTP " + String(status) + ")");
1875
- this.status = status;
1876
- }
1877
- };
1878
- /**
1879
- * Probe the update status: install mode, owning profile, and the
1880
- * current-vs-latest comparison for every family package.
1881
- * @returns the status snapshot.
1882
- */
1883
- async function fetchUpdateStatus() {
1884
- let response;
1885
- try {
1886
- response = await fetch("api/update/status");
1887
- } catch {
1888
- throw new UpdateStatusError(0);
1889
- }
1890
- if (!response.ok) throw new UpdateStatusError(response.status);
1891
- return await response.json();
1892
- }
1893
- /**
1894
- * Run the update (pnpm update in the owning profile). Blocks until pnpm
1895
- * exits — the panel shows an in-flight state meanwhile.
1896
- * @returns the run outcome.
1897
- */
1898
- async function runUpdate() {
1899
- const response = await fetch("api/update/run", { method: "POST" });
1900
- if (!response.ok) throw new Error("update run unavailable");
1901
- return await response.json();
1902
- }
1903
- //#endregion
1904
- //#region src/client/UpdatePanel.tsx
1905
- /** The anchor package name (aggregate first) for copy purposes. */
1906
- function anchorName(status) {
1907
- return status?.anchor ?? status?.packages[0]?.name;
1908
- }
1909
- /** The latest npm release of the anchor, for reference copy. */
1910
- function anchorLatest(status) {
1911
- return status?.packages[0]?.latest;
1912
- }
1913
- /**
1914
- * Render the update panel.
1915
- * @param props - copy, view state, and actions.
1916
- * @returns the panel element tree.
1917
- */
1918
- function UpdatePanel({ t, view, onClose, onRecheck, onStartUpdate }) {
1919
- const status = view.kind === "result" || view.kind === "updating" ? view.status : void 0;
1920
- const title = view.kind === "done" && view.result.ok ? t("update.done") : t("update.title");
1921
- const subtitle = subtitleOf(t, view);
1922
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1923
- className: remote_module_css_default.panel,
1924
- role: "dialog",
1925
- "aria-modal": "true",
1926
- "aria-label": title,
1927
- children: [
1928
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1929
- className: remote_module_css_default.header,
1930
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1931
- className: remote_module_css_default.heading,
1932
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1933
- className: remote_module_css_default.title,
1934
- children: title
1935
- }), subtitle !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1936
- className: remote_module_css_default.subtitle,
1937
- children: subtitle
1938
- })]
1939
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1940
- type: "button",
1941
- className: remote_module_css_default.close,
1942
- "aria-label": t("update.close"),
1943
- onClick: onClose,
1944
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, {})
1945
- })]
1946
- }),
1947
- view.kind === "checking" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1948
- className: remote_module_css_default.updateStatus,
1949
- children: t("update.checking")
1950
- }),
1951
- view.kind === "result" && status !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResultBody, {
1952
- t,
1953
- status
1954
- }),
1955
- view.kind === "result" && status !== void 0 && status.mode === "npm" && status.outdated && status.error === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1956
- className: remote_module_css_default.updateActions,
1957
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1958
- type: "button",
1959
- className: remote_module_css_default.updateRetry,
1960
- onClick: () => onStartUpdate(status),
1961
- children: t("update.start")
1962
- })
1963
- }),
1964
- view.kind === "updating" && status !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
1965
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1966
- className: remote_module_css_default.updateStatus,
1967
- children: t("update.updating", {
1968
- name: anchorName(status) ?? "",
1969
- version: anchorLatest(status) ?? ""
1970
- })
1971
- }),
1972
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1973
- className: remote_module_css_default.updateDetail,
1974
- children: t("update.cooldownNotice")
1975
- }),
1976
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageSummary, {
1977
- t,
1978
- status
1979
- })
1980
- ] }),
1981
- view.kind === "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoneBody, {
1982
- t,
1983
- result: view.result
1984
- }),
1985
- view.kind === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1986
- className: remote_module_css_default.updateError,
1987
- children: view.message
1988
- }), view.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
1989
- className: remote_module_css_default.updateOutput,
1990
- children: view.detail
1991
- })] }),
1992
- (view.kind === "done" || view.kind === "error") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1993
- className: remote_module_css_default.updateActions,
1994
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1995
- type: "button",
1996
- className: remote_module_css_default.updateRetry,
1997
- onClick: onRecheck,
1998
- children: [
1999
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {}),
2000
- " ",
2001
- t("update.retry")
2002
- ]
2003
- })
2004
- })
2005
- ]
2006
- });
2007
- }
2008
- /** The subtitle copy per view state (absent on plain results). */
2009
- function subtitleOf(t, view) {
2010
- switch (view.kind) {
2011
- case "checking": return t("update.checking");
2012
- case "updating": return t("update.updatingTitle");
2013
- case "result": return;
2014
- case "done": return view.result.ok ? t("update.doneDetail") : t("update.error");
2015
- case "error": return;
2016
- }
2017
- }
2018
- /** The checked result body: mode banner + version list. */
2019
- function ResultBody({ t, status }) {
2020
- const anchor = anchorName(status);
2021
- const latest = anchorLatest(status);
2022
- if (status.mode === "link") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2023
- className: remote_module_css_default.updateStatus,
2024
- children: t("update.linkMode")
2025
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2026
- className: remote_module_css_default.updateDetail,
2027
- children: t("update.linkModeDetail", { version: latest ?? "-" })
2028
- })] });
2029
- if (status.mode === "missing") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2030
- className: remote_module_css_default.updateStatus,
2031
- children: t("update.missing")
2032
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2033
- className: remote_module_css_default.updateDetail,
2034
- children: t("update.missingDetail")
2035
- })] });
2036
- if (status.error === "registry-unreachable") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2037
- className: remote_module_css_default.updateStatus,
2038
- children: t("update.offline")
2039
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2040
- className: remote_module_css_default.updateDetail,
2041
- children: t("update.offlineDetail")
2042
- })] });
2043
- if (status.outdated) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
2044
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2045
- className: remote_module_css_default.updateStatus,
2046
- children: t("update.found")
2047
- }),
2048
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2049
- className: remote_module_css_default.updateDetail,
2050
- children: anchor !== void 0 ? t("update.foundDetail", {
2051
- name: anchor,
2052
- version: latest ?? ""
2053
- }) : ""
2054
- }),
2055
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2056
- className: remote_module_css_default.updateDetail,
2057
- children: t("update.cooldownNotice")
2058
- }),
2059
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageSummary, {
2060
- t,
2061
- status
2062
- })
2063
- ] });
2064
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
2065
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2066
- className: remote_module_css_default.updateStatus,
2067
- children: t("update.upToDate")
2068
- }),
2069
- anchor !== void 0 && latest !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2070
- className: remote_module_css_default.updateDetail,
2071
- children: t("update.upToDateDetail", {
2072
- name: anchor,
2073
- version: latest
2074
- })
2075
- }),
2076
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageSummary, {
2077
- t,
2078
- status
2079
- })
2080
- ] });
2081
- }
2082
- /** Release-note summary when available; otherwise fall back to the package list. */
2083
- function PackageSummary({ t, status }) {
2084
- if (status.notes === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageList, { status });
2085
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReleaseNotes, {
2086
- t,
2087
- notes: status.notes
2088
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
2089
- className: remote_module_css_default.updateVersions,
2090
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
2091
- className: remote_module_css_default.updateVersionsSummary,
2092
- children: t("update.componentVersions")
2093
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageList, { status })]
2094
- })] });
2095
- }
2096
- /** Render GitHub Release sections as a compact three-group list. */
2097
- function ReleaseNotes({ t, notes }) {
2098
- const sections = [
2099
- {
2100
- key: "features",
2101
- title: t("update.releaseFeatures"),
2102
- items: notes.features
2103
- },
2104
- {
2105
- key: "fixes",
2106
- title: t("update.releaseFixes"),
2107
- items: notes.fixes
2108
- },
2109
- {
2110
- key: "other",
2111
- title: t("update.releaseOther"),
2112
- items: notes.other
2113
- }
2114
- ].filter((section) => section.items.length > 0);
2115
- if (sections.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2116
- className: remote_module_css_default.updateDetail,
2117
- children: t("update.releaseUnavailable")
2118
- });
2119
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2120
- className: remote_module_css_default.updateNotes,
2121
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2122
- className: remote_module_css_default.updateNotesTitle,
2123
- children: t("update.releaseNotes", { version: notes.version })
2124
- }), sections.map((section) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2125
- className: remote_module_css_default.updateNoteSection,
2126
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
2127
- className: remote_module_css_default.updateNoteHeading,
2128
- children: section.title
2129
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2130
- className: remote_module_css_default.updateNoteList,
2131
- children: section.items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
2132
- className: remote_module_css_default.updateNoteItem,
2133
- children: item
2134
- }, index))
2135
- })]
2136
- }, section.key))]
2137
- });
2138
- }
2139
- /** The per-package current → latest comparison list. */
2140
- function PackageList({ status }) {
2141
- if (status.packages.length === 0) return null;
2142
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2143
- className: remote_module_css_default.updateList,
2144
- children: status.packages.map((packageStatus) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2145
- className: remote_module_css_default.updateListItem,
2146
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2147
- className: remote_module_css_default.updateListName,
2148
- children: packageStatus.name
2149
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2150
- className: remote_module_css_default.updateListVersions,
2151
- children: [packageStatus.current, packageStatus.latest !== void 0 && packageStatus.latest !== packageStatus.current && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" → ", packageStatus.latest] })]
2152
- })]
2153
- }, packageStatus.name))
2154
- });
2155
- }
2156
- /** The outcome body: success + restart hint, or the translated failure. */
2157
- function DoneBody({ t, result }) {
2158
- if (result.ok) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2159
- className: remote_module_css_default.updateDetail,
2160
- children: t("update.doneDetail")
2161
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2162
- className: remote_module_css_default.updateDetail,
2163
- children: t("update.restartHint")
2164
- })] });
2165
- const message = errorMessageOf(t, result);
2166
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2167
- className: remote_module_css_default.updateError,
2168
- children: message
2169
- }), result.output.trim() !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
2170
- className: remote_module_css_default.updateOutput,
2171
- children: result.output.trim()
2172
- })] });
2173
- }
2174
- /** Translate a structured failure code; fall back to the raw message. */
2175
- function errorMessageOf(t, result) {
2176
- switch (result.errorCode) {
2177
- case "pnpm-missing": return t("update.error.pnpmMissing");
2178
- case "timeout": return t("update.error.timeout");
2179
- case "not-found": return t("update.error.notFound");
2180
- case "link": return t("update.error.link");
2181
- case "pnpm-failed": return t("update.error.pnpmFailed", { code: String(result.exitCode ?? "?") });
2182
- case "stale": return t("update.error.stale");
2183
- case "verify-failed": return t("update.error.verifyFailed");
2184
- default: return result.error ?? t("update.error.unknown");
2185
- }
2186
- }
2187
- //#endregion
2188
- //#region src/client/UpdateEntry.tsx
2189
- /**
2190
- * The sidebar update seat: the download trigger beside the remote-control
2191
- * trigger plus the update panel modal. Owns the flow — probe the registry
2192
- * on open, show the result, and let the user start the update from the
2193
- * result view (#507), then report the outcome (restart hint on success,
2194
- * translated failure on error).
2195
- * Component-local state per the client stack rules.
2196
- */
2197
- /**
2198
- * Render the update trigger and panel.
2199
- * @param props - column state and locale seat.
2200
- * @returns the entry element tree.
2201
- */
2202
- function UpdateEntry({ wide, t }) {
2203
- const [open, setOpen] = (0, react.useState)(false);
2204
- const [view, setView] = (0, react.useState)({ kind: "checking" });
2205
- const [updateAvailable, setUpdateAvailable] = (0, react.useState)(false);
2206
- const runToken = (0, react.useRef)(0);
2207
- const availabilityToken = (0, react.useRef)(0);
2208
- const mounted = (0, react.useRef)(false);
2209
- const probeAvailability = (0, react.useCallback)(async () => {
2210
- const token = ++availabilityToken.current;
2211
- try {
2212
- const status = await fetchUpdateStatus();
2213
- if (token === availabilityToken.current) setUpdateAvailable(status.mode === "npm" && status.outdated);
2214
- } catch {
2215
- if (token === availabilityToken.current) setUpdateAvailable(false);
2216
- }
2217
- }, []);
2218
- const check = (0, react.useCallback)(async () => {
2219
- const availabilityCheck = ++availabilityToken.current;
2220
- setView({ kind: "checking" });
2221
- let status;
2222
- try {
2223
- status = await fetchUpdateStatus();
2224
- } catch (error) {
2225
- if (availabilityCheck === availabilityToken.current) setUpdateAvailable(false);
2226
- if (error instanceof UpdateStatusError && error.status === 404) {
2227
- setView({
2228
- kind: "error",
2229
- message: t("update.unmounted"),
2230
- detail: t("update.unmountedDetail")
2231
- });
2232
- return;
2233
- }
2234
- setView({
2235
- kind: "error",
2236
- message: t("update.offline"),
2237
- detail: t("update.offlineDetail")
2238
- });
2239
- return;
2240
- }
2241
- if (availabilityCheck === availabilityToken.current) setUpdateAvailable(status.mode === "npm" && status.outdated);
2242
- if (status.error === "registry-unreachable") {
2243
- setView({
2244
- kind: "result",
2245
- status
2246
- });
2247
- return;
2248
- }
2249
- setView({
2250
- kind: "result",
2251
- status
2252
- });
2253
- }, [t]);
2254
- const startUpdate = (0, react.useCallback)(async (status) => {
2255
- if (status.mode !== "npm" || !status.outdated) return;
2256
- setView({
2257
- kind: "updating",
2258
- status
2259
- });
2260
- const token = ++runToken.current;
2261
- try {
2262
- const result = await runUpdate();
2263
- if (result.ok && mounted.current) setUpdateAvailable(false);
2264
- if (token !== runToken.current) return;
2265
- setView({
2266
- kind: "done",
2267
- result
2268
- });
2269
- } catch {
2270
- if (token !== runToken.current) return;
2271
- setView({
2272
- kind: "error",
2273
- message: t("update.error"),
2274
- detail: t("update.offlineDetail")
2275
- });
2276
- }
2277
- }, [t]);
2278
- const openPanel = (0, react.useCallback)(() => {
2279
- setOpen(true);
2280
- check();
2281
- }, [check]);
2282
- const closePanel = (0, react.useCallback)(() => {
2283
- runToken.current++;
2284
- setOpen(false);
2285
- }, []);
2286
- (0, react.useEffect)(() => {
2287
- mounted.current = true;
2288
- probeAvailability();
2289
- return () => {
2290
- mounted.current = false;
2291
- runToken.current++;
2292
- availabilityToken.current++;
2293
- };
2294
- }, [probeAvailability]);
2295
- const updateLabel = updateAvailable ? t("update.availableLabel") : t("update.label");
2296
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2297
- type: "button",
2298
- className: remote_module_css_default.trigger,
2299
- "data-wide": wide ? updateAvailable ? "wide" : void 0 : "rail",
2300
- "data-update-available": updateAvailable ? "true" : void 0,
2301
- "aria-label": updateLabel,
2302
- "aria-expanded": open,
2303
- title: updateLabel,
2304
- onClick: openPanel,
2305
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutlineRegular, { size: wide ? 16 : 18 }), updateAvailable && wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2306
- className: remote_module_css_default.updateBadgeText,
2307
- children: t("update.badge")
2308
- })]
2309
- }), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2310
- className: remote_module_css_default.overlay,
2311
- role: "presentation",
2312
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2313
- className: remote_module_css_default.mask,
2314
- "aria-hidden": "true",
2315
- onClick: closePanel
2316
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UpdatePanel, {
2317
- t,
2318
- view,
2319
- onClose: closePanel,
2320
- onRecheck: () => {
2321
- check();
2322
- },
2323
- onStartUpdate: (status) => {
2324
- startUpdate(status);
2325
- }
2326
- })]
2327
- }), document.body)] });
2328
- }
2329
- //#endregion
2330
1845
  //#region src/client/RemoteEntry.tsx
2331
1846
  /**
2332
- * The sidebar remote-control seat: the update trigger plus the phone-icon
2333
- * trigger beside the settings button, and the pairing panel modal. Owns the
2334
- * panel behavior — token minting on open, the status SSE subscription,
2335
- * stop/refresh/copy — and renders the pure {@link RemotePanel} body. The
2336
- * update seat (the dsh-web self-update flow) rides the same footer row,
2337
- * rendered by {@link UpdateEntry}. Component-local state per the client
2338
- * stack rules: nothing here survives remounts or crosses entries.
1847
+ * The sidebar remote-control seat: the phone-icon trigger beside the
1848
+ * settings button, and the pairing panel modal. Owns the panel behavior —
1849
+ * token minting on open, the status SSE subscription, stop/refresh/copy — and
1850
+ * renders the pure {@link RemotePanel} body. The family self-update seat is
1851
+ * its own plugin (dsh-update) and no longer rides this row. Component-local
1852
+ * state per the client stack rules: nothing here survives remounts or crosses
1853
+ * entries.
2339
1854
  */
2340
1855
  /**
2341
1856
  * Apply one status frame onto the current state: the ready state mirrors
@@ -2503,18 +2018,15 @@ window.__ModuleLoader__.load({
2503
2018
  }, 1500);
2504
2019
  });
2505
2020
  }, []);
2506
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2021
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2507
2022
  className: remote_module_css_default.entryRow,
2508
2023
  "data-rail": wide ? void 0 : "rail",
2509
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UpdateEntry, {
2510
- wide,
2511
- t
2512
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipAnchor, {
2024
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipAnchor, {
2513
2025
  wide,
2514
2026
  label: t("entry.label"),
2515
2027
  onClick: openPanel,
2516
2028
  expanded: open
2517
- })]
2029
+ })
2518
2030
  }), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2519
2031
  className: remote_module_css_default.overlay,
2520
2032
  role: "presentation",
@@ -3468,8 +2980,6 @@ window.__ModuleLoader__.load({
3468
2980
  descriptionKey: "settings.description",
3469
2981
  defaultOpen: false,
3470
2982
  state,
3471
- renderChildrenWhenNotExposed: true,
3472
- hideNotExposedNotice: true,
3473
2983
  onSave: props.save,
3474
2984
  onDiscard: props.discard,
3475
2985
  children: [
@@ -3827,49 +3337,7 @@ window.__ModuleLoader__.load({
3827
3337
  "settings.discard": "放弃",
3828
3338
  "settings.unsaved": "未保存",
3829
3339
  "settings.saveFailed": "部署未接受这些值,已保留供你修改。",
3830
- "settings.invalidNumber": "请输入数字,留空则使用默认值。",
3831
- "update.label": "检查更新",
3832
- "update.badge": "有更新",
3833
- "update.availableLabel": "发现新版本,检查更新",
3834
- "update.title": "检查更新",
3835
- "update.checking": "正在检查远程仓库中的新版本…",
3836
- "update.upToDate": "当前已是最新版本",
3837
- "update.upToDateDetail": "{name} 已是最新版本 v{version}。",
3838
- "update.found": "发现新版本",
3839
- "update.foundDetail": "{name} 有新版本 v{version}。确认后开始更新。",
3840
- "update.start": "开始更新",
3841
- "update.updating": "正在更新 {name} 至 v{version},可能需要 1-2 分钟…",
3842
- "update.cooldownNotice": "本次更新由你主动触发,将跳过 pnpm 的 24 小时发布冷却期(供应链保护)直接安装所选版本。",
3843
- "update.updatingTitle": "正在更新…",
3844
- "update.done": "更新完成",
3845
- "update.doneDetail": "所有组件已更新到最新版本。",
3846
- "update.restartHint": "请重启 dsh web 使新版本生效:在终端按 Ctrl+C 停止后重新运行 dsh web。",
3847
- "update.linkMode": "当前为本地开发模式",
3848
- "update.linkModeDetail": "dsh-web 或其组件通过本地链接安装(开发模式),无法自动更新。npm 上最新版本:{version}。请同步本地仓库,或移除与全家桶重复的独立链接。",
3849
- "update.missing": "未找到 dsh-web 安装",
3850
- "update.missingDetail": "无法定位已安装的 dsh-web 聚合包。请先运行 dsh plugin --profile <profile> add @linxin666/dsh-web-all。",
3851
- "update.offline": "无法连接更新源",
3852
- "update.offlineDetail": "网络不可用或更新服务异常,请稍后重试。",
3853
- "update.unmounted": "更新服务未加载",
3854
- "update.unmountedDetail": "当前 dsh web 进程运行的插件版本不含更新服务,请重启 dsh web(Ctrl+C 后重新运行)后再试。",
3855
- "update.error": "更新失败",
3856
- "update.packages": "组件版本",
3857
- "update.releaseNotes": "v{version} 更新说明",
3858
- "update.releaseFeatures": "新增功能",
3859
- "update.releaseFixes": "修复",
3860
- "update.releaseOther": "其他变动",
3861
- "update.releaseUnavailable": "本次发布说明暂不可用,可前往 GitHub Release 查看。",
3862
- "update.componentVersions": "查看组件版本",
3863
- "update.retry": "重新检查",
3864
- "update.close": "关闭更新面板",
3865
- "update.error.pnpmMissing": "未找到 pnpm,已尝试 pnpm / corepack / npx 仍不可用。请安装 pnpm 后重启应用(Windows: npm install -g pnpm 或 corepack enable;macOS: brew install pnpm 或 corepack enable)。",
3866
- "update.error.timeout": "更新超时,已终止安装进程,请重试。",
3867
- "update.error.notFound": "未找到 dsh-web 聚合包安装,无法更新。",
3868
- "update.error.link": "dsh-web 或其组件使用本地链接,无法自动更新;请同步本地仓库,或移除与全家桶重复的独立链接。",
3869
- "update.error.pnpmFailed": "更新执行失败(pnpm 退出码 {code}),详见下方输出。",
3870
- "update.error.stale": "更新命令已执行,但安装版本未变化。常见原因:pnpm 11 的 minimumReleaseAge 门禁会静默跳过发布不足 24 小时的新版本。请在 profile 目录(~/.dsh/profiles/<profile>)的 pnpm-workspace.yaml 中配置 minimumReleaseAgeExclude(例如 \"@linxin666/*\")或 minimumReleaseAge: 0 后重新点击更新。",
3871
- "update.error.verifyFailed": "更新命令已执行,但无法核对安装后的版本(可能是 registry 探测失败或安装路径变化)。请查看下方输出,或稍后重试。",
3872
- "update.error.unknown": "更新失败,请重试。"
3340
+ "settings.invalidNumber": "请输入数字,留空则使用默认值。"
3873
3341
  };
3874
3342
  /** English dictionary, checked complete against the zh key set. */
3875
3343
  const en = {
@@ -3991,49 +3459,7 @@ window.__ModuleLoader__.load({
3991
3459
  "settings.discard": "Discard",
3992
3460
  "settings.unsaved": "Unsaved",
3993
3461
  "settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
3994
- "settings.invalidNumber": "Enter a number, or leave blank to use the default.",
3995
- "update.label": "Check for updates",
3996
- "update.badge": "Update available",
3997
- "update.availableLabel": "New version available. Check for updates",
3998
- "update.title": "Check for updates",
3999
- "update.checking": "Checking the remote repository for newer releases…",
4000
- "update.upToDate": "Everything is up to date",
4001
- "update.upToDateDetail": "{name} is at the latest version v{version}.",
4002
- "update.found": "A new version is available",
4003
- "update.foundDetail": "{name} has a newer release v{version}. Confirm to start the update.",
4004
- "update.start": "Update now",
4005
- "update.updating": "Updating {name} to v{version}; this may take 1-2 minutes…",
4006
- "update.cooldownNotice": "This user-initiated update bypasses pnpm's 24-hour release cooldown (supply-chain protection) and installs the selected version immediately.",
4007
- "update.updatingTitle": "Updating…",
4008
- "update.done": "Update complete",
4009
- "update.doneDetail": "All components are up to date.",
4010
- "update.restartHint": "Restart dsh web for the new version to take effect: stop it with Ctrl+C in the terminal, then run dsh web again.",
4011
- "update.linkMode": "Local development mode",
4012
- "update.linkModeDetail": "dsh-web or one of its components is installed via a local link and cannot be updated remotely. Latest npm release: {version}. Sync the local checkout, or remove a standalone link duplicated by the aggregate.",
4013
- "update.missing": "dsh-web not found",
4014
- "update.missingDetail": "Could not locate the installed dsh-web aggregate package. Install it with: dsh plugin --profile <profile> add @linxin666/dsh-web-all",
4015
- "update.offline": "Cannot reach the update source",
4016
- "update.offlineDetail": "The network is unavailable or the update service is failing; try again later.",
4017
- "update.unmounted": "The update service is not loaded",
4018
- "update.unmountedDetail": "The running dsh web process loads a plugin build without the update service; restart dsh web (Ctrl+C, then run dsh web again) and retry.",
4019
- "update.error": "Update failed",
4020
- "update.packages": "Component versions",
4021
- "update.releaseNotes": "v{version} release notes",
4022
- "update.releaseFeatures": "New Features",
4023
- "update.releaseFixes": "Bug Fixes",
4024
- "update.releaseOther": "Other Changes",
4025
- "update.releaseUnavailable": "Release notes are unavailable for this version; see the GitHub release instead.",
4026
- "update.componentVersions": "View component versions",
4027
- "update.retry": "Check again",
4028
- "update.close": "Close update panel",
4029
- "update.error.pnpmMissing": "pnpm was not found (tried pnpm / corepack / npx). Install pnpm and restart the app (Windows: npm install -g pnpm or corepack enable; macOS: brew install pnpm or corepack enable).",
4030
- "update.error.timeout": "The update timed out and the install process was killed; try again.",
4031
- "update.error.notFound": "The dsh-web aggregate package is not installed; nothing to update.",
4032
- "update.error.link": "dsh-web or one of its components uses a local link. Sync the checkout, or remove a standalone link duplicated by the aggregate.",
4033
- "update.error.pnpmFailed": "The update failed (pnpm exited with code {code}); see the output below.",
4034
- "update.error.stale": "The update command ran but the installed versions did not change. Likely cause: the pnpm 11 minimumReleaseAge gate silently skips releases published less than 24 hours ago. Add minimumReleaseAgeExclude (e.g. \"@linxin666/*\") or set minimumReleaseAge: 0 in pnpm-workspace.yaml under the profile directory (~/.dsh/profiles/<profile>), then run the update again.",
4035
- "update.error.verifyFailed": "The update command ran but the installed versions could not be verified afterwards (registry probe failed or the install path changed). Inspect the output below, or retry later.",
4036
- "update.error.unknown": "Update failed; try again."
3462
+ "settings.invalidNumber": "Enter a number, or leave blank to use the default."
4037
3463
  };
4038
3464
  //#endregion
4039
3465
  //#region src/client/deep-link.ts
@@ -4655,7 +4081,7 @@ window.__ModuleLoader__.load({
4655
4081
  /** The building package's version, when the bundle carries it. */
4656
4082
  function bakedVersion() {
4657
4083
  try {
4658
- return "0.4.2";
4084
+ return "0.4.4";
4659
4085
  } catch {
4660
4086
  return;
4661
4087
  }
@@ -4729,6 +4155,16 @@ window.__ModuleLoader__.load({
4729
4155
  const ACTIVE_CLASS = "dsh-remote-portrait";
4730
4156
  /** Body class while the collapsed rail is hidden behind the whale. */
4731
4157
  const RAIL_HIDDEN_CLASS = "dsh-remote-rail-hidden";
4158
+ /**
4159
+ * The official sidebar column root. Also the scope of the row sweep: every row
4160
+ * the drag suppression covers is a descendant of it, so the document-wide scan
4161
+ * it replaces only ever returned these.
4162
+ */
4163
+ const SIDEBAR_SELECTOR = "[class*=\"_sidebarCol\"]";
4164
+ /** The official draggable session/project rows (see disableRowDrag). */
4165
+ const ROW_SELECTOR = "[class*=\"_sessionRow\"], [class*=\"_projectRow\"]";
4166
+ /** The injected stylesheet, addressed through the same cached lookup. */
4167
+ const STYLE_SELECTOR = `style[data-plugin-css="${ADAPT_CSS_ID}"]`;
4732
4168
  /** Whale button id. */
4733
4169
  const WHALE_ID = "dshRemoteWhale";
4734
4170
  /** Compact picker: synthesized model button id. */
@@ -4896,6 +4332,8 @@ window.__ModuleLoader__.load({
4896
4332
  let whaleObserver = null;
4897
4333
  /** Header subtree observer: marks the geometry measurement dirty on re-render. */
4898
4334
  let headerObserver = null;
4335
+ /** Document child-list observer: invalidates the cached-absent selector set. */
4336
+ let domObserver = null;
4899
4337
  let observedHeader = null;
4900
4338
  /** Whether the seated-actions geometry needs re-measuring (see alignActionsText). */
4901
4339
  let headerGeometryDirty = true;
@@ -4914,16 +4352,19 @@ window.__ModuleLoader__.load({
4914
4352
  * rail compaction) while the body class stays.
4915
4353
  */
4916
4354
  function ensureAdaptStyle() {
4917
- if (nodeOf(`style[data-plugin-css="${ADAPT_CSS_ID}"]`) !== null) return;
4355
+ if (nodeOf(STYLE_SELECTOR) !== null) return;
4918
4356
  const tag = document.createElement("style");
4919
4357
  tag.dataset.plugin = "remote-web-ui";
4920
4358
  tag.dataset.pluginCss = ADAPT_CSS_ID;
4921
4359
  tag.textContent = ADAPT_CSS.join("");
4922
4360
  document.head.appendChild(tag);
4361
+ nodeCache.set(STYLE_SELECTOR, tag);
4362
+ nodeMissCache.delete(STYLE_SELECTOR);
4923
4363
  }
4924
4364
  function apply() {
4925
4365
  if (active) return;
4926
4366
  active = true;
4367
+ forgetAbsentSelectors();
4927
4368
  document.body.classList.add(ACTIVE_CLASS);
4928
4369
  try {
4929
4370
  w.__dshRemoteAdapt?.closeDetails?.();
@@ -4935,6 +4376,7 @@ window.__ModuleLoader__.load({
4935
4376
  meta.setAttribute("content", `${savedViewportContent}, viewport-fit=cover`);
4936
4377
  }
4937
4378
  ensureWhale();
4379
+ ensureDomObserver();
4938
4380
  syncWhale();
4939
4381
  setWhaleTimer(true);
4940
4382
  seatHeaderActions();
@@ -4960,6 +4402,40 @@ window.__ModuleLoader__.load({
4960
4402
  whaleObserver.disconnect();
4961
4403
  whaleObserver = null;
4962
4404
  }
4405
+ if (domObserver !== null) {
4406
+ domObserver.disconnect();
4407
+ domObserver = null;
4408
+ }
4409
+ forgetAbsentSelectors();
4410
+ }
4411
+ /**
4412
+ * Observe the document for the two kinds of change that can make a cached-absent
4413
+ * target discoverable again (see nodeOf): node insertion/removal, and the
4414
+ * class / compat-stamp attributes the cached selectors match on.
4415
+ *
4416
+ * Body class writes are excluded by target: the layer toggles three body
4417
+ * classes every tick, and counting those would invalidate the negative cache
4418
+ * on every tick and restore the very scan this cache removes. They cannot
4419
+ * create a target either — every cached selector matches an element other
4420
+ * than <body>.
4421
+ *
4422
+ * The record queue is drained on a microtask, so this stays off the layout
4423
+ * path; a chat turn's insertions are a handful per second, not per element.
4424
+ */
4425
+ function ensureDomObserver() {
4426
+ if (domObserver !== null || typeof MutationObserver === "undefined" || !document.body) return;
4427
+ domObserver = new MutationObserver((records) => {
4428
+ for (const record of records) if (record.type === "childList" || record.target !== document.body) {
4429
+ noteDomChange();
4430
+ return;
4431
+ }
4432
+ });
4433
+ domObserver.observe(document.body, {
4434
+ childList: true,
4435
+ subtree: true,
4436
+ attributes: true,
4437
+ attributeFilter: ["class", "data-dsh-frame"]
4438
+ });
4963
4439
  }
4964
4440
  /** Start/stop the 600ms sync tick; a no-op when already in the asked state. */
4965
4441
  function setWhaleTimer(on) {
@@ -5208,7 +4684,7 @@ window.__ModuleLoader__.load({
5208
4684
  const dragOverridden = /* @__PURE__ */ new Map();
5209
4685
  function disableRowDrag() {
5210
4686
  if (!active) return;
5211
- const rows = document.querySelectorAll("[class*=\"_sidebarCol\"] [class*=\"_sessionRow\"], [class*=\"_sidebarCol\"] [class*=\"_projectRow\"]");
4687
+ const rows = sidebarRows();
5212
4688
  for (const row of rows) if (row.getAttribute("draggable") !== "false") {
5213
4689
  if (!dragOverridden.has(row)) dragOverridden.set(row, row.getAttribute("draggable"));
5214
4690
  row.setAttribute("draggable", "false");
@@ -5229,14 +4705,83 @@ window.__ModuleLoader__.load({
5229
4705
  * node on a major re-render, which the isConnected guard detects.
5230
4706
  */
5231
4707
  const nodeCache = /* @__PURE__ */ new Map();
4708
+ /**
4709
+ * Selectors known to be absent as of {@link domGeneration}. A miss is the
4710
+ * common case, not the exception: the layer holds no overlay of its own, and
4711
+ * several official surfaces it looks for (tabs row, tools row, sidebar rows)
4712
+ * legitimately do not exist at once. Re-walking a conversation-sized document
4713
+ * for the same absent selector on every 600ms tick was waste that almost
4714
+ * always resolved to null: five document-wide lookups per tick on the measured
4715
+ * fixture, zero after this cache. That is a lookup count — the wall-clock
4716
+ * saving is real while the mounted content is quiet and shrinks to the
4717
+ * row-scope saving when a turn streams (see the Agent Note for the harness and
4718
+ * the before/after numbers).
4719
+ *
4720
+ * A miss is trusted only while the observed content has not changed, so a
4721
+ * surface that appears is still found on the very next tick — the same
4722
+ * discovery latency the unconditional probe had. The cache is never aged by a
4723
+ * clock: it is cleared by {@link noteDomChange} while the observer is live and
4724
+ * by {@link forgetAbsentSelectors} across the windows where it is not (apply
4725
+ * and revert). One blind spot is deliberate and load-bearing: the observer
4726
+ * watches <body>, so a head-resident target is not covered by that signal —
4727
+ * the one such target is seeded into the cache where it is created (see
4728
+ * ensureAdaptStyle).
4729
+ */
4730
+ const nodeMissCache = /* @__PURE__ */ new Set();
4731
+ /**
4732
+ * Bumped whenever a cached target may have appeared or disappeared. A surface
4733
+ * qualifies on a class token or on the aggregate compat stamp
4734
+ * (`data-dsh-frame`), so both insertions/removals AND those two attributes
4735
+ * can change the answer. Nothing the layer writes per tick lands here: its own
4736
+ * class writes are all on <body>, and its row/transform/label writes touch
4737
+ * other attributes entirely (see ensureDomObserver).
4738
+ */
4739
+ let domGeneration = 0;
4740
+ /** The generation the negative cache was recorded against. */
4741
+ let missGeneration = -1;
4742
+ function noteDomChange() {
4743
+ domGeneration += 1;
4744
+ }
4745
+ /**
4746
+ * Drop every cached "absent" verdict. Called on apply and revert: while the
4747
+ * observer is disconnected the layer is blind to insertions, so a verdict
4748
+ * recorded before the gap could otherwise outlive the change that falsified
4749
+ * it and be served without a probe. The positive cache needs no equivalent —
4750
+ * its `isConnected` guard already re-resolves a replaced node.
4751
+ */
4752
+ function forgetAbsentSelectors() {
4753
+ nodeMissCache.clear();
4754
+ domGeneration += 1;
4755
+ }
5232
4756
  function nodeOf(selector) {
5233
4757
  const cached = nodeCache.get(selector);
5234
- if (cached !== void 0 && cached.isConnected) return cached;
4758
+ if (cached !== void 0) {
4759
+ if (cached.isConnected) return cached;
4760
+ nodeCache.delete(selector);
4761
+ }
4762
+ if (missGeneration !== domGeneration) {
4763
+ nodeMissCache.clear();
4764
+ missGeneration = domGeneration;
4765
+ }
4766
+ if (nodeMissCache.has(selector)) return null;
5235
4767
  const found = document.querySelector(selector);
5236
- if (found === null) nodeCache.delete(selector);
5237
- else nodeCache.set(selector, found);
4768
+ if (found === null) {
4769
+ nodeMissCache.add(selector);
4770
+ return null;
4771
+ }
4772
+ nodeCache.set(selector, found);
5238
4773
  return found;
5239
4774
  }
4775
+ /**
4776
+ * The session/project rows the drag suppression covers, scoped to the cached
4777
+ * sidebar root. Each row carries the sidebar column class as an ancestor, so
4778
+ * the document-wide scan this replaces only ever returned rows from here.
4779
+ */
4780
+ function sidebarRows() {
4781
+ const sidebar = nodeOf(SIDEBAR_SELECTOR);
4782
+ if (sidebar === null) return [];
4783
+ return Array.from(sidebar.querySelectorAll(ROW_SELECTOR));
4784
+ }
5240
4785
  /** The official application frame, through the same cached lookup. */
5241
4786
  function frameEl() {
5242
4787
  const frame = nodeOf(APP_FRAME_SELECTOR);
@@ -5779,6 +5324,96 @@ window.__ModuleLoader__.load({
5779
5324
  reconcile();
5780
5325
  }
5781
5326
  //#endregion
5327
+ //#region src/client/settings-entry-form.ts
5328
+ /** The snapshot a form reports before the Host has answered with this entry. */
5329
+ function pendingSnapshot() {
5330
+ return {
5331
+ status: "loading",
5332
+ value: void 0,
5333
+ base: void 0,
5334
+ user: void 0,
5335
+ revision: void 0,
5336
+ writable: false,
5337
+ mode: "host"
5338
+ };
5339
+ }
5340
+ /**
5341
+ * Create a settings form bound to the profile entry id this Host serves, and
5342
+ * rebound whenever the shared describe mirror names a different one of this
5343
+ * package's rows.
5344
+ * @param options - the shared forms service and this package's candidate row ids.
5345
+ * @returns a form delegating to the currently resolved entry's own form.
5346
+ */
5347
+ function createServedEntryForm(options) {
5348
+ const { forms, entryIds } = options;
5349
+ const fallbackId = entryIds[0];
5350
+ const namespaceId = entryIds[entryIds.length - 1];
5351
+ const listeners = /* @__PURE__ */ new Set();
5352
+ let boundId;
5353
+ let bound;
5354
+ let offBound;
5355
+ let snapshot = pendingSnapshot();
5356
+ /** Republish: the delegated snapshot when one is bound, the pending one otherwise. */
5357
+ const publish = () => {
5358
+ if (bound !== void 0) snapshot = bound.getSnapshot();
5359
+ for (const listener of [...listeners]) listener();
5360
+ };
5361
+ /**
5362
+ * The entry id the mirror currently justifies. An unanswered or empty mirror
5363
+ * is not evidence of absence, so it keeps the first candidate; a mirror that
5364
+ * answers without any of this package's rows leaves the namespace itself.
5365
+ * @returns the entry id to bind.
5366
+ */
5367
+ const resolve = () => {
5368
+ let served;
5369
+ try {
5370
+ served = forms.describe().getSnapshot().view?.namespaces.map((row) => row.ns);
5371
+ } catch {
5372
+ served = void 0;
5373
+ }
5374
+ if (served === void 0 || served.length === 0) return fallbackId;
5375
+ return entryIds.find((id) => served.includes(id)) ?? namespaceId;
5376
+ };
5377
+ /** Bind (or rebind) the resolved entry's form; a no-op while it is unchanged. */
5378
+ const bind = () => {
5379
+ const target = resolve();
5380
+ if (target === boundId) return;
5381
+ offBound?.();
5382
+ offBound = void 0;
5383
+ let form;
5384
+ try {
5385
+ form = forms.get(target);
5386
+ } catch {
5387
+ boundId = void 0;
5388
+ return;
5389
+ }
5390
+ boundId = target;
5391
+ bound = form;
5392
+ offBound = form.subscribe(() => {
5393
+ publish();
5394
+ });
5395
+ publish();
5396
+ };
5397
+ try {
5398
+ forms.describe().subscribe(() => {
5399
+ bind();
5400
+ });
5401
+ } catch {}
5402
+ bind();
5403
+ return {
5404
+ getSnapshot: () => snapshot,
5405
+ subscribe: (listener) => {
5406
+ listeners.add(listener);
5407
+ return () => {
5408
+ listeners.delete(listener);
5409
+ };
5410
+ },
5411
+ set: (field, value) => bound?.set(field, value) ?? Promise.resolve(false),
5412
+ unset: (field) => bound?.unset(field) ?? Promise.resolve(false),
5413
+ mutate: (ops, expectedRevision) => bound?.mutate(ops, expectedRevision) ?? Promise.resolve(false)
5414
+ };
5415
+ }
5416
+ //#endregion
5782
5417
  //#region src/client/index.ts
5783
5418
  /**
5784
5419
  * Remote control — browser half. Registers the `remote` dictionaries, the
@@ -5791,13 +5426,36 @@ window.__ModuleLoader__.load({
5791
5426
  */
5792
5427
  /** Dictionary namespace owned by this plugin. */
5793
5428
  const NS = "remote";
5794
- /** Settings namespace the remote-control card edits (the Host plugin registers it). */
5429
+ /**
5430
+ * Settings namespace the remote-control card edits: the family identity of
5431
+ * this plugin's own settings form, and the row id a standalone bundle install
5432
+ * carries.
5433
+ */
5795
5434
  const REMOTE_WEB_UI_NS = "remote-web-ui";
5435
+ /** Profile entry id the family aggregate's generated row carries. */
5436
+ const AGGREGATE_ENTRY_ID = "web-ui-remote-web-ui";
5437
+ /** Profile entry ids this package's patch rows carry, most specific first. */
5796
5438
  const REMOTE_WEB_UI_ENTRY_IDS = [
5797
- "web-ui-remote-web-ui",
5439
+ AGGREGATE_ENTRY_ID,
5798
5440
  "ui-remote-web-ui",
5799
5441
  REMOTE_WEB_UI_NS
5800
5442
  ];
5443
+ /**
5444
+ * The profile entry id this package's own row carries, for a page that serves
5445
+ * no family binder.
5446
+ *
5447
+ * The shared describe mirror answers asynchronously, so at plugin activation it
5448
+ * usually holds nothing yet — and an unanswered or empty mirror is not evidence
5449
+ * of absence. Binding the bare namespace there left the card bound to an entry
5450
+ * the Host does not serve, and the form is bound once per session, so the card
5451
+ * never recovered (every save answered `No configurable plugin entry`). The
5452
+ * aggregate row id matches nearly every deployment; only a mirror that answers
5453
+ * with OTHER plugins' rows falls back to the namespace itself (the pre-0.1.7
5454
+ * keying shape), because that answer is the one that actually proves this
5455
+ * package's own row is not served.
5456
+ * @param forms - the shared configuration forms service.
5457
+ * @returns the entry id to bind.
5458
+ */
5801
5459
  function servedEntryId(forms) {
5802
5460
  let served;
5803
5461
  try {
@@ -5805,9 +5463,29 @@ window.__ModuleLoader__.load({
5805
5463
  } catch {
5806
5464
  served = void 0;
5807
5465
  }
5808
- if (!served || served.length === 0) return REMOTE_WEB_UI_NS;
5466
+ if (served === void 0 || served.length === 0) return AGGREGATE_ENTRY_ID;
5809
5467
  return REMOTE_WEB_UI_ENTRY_IDS.find((id) => served.includes(id)) ?? REMOTE_WEB_UI_NS;
5810
5468
  }
5469
+ /**
5470
+ * Bind the settings form the remote-control card reads and writes.
5471
+ *
5472
+ * The family binder comes first: it resolves this package's family namespace
5473
+ * onto the profile entry id the Host serves the form under, and keeps the
5474
+ * loopback bridge as its own fallback. A page without that group (or one where
5475
+ * its client half has not applied yet) binds through the shared forms service,
5476
+ * on the entry id the describe mirror justifies and rebound as soon as the
5477
+ * mirror answers — see {@link createServedEntryForm}.
5478
+ * @param ctx - client context carrying the family binder and/or the shared forms service.
5479
+ * @returns the form the card stages and saves through.
5480
+ */
5481
+ function bindSettingsForm(ctx) {
5482
+ const family = ctx.get("webUiSettings");
5483
+ if (family !== void 0 && typeof family.bind === "function") return family.bind({ namespace: REMOTE_WEB_UI_NS });
5484
+ return createServedEntryForm({
5485
+ forms: ctx.configForms,
5486
+ entryIds: REMOTE_WEB_UI_ENTRY_IDS
5487
+ });
5488
+ }
5811
5489
  /** Heartbeat cadence from a paired phone (presence + revocation liveness). */
5812
5490
  const HEARTBEAT_INTERVAL_MS = 1e4;
5813
5491
  /** Services required by this plugin. */
@@ -5858,8 +5536,7 @@ window.__ModuleLoader__.load({
5858
5536
  };
5859
5537
  const t = ctx.locale.bind(NS);
5860
5538
  if (adapt !== void 0) adapt.translate = t;
5861
- const family = ctx.get("webUiSettings");
5862
- const settingsForm = family !== void 0 && typeof family.bind === "function" ? family.bind({ namespace: REMOTE_WEB_UI_NS }) : ctx.configForms.get(servedEntryId(ctx.configForms));
5539
+ const settingsForm = bindSettingsForm(ctx);
5863
5540
  const enabled = () => {
5864
5541
  const snapshot = settingsForm.getSnapshot();
5865
5542
  return snapshot.status === "ready" ? snapshot.value?.enabled ?? true : snapshot.status === "unavailable";
@@ -6021,7 +5698,9 @@ window.__ModuleLoader__.load({
6021
5698
  }
6022
5699
  //#endregion
6023
5700
  exports.apply = apply;
5701
+ exports.bindSettingsForm = bindSettingsForm;
6024
5702
  exports.inject = inject;
5703
+ exports.servedEntryId = servedEntryId;
6025
5704
  return module.exports;
6026
5705
  }
6027
5706
  });