@linxin666/dsh-remote-web-ui 0.3.24 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +13 -10
  3. package/README.zh.md +13 -10
  4. package/icon.svg +3 -0
  5. package/lib/client.js +263 -129
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +565 -256
  8. package/lib/types/client/FenceNotice.d.ts.map +1 -1
  9. package/lib/types/client/FenceNotice.js +0 -3
  10. package/lib/types/client/PluginSettingsCard.d.ts +4 -0
  11. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  12. package/lib/types/client/PluginSettingsCard.js +3 -2
  13. package/lib/types/client/RemoteEntry.d.ts.map +1 -1
  14. package/lib/types/client/RemoteEntry.js +14 -9
  15. package/lib/types/client/RemotePanel.d.ts.map +1 -1
  16. package/lib/types/client/RemotePanel.js +13 -4
  17. package/lib/types/client/RemoteSettingsCard.d.ts +6 -6
  18. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  19. package/lib/types/client/RemoteSettingsCard.js +14 -8
  20. package/lib/types/client/UpdateEntry.js +2 -2
  21. package/lib/types/client/UpdatePanel.js +2 -2
  22. package/lib/types/client/deep-link.js +2 -2
  23. package/lib/types/client/index.d.ts +16 -8
  24. package/lib/types/client/index.d.ts.map +1 -1
  25. package/lib/types/client/index.js +47 -18
  26. package/lib/types/client/locales.d.ts +0 -2
  27. package/lib/types/client/locales.d.ts.map +1 -1
  28. package/lib/types/client/locales.js +6 -8
  29. package/lib/types/client/mobile-adapt.d.ts.map +1 -1
  30. package/lib/types/client/mobile-adapt.js +103 -22
  31. package/lib/types/client/pair-api.d.ts +32 -3
  32. package/lib/types/client/pair-api.d.ts.map +1 -1
  33. package/lib/types/client/pair-api.js +44 -6
  34. package/lib/types/client/plugin-card-seat.d.ts +23 -16
  35. package/lib/types/client/plugin-card-seat.d.ts.map +1 -1
  36. package/lib/types/client/plugin-card-seat.js +16 -14
  37. package/lib/types/client/remote-channel.d.ts.map +1 -1
  38. package/lib/types/client/remote-channel.js +18 -5
  39. package/lib/types/client/settings-form.d.ts +17 -17
  40. package/lib/types/client/settings-form.d.ts.map +1 -1
  41. package/lib/types/client/settings-form.js +30 -27
  42. package/lib/types/http.d.ts +18 -1
  43. package/lib/types/http.d.ts.map +1 -1
  44. package/lib/types/http.js +22 -1
  45. package/lib/types/index.d.ts +19 -11
  46. package/lib/types/index.d.ts.map +1 -1
  47. package/lib/types/index.js +91 -76
  48. package/lib/types/inner-auth.d.ts +3 -2
  49. package/lib/types/inner-auth.d.ts.map +1 -1
  50. package/lib/types/inner-auth.js +3 -2
  51. package/lib/types/pair-grant.d.ts +66 -0
  52. package/lib/types/pair-grant.d.ts.map +1 -0
  53. package/lib/types/pair-grant.js +95 -0
  54. package/lib/types/pairing.d.ts +44 -12
  55. package/lib/types/pairing.d.ts.map +1 -1
  56. package/lib/types/pairing.js +69 -20
  57. package/lib/types/pairing.persist.test.js +26 -0
  58. package/lib/types/posture.d.ts +11 -3
  59. package/lib/types/posture.d.ts.map +1 -1
  60. package/lib/types/posture.js +8 -3
  61. package/lib/types/remote-api.d.ts +10 -13
  62. package/lib/types/remote-api.d.ts.map +1 -1
  63. package/lib/types/remote-api.js +34 -34
  64. package/lib/types/remote-channel-boot.d.ts +6 -1
  65. package/lib/types/remote-channel-boot.d.ts.map +1 -1
  66. package/lib/types/remote-channel-boot.js +16 -8
  67. package/lib/types/remote-channel-rules.d.ts +14 -0
  68. package/lib/types/remote-channel-rules.d.ts.map +1 -1
  69. package/lib/types/remote-channel-rules.js +8 -0
  70. package/lib/types/remote-methods.d.ts +1 -1
  71. package/lib/types/remote-methods.d.ts.map +1 -1
  72. package/lib/types/remote-methods.js +1 -0
  73. package/lib/types/routes.d.ts +47 -5
  74. package/lib/types/routes.d.ts.map +1 -1
  75. package/lib/types/routes.js +160 -58
  76. package/lib/types/tunnel.d.ts +21 -6
  77. package/lib/types/tunnel.d.ts.map +1 -1
  78. package/lib/types/tunnel.js +58 -11
  79. package/lib/types/update.d.ts +4 -0
  80. package/lib/types/update.d.ts.map +1 -1
  81. package/lib/types/update.js +74 -26
  82. package/package.json +23 -20
  83. package/src/client/FenceNotice.tsx +0 -2
  84. package/src/client/PluginSettingsCard.tsx +7 -2
  85. package/src/client/RemoteEntry.tsx +14 -9
  86. package/src/client/RemotePanel.tsx +17 -7
  87. package/src/client/RemoteSettingsCard.tsx +16 -9
  88. package/src/client/UpdateEntry.tsx +2 -2
  89. package/src/client/UpdatePanel.tsx +3 -3
  90. package/src/client/deep-link.ts +2 -2
  91. package/src/client/index.ts +61 -27
  92. package/src/client/locales.ts +6 -8
  93. package/src/client/mobile-adapt.ts +97 -20
  94. package/src/client/pair-api.ts +47 -7
  95. package/src/client/plugin-card-seat.ts +24 -17
  96. package/src/client/remote-channel.ts +19 -4
  97. package/src/client/remote.module.css +38 -0
  98. package/src/client/settings-form.ts +34 -31
  99. package/src/http.ts +23 -1
  100. package/src/index.ts +102 -79
  101. package/src/inner-auth.ts +3 -2
  102. package/src/pair-grant.ts +131 -0
  103. package/src/pairing.persist.test.ts +36 -0
  104. package/src/pairing.ts +75 -24
  105. package/src/posture.ts +12 -4
  106. package/src/remote-api.ts +34 -41
  107. package/src/remote-channel-boot.ts +16 -8
  108. package/src/remote-channel-rules.ts +17 -0
  109. package/src/remote-methods.ts +1 -0
  110. package/src/routes.ts +165 -57
  111. package/src/tunnel.ts +56 -11
  112. package/src/update.ts +74 -24
@@ -30,9 +30,9 @@ export const zh = {
30
30
  'pair.linkLabel': '配对链接',
31
31
  'pair.tokenLabel': '配对令牌',
32
32
  'pair.dockerHint': 'Docker 或反向代理环境下,可直接复制此令牌并在目标设备配对页面输入。',
33
- 'pair.oneTimeHint': '链接含一次性令牌;任一设备配对成功后立即失效,配对下一台设备请刷新二维码。',
33
+ 'pair.oneTimeHint': '链接含限时令牌;在令牌过期或刷新二维码之前,同一链接可为多台设备完成配对(每次配对各自生成独立设备会话)。',
34
34
  'pair.failed.title': '配对失败',
35
- 'pair.failed.detail': '链接无效或已使用,请回到电脑端刷新二维码后重新扫码。',
35
+ 'pair.failed.detail': '链接无效或已过期,请回到电脑端刷新二维码后重新扫码。',
36
36
  'fence.unpaired.title': '此设备未配对,无法访问工作区数据',
37
37
  'fence.unpaired.eyebrow': '需要设备配对',
38
38
  'fence.unpaired.hint': '为保护工作区、会话与插件数据,远程电脑必须先通过主电脑授权。',
@@ -44,7 +44,6 @@ export const zh = {
44
44
  'fence.unpaired.pairAction': '立即配对',
45
45
  'fence.unpaired.pairing': '配对中…',
46
46
  'fence.unpaired.tokenInvalid': '配对链接或 Token 无效已过期',
47
- 'fence.unpaired.tokenUsed': '该配对链接已被使用',
48
47
  'fence.unpaired.tokenFailed': '配对失败,请检查网络或重新获取链接',
49
48
  'fence.unpaired.footnote': '请勿使用他人提供的配对链接;管理员可随时取消此设备的授权。',
50
49
  'posture.exposed': '/api 通道对未配对设备敞开',
@@ -71,7 +70,7 @@ export const zh = {
71
70
  'relay.registering': '正在把临时隧道同步到固定域名(<id>.dsh-market.com)…',
72
71
  'relay.failed': '固定域名同步失败:{error}。正在自动重试,期间二维码可能使用临时地址。',
73
72
  'close.label': '关闭远程访问面板',
74
- // 插件设置卡片(settings.plugin.item 席位)。
73
+ // 插件设置卡片(家族 list 席位,或官方 plugins.bundle.config 席位)。
75
74
  'settings.title': '远程访问设置',
76
75
  'settings.description': '配对安全与设备限额。',
77
76
  'settings.enabled': '启用远程访问',
@@ -197,9 +196,9 @@ export const en = {
197
196
  'pair.linkLabel': 'Pairing link',
198
197
  'pair.tokenLabel': 'Pairing token',
199
198
  'pair.dockerHint': 'In Docker or reverse proxy environments, copy this token to pair directly on the target device.',
200
- 'pair.oneTimeHint': 'The link carries one single-use token; it dies as soon as any device pairs. Refresh the QR to pair the next device.',
199
+ 'pair.oneTimeHint': 'The link carries a time-limited token: until it expires or the QR is refreshed, the same link can pair several devices, each with its own device session.',
201
200
  'pair.failed.title': 'Pairing failed',
202
- 'pair.failed.detail': 'The link is invalid or was already used. Refresh the QR code on your computer and scan again.',
201
+ 'pair.failed.detail': 'The link is invalid or has expired. Refresh the QR code on your computer and scan again.',
203
202
  'fence.unpaired.title': 'This device is not paired and cannot reach workspace data',
204
203
  'fence.unpaired.eyebrow': 'Device pairing required',
205
204
  'fence.unpaired.hint': 'To protect workspace, session, and plugin data, a remote computer must first be authorized by the primary computer.',
@@ -211,7 +210,6 @@ export const en = {
211
210
  'fence.unpaired.pairAction': 'Pair Now',
212
211
  'fence.unpaired.pairing': 'Pairing…',
213
212
  'fence.unpaired.tokenInvalid': 'Pairing link or token is invalid or expired',
214
- 'fence.unpaired.tokenUsed': 'This pairing link has already been used',
215
213
  'fence.unpaired.tokenFailed': 'Pairing failed, please check network or issue a new link',
216
214
  'fence.unpaired.footnote': 'Do not use pairing links from people you do not trust. An administrator can revoke this device at any time.',
217
215
  'posture.exposed': 'The /api channel is open to unpaired devices',
@@ -238,7 +236,7 @@ export const en = {
238
236
  'relay.registering': 'Syncing the ephemeral tunnel to the stable hostname (<id>.dsh-market.com)…',
239
237
  'relay.failed': 'Stable-hostname sync failed: {error}. Retrying automatically; the QR may use the ephemeral address meanwhile.',
240
238
  'close.label': 'Close remote access panel',
241
- // Plugin settings card (the `settings.plugin.item` seat).
239
+ // Plugin settings card (the family list seat, or the official bundle-config seat).
242
240
  'settings.title': 'Remote access settings',
243
241
  'settings.description': 'Pairing security and device limits.',
244
242
  'settings.enabled': 'Enable remote access',
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9C,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AAmRD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAw0BvC"}
1
+ {"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9C,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AAmRD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAq5BvC"}
@@ -340,7 +340,7 @@ export function startMobileAdapt() {
340
340
  * rail compaction) while the body class stays.
341
341
  */
342
342
  function ensureAdaptStyle() {
343
- if (document.querySelector(`style[data-plugin-css="${ADAPT_CSS_ID}"]`) !== null)
343
+ if (nodeOf(`style[data-plugin-css="${ADAPT_CSS_ID}"]`) !== null)
344
344
  return;
345
345
  const tag = document.createElement('style');
346
346
  tag.dataset.plugin = 'remote-web-ui';
@@ -396,6 +396,9 @@ export function startMobileAdapt() {
396
396
  }
397
397
  if (whaleEl !== null)
398
398
  whaleEl.style.display = 'none';
399
+ // The next apply() is a hidden-to-shown transition again: without this the
400
+ // whale would keep its pre-revert position state and skip the clamp.
401
+ whaleShown = false;
399
402
  setWhaleTimer(false);
400
403
  if (whaleObserver !== null) {
401
404
  whaleObserver.disconnect();
@@ -467,7 +470,7 @@ export function startMobileAdapt() {
467
470
  function syncCompactPicker() {
468
471
  if (!active)
469
472
  return;
470
- const tools = document.querySelector('[class$="_composerSeat"] [class$="_tools"]');
473
+ const tools = nodeOf('[class$="_composerSeat"] [class$="_tools"]');
471
474
  const trigger = tools?.parentElement?.querySelector('[class$="_triggerEffort"]')?.parentElement;
472
475
  if (tools === null || trigger === null) {
473
476
  removeCompactPicker();
@@ -646,22 +649,28 @@ export function startMobileAdapt() {
646
649
  whaleEl = whale;
647
650
  document.body.appendChild(whale);
648
651
  }
652
+ /**
653
+ * Write one row's recorded official draggable state back onto it.
654
+ * @param row - the tracked row element.
655
+ * @param original - the recorded attribute value, or null when it was absent.
656
+ */
657
+ function restoreRowDragState(row, original) {
658
+ if (original === null)
659
+ row.removeAttribute('draggable');
660
+ else
661
+ row.setAttribute('draggable', original);
662
+ }
649
663
  /**
650
664
  * Restore the official draggable state this layer overrode while active.
651
665
  * Rows are React-owned and may have been re-created meanwhile, so only the
652
- * tracked elements are touched; a detached element is skipped.
666
+ * tracked elements are touched; a detached element is written back too
667
+ * (harmless, and it keeps the entry prunable at every tick).
653
668
  */
654
669
  function restoreRowDrag() {
655
670
  if (dragOverridden.size === 0)
656
671
  return;
657
- for (const [row, original] of dragOverridden) {
658
- if (!row.isConnected)
659
- continue;
660
- if (original === null)
661
- row.removeAttribute('draggable');
662
- else
663
- row.setAttribute('draggable', original);
664
- }
672
+ for (const [row, original] of dragOverridden)
673
+ restoreRowDragState(row, original);
665
674
  dragOverridden.clear();
666
675
  }
667
676
  /** Restore a dragged position when the whale becomes visible again. */
@@ -700,6 +709,44 @@ export function startMobileAdapt() {
700
709
  row.setAttribute('draggable', 'false');
701
710
  }
702
711
  }
712
+ // React replaces rows as the session list re-renders; a detached row can
713
+ // never be restored by a later revert, so keeping it here would retain the
714
+ // whole detached subtree (and its children) for the page lifetime. Its
715
+ // official state goes back on the node itself and the entry is dropped:
716
+ // a node React re-attaches is recorded again on the next tick.
717
+ if (dragOverridden.size === 0)
718
+ return;
719
+ for (const [row, original] of dragOverridden) {
720
+ if (row.isConnected)
721
+ continue;
722
+ restoreRowDragState(row, original);
723
+ dragOverridden.delete(row);
724
+ }
725
+ }
726
+ /**
727
+ * Official nodes resolved through the document and cached while they stay
728
+ * connected. The sync tick runs every 600ms for the page lifetime, and these
729
+ * selectors (suffix class matches, :has()) cannot use Blink's fast paths, so
730
+ * every miss walks the whole mounted DOM — the official chat keeps the whole
731
+ * conversation mounted, i.e. tens of thousands of elements. React replaces a
732
+ * node on a major re-render, which the isConnected guard detects.
733
+ */
734
+ const nodeCache = new Map();
735
+ function nodeOf(selector) {
736
+ const cached = nodeCache.get(selector);
737
+ if (cached !== undefined && cached.isConnected)
738
+ return cached;
739
+ const found = document.querySelector(selector);
740
+ if (found === null)
741
+ nodeCache.delete(selector);
742
+ else
743
+ nodeCache.set(selector, found);
744
+ return found;
745
+ }
746
+ /** The official application frame, through the same cached lookup. */
747
+ function frameEl() {
748
+ const frame = nodeOf(APP_FRAME_SELECTOR);
749
+ return frame instanceof HTMLElement ? frame : null;
703
750
  }
704
751
  function syncWhale() {
705
752
  // Keep the stylesheet present while the layer is active (the tick runs
@@ -712,8 +759,8 @@ export function startMobileAdapt() {
712
759
  whaleEl.style.display = 'none';
713
760
  return;
714
761
  }
715
- const collapsed = appFrame()?.hasAttribute('data-sidebar-collapsed') === true;
716
- const overlayUp = document.querySelector('[class$="_overlay"]') !== null;
762
+ const collapsed = frameEl()?.hasAttribute('data-sidebar-collapsed') === true;
763
+ const overlayUp = nodeOf('[class$="_overlay"]') !== null;
717
764
  const show = collapsed && !overlayUp;
718
765
  // Restore on every hidden-to-shown transition — including the very
719
766
  // first show on a fresh page load, where the inline display is still
@@ -743,7 +790,7 @@ export function startMobileAdapt() {
743
790
  function seatHeaderActions() {
744
791
  if (!active)
745
792
  return;
746
- const header = document.querySelector('[class$="_header"]');
793
+ const header = nodeOf('[class$="_header"]');
747
794
  const tabs = header !== null ? header.querySelector('[class$="_tabs"]') : null;
748
795
  const actions = header !== null ? header.querySelector('[class$="_titleCluster"] [class$="_headerActions"]') : null;
749
796
  // The actions node is NOT moved: it is a React-owned host node whose badge
@@ -765,7 +812,7 @@ export function startMobileAdapt() {
765
812
  function alignActionsText() {
766
813
  if (!active)
767
814
  return;
768
- const header = document.querySelector('[class$="_header"]');
815
+ const header = nodeOf('[class$="_header"]');
769
816
  if (header === null)
770
817
  return;
771
818
  const tabs = header.querySelector('[class$="_tabs"]');
@@ -890,6 +937,12 @@ export function startMobileAdapt() {
890
937
  if (isMobilePortrait()) {
891
938
  apply();
892
939
  ensureWhaleObserver();
940
+ // A viewport shrink (split-screen, foldable, rotation) can leave the
941
+ // whale — the only portrait sidebar entry — parked beyond the new right
942
+ // edge while it stays visible; re-clamp it here. Skipped mid-drag so a
943
+ // resize cannot yank the button away from the finger.
944
+ if (active && drag === null)
945
+ applyWhalePos();
893
946
  }
894
947
  else {
895
948
  revert();
@@ -1001,14 +1054,18 @@ export function startMobileAdapt() {
1001
1054
  }
1002
1055
  const t = e.target;
1003
1056
  const editable = t instanceof HTMLElement && t.isContentEditable;
1004
- if (t instanceof Element && (t.tagName === 'TEXTAREA' || t.tagName === 'INPUT' || editable || t.closest(`#${WHALE_ID}`) !== null || insideHScrollable(t) || t.closest('table, [class$="_table"], [class$="_tablePane"]') !== null)) {
1057
+ if (t instanceof Element && (t.tagName === 'TEXTAREA' || t.tagName === 'INPUT' || editable || t.closest(`#${WHALE_ID}`) !== null || t.closest('table, [class$="_table"], [class$="_tablePane"]') !== null)) {
1005
1058
  swipeTouch = null;
1006
1059
  return;
1007
1060
  }
1008
1061
  const ct = e.changedTouches[0];
1009
1062
  if (ct === undefined)
1010
1063
  return;
1011
- swipeTouch = { x: ct.clientX, y: ct.clientY, id: ct.identifier };
1064
+ // The horizontally-scrollable veto is judged at release (see touchend):
1065
+ // evaluating it here forced a style recalc plus a synchronous layout on
1066
+ // every tap and scroll-start while the page was dirty, for a gesture that
1067
+ // mostly never becomes a swipe.
1068
+ swipeTouch = { x: ct.clientX, y: ct.clientY, id: ct.identifier, el: t instanceof Element ? t : null };
1012
1069
  }, { capture: true, passive: true });
1013
1070
  document.addEventListener('touchend', (e) => {
1014
1071
  if (swipeTouch === null)
@@ -1021,15 +1078,21 @@ export function startMobileAdapt() {
1021
1078
  return;
1022
1079
  const dx = ct.clientX - swipeTouch.x;
1023
1080
  const dy = ct.clientY - swipeTouch.y;
1081
+ const start = swipeTouch;
1024
1082
  swipeTouch = null;
1025
1083
  if (!active)
1026
1084
  return;
1027
1085
  if (Math.abs(dx) < 60 || Math.abs(dx) < Math.abs(dy) * 1.5)
1028
1086
  return;
1029
- const frame = appFrame();
1087
+ // A gesture that started inside a horizontally scrollable container belongs
1088
+ // to that container, not to the sidebar. Judged now, only for a gesture
1089
+ // that already qualified as a swipe.
1090
+ if (start.el !== null && insideHScrollable(start.el))
1091
+ return;
1092
+ const frame = frameEl();
1030
1093
  if (frame === null)
1031
1094
  return;
1032
- if (document.querySelector('[class$="_overlay"], [class$="_dialog"], [class$="_menu"], [class*="_portal"]') !== null)
1095
+ if (nodeOf('[class$="_overlay"], [class$="_dialog"], [class$="_menu"], [class*="_portal"]') !== null)
1033
1096
  return;
1034
1097
  const collapsed = frame.hasAttribute('data-sidebar-collapsed');
1035
1098
  if (dx < 0 && !collapsed)
@@ -1190,11 +1253,25 @@ export function startMobileAdapt() {
1190
1253
  return isComposerField(el) ? el : null;
1191
1254
  };
1192
1255
  const lanOrigFocus = HTMLElement.prototype.focus;
1193
- HTMLElement.prototype.focus = function (options) {
1256
+ const patchedFocus = function (options) {
1194
1257
  if (active && isComposerField(this) && Date.now() - lastComposerTap >= 800)
1195
1258
  return;
1196
1259
  lanOrigFocus.call(this, options);
1197
1260
  };
1261
+ /** (Re-)install the composer-focus guard (see setEnabled). */
1262
+ const installFocusPatch = () => {
1263
+ HTMLElement.prototype.focus = patchedFocus;
1264
+ };
1265
+ /**
1266
+ * Remove the guard when the layer is disabled. A patch another plugin
1267
+ * installed after ours is left alone (identity check), so disabling this
1268
+ * layer never removes someone else's behavior.
1269
+ */
1270
+ const restoreFocusPatch = () => {
1271
+ if (HTMLElement.prototype.focus === patchedFocus)
1272
+ HTMLElement.prototype.focus = lanOrigFocus;
1273
+ };
1274
+ installFocusPatch();
1198
1275
  document.addEventListener('pointerdown', (e) => {
1199
1276
  if (!active)
1200
1277
  return;
@@ -1211,10 +1288,14 @@ export function startMobileAdapt() {
1211
1288
  translate: null,
1212
1289
  setEnabled(on) {
1213
1290
  adaptEnabled = on;
1214
- if (on)
1291
+ if (on) {
1292
+ installFocusPatch();
1215
1293
  evaluate();
1216
- else
1294
+ }
1295
+ else {
1217
1296
  revert();
1297
+ restoreFocusPatch();
1298
+ }
1218
1299
  },
1219
1300
  flushCloseDetails() {
1220
1301
  // The first apply() ran before any wiring existed, so its
@@ -39,7 +39,7 @@ export declare function readPairGatePolicy(): Promise<PairGatePolicy>;
39
39
  /** accept() refusal codes. */
40
40
  export type AcceptFailure = {
41
41
  ok: false;
42
- code: 'invalid' | 'used' | 'forbidden';
42
+ code: 'invalid' | 'forbidden';
43
43
  };
44
44
  /** One auto-tunnel status frame (absent while the feature is off). */
45
45
  export interface TunnelStatusFrame {
@@ -116,8 +116,20 @@ export declare function stopPair(): Promise<void>;
116
116
  * @param deviceId - the session id of the row to drop.
117
117
  */
118
118
  export declare function revokePair(deviceId: string): Promise<void>;
119
- /** Presence heartbeat from a paired phone (unpaired heartbeats 401 harmlessly). */
120
- export declare function sendHeartbeat(): Promise<void>;
119
+ /**
120
+ * Presence heartbeat from a paired phone.
121
+ * @returns the response status, so the caller can stop polling once the server
122
+ * proves this page is not paired (see {@link shouldStopHeartbeat}).
123
+ */
124
+ export declare function sendHeartbeat(): Promise<number>;
125
+ /**
126
+ * Whether a heartbeat answer means "this page can never be accepted again" and
127
+ * the 10 s wake source should stop: 401 (unpaired, or the device was revoked)
128
+ * and 403 (the fence refused it) are permanent for this page, while a network
129
+ * error or a 5xx is transient and keeps the cadence.
130
+ * @param status - the heartbeat response status.
131
+ */
132
+ export declare function shouldStopHeartbeat(status: number): boolean;
121
133
  /** Whether the current page URL carries a pairing token. */
122
134
  export declare function readPairParams(search: string): {
123
135
  pair?: string;
@@ -148,8 +160,25 @@ export interface LanBindFrame {
148
160
  /** true when the running bind has not caught up with the toggle yet. */
149
161
  pendingRestart?: boolean;
150
162
  }
163
+ /** A failed LAN-bind read, carrying the HTTP status when the server answered. */
164
+ export declare class LanBindStatusError extends Error {
165
+ readonly status: number | undefined;
166
+ /**
167
+ * @param status - the response status, or undefined when the request failed
168
+ * before a response (network error).
169
+ * @param message - the diagnostic message.
170
+ */
171
+ constructor(status: number | undefined, message: string);
172
+ }
151
173
  /** Read the LAN-bind facts (loopback-only endpoint). */
152
174
  export declare function readLanBindStatus(): Promise<LanBindFrame>;
175
+ /**
176
+ * Whether a LAN-bind status failure means this origin can never read the
177
+ * endpoint (401/403 from the loopback-only fence), so a poll should stop
178
+ * instead of retrying a known refusal; a transient error keeps the cadence.
179
+ * @param status - the response status, or undefined for a network failure.
180
+ */
181
+ export declare function shouldStopLanBindPoll(status: number | undefined): boolean;
153
182
  /** Human-readable expiry clock, e.g. "10:35". */
154
183
  export declare function formatClock(epochMs: number): string;
155
184
  /** Calendar + clock for last-seen timestamps, e.g. "2026-08-19 10:35". */
@@ -1 +1 @@
1
- {"version":3,"file":"pair-api.d.ts","sourceRoot":"","sources":["../../../src/client/pair-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pair-api.d.ts","sourceRoot":"","sources":["../../../src/client/pair-api.ts"],"names":[],"mappings":"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"}
@@ -57,10 +57,10 @@ export async function acceptPair(token) {
57
57
  });
58
58
  if (response.ok)
59
59
  return { ok: true };
60
- if (response.status === 404)
60
+ // 409 is the legacy "already used" answer a pre-bearer-token host could send;
61
+ // it means the same thing to the caller as a 404.
62
+ if (response.status === 404 || response.status === 409)
61
63
  return { ok: false, code: 'invalid' };
62
- if (response.status === 409)
63
- return { ok: false, code: 'used' };
64
64
  return { ok: false, code: 'forbidden' };
65
65
  }
66
66
  /** Revoke mobile access (paired devices + the current token). */
@@ -84,9 +84,24 @@ export async function revokePair(deviceId) {
84
84
  if (!response.ok)
85
85
  throw new Error(`remote-web-ui: revoke failed with ${String(response.status)}`);
86
86
  }
87
- /** Presence heartbeat from a paired phone (unpaired heartbeats 401 harmlessly). */
87
+ /**
88
+ * Presence heartbeat from a paired phone.
89
+ * @returns the response status, so the caller can stop polling once the server
90
+ * proves this page is not paired (see {@link shouldStopHeartbeat}).
91
+ */
88
92
  export async function sendHeartbeat() {
89
- await fetch('/api/pair/heartbeat', { method: 'POST' });
93
+ const response = await fetch('/api/pair/heartbeat', { method: 'POST' });
94
+ return response.status;
95
+ }
96
+ /**
97
+ * Whether a heartbeat answer means "this page can never be accepted again" and
98
+ * the 10 s wake source should stop: 401 (unpaired, or the device was revoked)
99
+ * and 403 (the fence refused it) are permanent for this page, while a network
100
+ * error or a 5xx is transient and keeps the cadence.
101
+ * @param status - the heartbeat response status.
102
+ */
103
+ export function shouldStopHeartbeat(status) {
104
+ return status === 401 || status === 403;
90
105
  }
91
106
  /** Whether the current page URL carries a pairing token. */
92
107
  export function readPairParams(search) {
@@ -104,13 +119,36 @@ export function stripParam(name) {
104
119
  url.searchParams.delete(name);
105
120
  return url.search;
106
121
  }
122
+ /** A failed LAN-bind read, carrying the HTTP status when the server answered. */
123
+ export class LanBindStatusError extends Error {
124
+ status;
125
+ /**
126
+ * @param status - the response status, or undefined when the request failed
127
+ * before a response (network error).
128
+ * @param message - the diagnostic message.
129
+ */
130
+ constructor(status, message) {
131
+ super(message);
132
+ this.status = status;
133
+ this.name = 'LanBindStatusError';
134
+ }
135
+ }
107
136
  /** Read the LAN-bind facts (loopback-only endpoint). */
108
137
  export async function readLanBindStatus() {
109
138
  const response = await fetch('/api/pair/lan-bind');
110
139
  if (!response.ok)
111
- throw new Error(`remote-web-ui: lan-bind status failed with ${String(response.status)}`);
140
+ throw new LanBindStatusError(response.status, `remote-web-ui: lan-bind status failed with ${String(response.status)}`);
112
141
  return await response.json();
113
142
  }
143
+ /**
144
+ * Whether a LAN-bind status failure means this origin can never read the
145
+ * endpoint (401/403 from the loopback-only fence), so a poll should stop
146
+ * instead of retrying a known refusal; a transient error keeps the cadence.
147
+ * @param status - the response status, or undefined for a network failure.
148
+ */
149
+ export function shouldStopLanBindPoll(status) {
150
+ return status === 401 || status === 403;
151
+ }
114
152
  /** Human-readable expiry clock, e.g. "10:35". */
115
153
  export function formatClock(epochMs) {
116
154
  const date = new Date(epochMs);
@@ -6,22 +6,24 @@
6
6
  *
7
7
  * - `web-ui.plugin.item` — the list seat declared by the dsh-web-settings
8
8
  * group section (this family's own first-level "Web UI plugins" section);
9
- * - `settings.plugin.item` — the official keyed seat of the harness's
10
- * `ui-settings-plugins` tab, keyed by the settings namespace the card edits.
9
+ * - `plugins.bundle.config` — the official keyed seat of the harness's plugin
10
+ * manager page, keyed by the bundle's package name and rendered on that
11
+ * bundle's page. alpha.2 removed the `settings.plugin.item` keyed seat of the
12
+ * `ui-settings-plugins` tab that this helper used before, so a card keyed by
13
+ * its settings namespace has no seat to land in any more.
11
14
  *
12
- * SEAT SELECTION IS NOT A DECLARATION PROBE. The official `ui-settings-plugins`
13
- * row belongs to the harness bundle and its `configurable` tab always declares
14
- * `settings.plugin.item` before any external plugin's `apply()` runs, so
15
- * "is the official seat declared?" answers yes even in the deployment whose
16
- * whole point is the family group. Choosing on that probe sends every family
17
- * card to the official Plugins tab and leaves the group's own section
18
- * permanently empty — the family of reports where the section renders its
19
- * heading and zero cards.
15
+ * SEAT SELECTION IS NOT A DECLARATION PROBE. The official plugin surface
16
+ * belongs to the harness bundle and declares its seats before any external
17
+ * plugin's `apply()` runs, so "is the official seat declared?" answers yes even
18
+ * in the deployment whose whole point is the family group. Choosing on that
19
+ * probe sends every family card to the official page and leaves the group's own
20
+ * section permanently empty — the family of reports where the section renders
21
+ * its heading and zero cards.
20
22
  *
21
23
  * The signal that actually distinguishes the two deployments is whether
22
24
  * dsh-web-settings is loaded: it is the package that owns the group section and
23
25
  * it publishes the `webUiSettings` service during `apply()`, which every
24
- * family plugin already reads for its settings scope. Group loaded -> the family
26
+ * family plugin already reads for its settings form. Group loaded -> the family
25
27
  * seat; group absent -> the official seat.
26
28
  *
27
29
  * The decision is re-evaluated on every `slots/changed` because the group may
@@ -37,8 +39,8 @@
37
39
  */
38
40
  /** The family list seat key. */
39
41
  export declare const FAMILY_PLUGIN_CARD_SEAT = "web-ui.plugin.item";
40
- /** The official keyed plugin-card seat key. */
41
- export declare const OFFICIAL_PLUGIN_CARD_SEAT = "settings.plugin.item";
42
+ /** The official keyed plugin-card seat key (the alpha.2 bundle-configuration seat). */
43
+ export declare const OFFICIAL_PLUGIN_CARD_SEAT = "plugins.bundle.config";
42
44
  /** The service dsh-web-settings publishes while it is loaded. */
43
45
  export declare const FAMILY_GROUP_SERVICE = "webUiSettings";
44
46
  /** The slot-registry member this helper uses (structurally satisfied by ctx.slots). */
@@ -54,15 +56,20 @@ export interface PluginCardContext {
54
56
  /** Event subscription seat; absent on an event-less test double. */
55
57
  on?(event: string, listener: (...args: never[]) => void): unknown;
56
58
  }
57
- /** Owner share of a plugin card (both seats supply nothing). */
59
+ /** Owner share of a family list-seat card (that seat supplies nothing). */
58
60
  export interface SettingsPluginItemOwnerProps {
59
61
  /** Marker field: card owner props are intentionally empty. */
60
62
  children?: never;
61
63
  }
62
64
  /** One family plugin's card contribution. */
63
65
  export interface PluginCardSeat {
64
- /** Settings namespace the card edits (the official seat's dispatch key). */
65
- namespace: string;
66
+ /**
67
+ * Npm package name of the bundle this card configures; the official keyed
68
+ * seat dispatches on it. Every family package ships its own bundle patch, so
69
+ * a standalone install lists that package as the bundle whose page renders
70
+ * this card.
71
+ */
72
+ bundle: string;
66
73
  /** Family list-seat entry id. */
67
74
  id: string;
68
75
  /** Family list-seat sort order. */
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-card-seat.d.ts","sourceRoot":"","sources":["../../../src/client/plugin-card-seat.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,gCAAgC;AAChC,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAE3D,+CAA+C;AAC/C,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAE/D,iEAAiE;AACjE,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AAEnD,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,GAAG,OAAO,CAAA;CACpD;AAED,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,CAAA;IACtB,6EAA6E;IAC7E,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,oEAAoE;IACpE,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,GAAG,OAAO,CAAA;CAClE;AAED,gEAAgE;AAChE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;IACpB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;IACrB;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAUjE;AAWD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CA4DpF"}
1
+ {"version":3,"file":"plugin-card-seat.d.ts","sourceRoot":"","sources":["../../../src/client/plugin-card-seat.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,gCAAgC;AAChC,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAE3D,uFAAuF;AACvF,eAAO,MAAM,yBAAyB,0BAA0B,CAAA;AAEhE,iEAAiE;AACjE,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AAEnD,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,GAAG,OAAO,CAAA;CACpD;AAED,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,CAAA;IACtB,6EAA6E;IAC7E,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,oEAAoE;IACpE,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,GAAG,OAAO,CAAA;CAClE;AAED,2EAA2E;AAC3E,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;IACpB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;IACrB;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAUjE;AAWD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CA4DpF"}
@@ -7,22 +7,24 @@
7
7
  *
8
8
  * - `web-ui.plugin.item` — the list seat declared by the dsh-web-settings
9
9
  * group section (this family's own first-level "Web UI plugins" section);
10
- * - `settings.plugin.item` — the official keyed seat of the harness's
11
- * `ui-settings-plugins` tab, keyed by the settings namespace the card edits.
10
+ * - `plugins.bundle.config` — the official keyed seat of the harness's plugin
11
+ * manager page, keyed by the bundle's package name and rendered on that
12
+ * bundle's page. alpha.2 removed the `settings.plugin.item` keyed seat of the
13
+ * `ui-settings-plugins` tab that this helper used before, so a card keyed by
14
+ * its settings namespace has no seat to land in any more.
12
15
  *
13
- * SEAT SELECTION IS NOT A DECLARATION PROBE. The official `ui-settings-plugins`
14
- * row belongs to the harness bundle and its `configurable` tab always declares
15
- * `settings.plugin.item` before any external plugin's `apply()` runs, so
16
- * "is the official seat declared?" answers yes even in the deployment whose
17
- * whole point is the family group. Choosing on that probe sends every family
18
- * card to the official Plugins tab and leaves the group's own section
19
- * permanently empty — the family of reports where the section renders its
20
- * heading and zero cards.
16
+ * SEAT SELECTION IS NOT A DECLARATION PROBE. The official plugin surface
17
+ * belongs to the harness bundle and declares its seats before any external
18
+ * plugin's `apply()` runs, so "is the official seat declared?" answers yes even
19
+ * in the deployment whose whole point is the family group. Choosing on that
20
+ * probe sends every family card to the official page and leaves the group's own
21
+ * section permanently empty — the family of reports where the section renders
22
+ * its heading and zero cards.
21
23
  *
22
24
  * The signal that actually distinguishes the two deployments is whether
23
25
  * dsh-web-settings is loaded: it is the package that owns the group section and
24
26
  * it publishes the `webUiSettings` service during `apply()`, which every
25
- * family plugin already reads for its settings scope. Group loaded -> the family
27
+ * family plugin already reads for its settings form. Group loaded -> the family
26
28
  * seat; group absent -> the official seat.
27
29
  *
28
30
  * The decision is re-evaluated on every `slots/changed` because the group may
@@ -38,8 +40,8 @@
38
40
  */
39
41
  /** The family list seat key. */
40
42
  export const FAMILY_PLUGIN_CARD_SEAT = 'web-ui.plugin.item';
41
- /** The official keyed plugin-card seat key. */
42
- export const OFFICIAL_PLUGIN_CARD_SEAT = 'settings.plugin.item';
43
+ /** The official keyed plugin-card seat key (the alpha.2 bundle-configuration seat). */
44
+ export const OFFICIAL_PLUGIN_CARD_SEAT = 'plugins.bundle.config';
43
45
  /** The service dsh-web-settings publishes while it is loaded. */
44
46
  export const FAMILY_GROUP_SERVICE = 'webUiSettings';
45
47
  /**
@@ -113,7 +115,7 @@ export function installPluginCard(ctx, seat) {
113
115
  }
114
116
  : {
115
117
  name: OFFICIAL_PLUGIN_CARD_SEAT,
116
- key: seat.namespace,
118
+ key: seat.bundle,
117
119
  locale: seat.locale,
118
120
  ...(seat.inject === undefined ? {} : { inject }),
119
121
  }), component);
@@ -1 +1 @@
1
- {"version":3,"file":"remote-channel.d.ts","sourceRoot":"","sources":["../../../src/client/remote-channel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"remote-channel.d.ts","sourceRoot":"","sources":["../../../src/client/remote-channel.ts"],"names":[],"mappings":"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"}