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

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 (47) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +22 -7
  3. package/README.zh.md +21 -6
  4. package/lib/client.js +75 -138
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +162 -22
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -0
  8. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  9. package/lib/types/client/PluginSettingsCard.js +2 -2
  10. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  11. package/lib/types/client/RemoteSettingsCard.js +2 -1
  12. package/lib/types/client/index.js +3 -3
  13. package/lib/types/client/locales.d.ts +0 -4
  14. package/lib/types/client/locales.d.ts.map +1 -1
  15. package/lib/types/client/locales.js +5 -9
  16. package/lib/types/client/mobile-adapt.d.ts +2 -2
  17. package/lib/types/client/mobile-adapt.d.ts.map +1 -1
  18. package/lib/types/client/mobile-adapt.js +51 -131
  19. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  20. package/lib/types/client/plugin-card-seat.d.ts.map +1 -1
  21. package/lib/types/client/plugin-card-seat.js +2 -0
  22. package/lib/types/client/settings-form.d.ts +16 -0
  23. package/lib/types/client/settings-form.d.ts.map +1 -1
  24. package/lib/types/client/settings-form.js +27 -1
  25. package/lib/types/detached-work.d.ts +45 -0
  26. package/lib/types/detached-work.d.ts.map +1 -0
  27. package/lib/types/detached-work.js +55 -0
  28. package/lib/types/index.d.ts.map +1 -1
  29. package/lib/types/index.js +31 -10
  30. package/lib/types/lan-bind.d.ts +21 -4
  31. package/lib/types/lan-bind.d.ts.map +1 -1
  32. package/lib/types/lan-bind.js +103 -17
  33. package/lib/types/remote-channel-boot.d.ts.map +1 -1
  34. package/lib/types/remote-channel-boot.js +4 -1
  35. package/package.json +19 -19
  36. package/src/client/PluginSettingsCard.tsx +9 -0
  37. package/src/client/RemoteSettingsCard.tsx +2 -0
  38. package/src/client/index.ts +3 -3
  39. package/src/client/locales.ts +5 -9
  40. package/src/client/mobile-adapt.ts +53 -136
  41. package/src/client/plugin-card-seat.ts +7 -0
  42. package/src/client/settings-card.module.css +31 -5
  43. package/src/client/settings-form.ts +27 -1
  44. package/src/detached-work.ts +58 -0
  45. package/src/index.ts +31 -10
  46. package/src/lan-bind.ts +111 -17
  47. package/src/remote-channel-boot.ts +4 -1
package/lib/index.js CHANGED
@@ -13,6 +13,7 @@ import { spawn, spawnSync } from "node:child_process";
13
13
  import { parseDocument } from "yaml";
14
14
  import { EventEmitter } from "node:events";
15
15
  import { Tunnel, bin, install } from "cloudflared";
16
+ import { AsyncResource } from "node:async_hooks";
16
17
  //#region src/pairing.ts
17
18
  /**
18
19
  * Pairing state machine: one active one-time token, a device-session table,
@@ -2746,16 +2747,100 @@ function readPatchContent(file) {
2746
2747
  if (!existsSync(file)) return "";
2747
2748
  return readFileSync(file, "utf8");
2748
2749
  }
2749
- /** Escape one literal string for embedding into a RegExp pattern. */
2750
- function escapeRegex(text) {
2751
- return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2750
+ /**
2751
+ * Split a block-sequence region into its top-level items. Only a column-0 `-`
2752
+ * starts an item: that is the shape every writer of this file produces - the
2753
+ * managed block itself and the YAML document API's block-style re-emission.
2754
+ * @param region - text between two line boundaries.
2755
+ * @returns the items in source order; text outside them is not returned.
2756
+ */
2757
+ function blockItems(region) {
2758
+ const items = [];
2759
+ let start = -1;
2760
+ let offset = 0;
2761
+ for (const line of region.split("\n")) {
2762
+ if (line.startsWith("- ") || line === "-") {
2763
+ if (start !== -1) items.push({
2764
+ start,
2765
+ end: offset,
2766
+ text: region.slice(start, offset)
2767
+ });
2768
+ start = offset;
2769
+ }
2770
+ offset += line.length + 1;
2771
+ }
2772
+ if (start !== -1) items.push({
2773
+ start,
2774
+ end: region.length,
2775
+ text: region.slice(start)
2776
+ });
2777
+ return items;
2778
+ }
2779
+ /**
2780
+ * Whether one top-level item is the webserver row this plugin's block owns.
2781
+ * The row id is the whole signal: the markers declare everything inside them
2782
+ * managed, our block always writes `- id: webserver`, and a same-id row later
2783
+ * in the patch replaces this one wholesale anyway - so an id-webserver row
2784
+ * found inside our markers is this plugin's row (a hand-truncated block may
2785
+ * have lost its `name` line).
2786
+ * @param item - one top-level item's text.
2787
+ * @returns true when the item is the managed webserver row.
2788
+ */
2789
+ function isManagedWebserverItem(item) {
2790
+ return /^- id:\s*['"]?webserver['"]?[ \t]*(?:\r?\n|$)/.test(item);
2752
2791
  }
2753
- /** Remove the managed block (and its markers) from patch content. */
2792
+ /**
2793
+ * The text between a block's BEGIN and END markers: from the character after
2794
+ * the BEGIN line's terminator to the start of the END marker's line (or to
2795
+ * the end of the content without an END marker).
2796
+ * @param content - the patch file text.
2797
+ * @param begin - index of the BEGIN marker.
2798
+ * @param end - index of the END marker, or -1 when the block is unterminated.
2799
+ * @returns the region's text.
2800
+ */
2801
+ function managedRegion(content, begin, end) {
2802
+ const newlineAfterBegin = content.indexOf("\n", begin);
2803
+ const innerStart = newlineAfterBegin === -1 ? content.length : newlineAfterBegin + 1;
2804
+ const endLineStart = end === -1 ? content.length : content.lastIndexOf("\n", end - 1) + 1;
2805
+ return content.slice(innerStart, Math.max(innerStart, endLineStart));
2806
+ }
2807
+ /**
2808
+ * Remove the managed block (its markers and the webserver row this plugin
2809
+ * wrote) from patch content, keeping every other row.
2810
+ *
2811
+ * The markers travel with the file through writers that append rows through
2812
+ * the YAML document API: the appended rows land at the end of the sequence,
2813
+ * which is BEFORE a trailing END comment, so the region between the markers
2814
+ * can hold rows this plugin never wrote. Deleting the whole region silently
2815
+ * removed the user's model, plugin and remote-control rows on the next rewrite
2816
+ * - the remote-control row carries the LAN toggle itself, so the feature reset
2817
+ * to `undefined` and the phone was locked out again after a restart (DSH
2818
+ * Desktop report, 2026-09-30). Only the managed item is removed here; every
2819
+ * foreign row is kept in place and the fresh block is appended after them (the
2820
+ * same-id row last wins).
2821
+ * @param content - the patch file text.
2822
+ * @returns the text with the markers and the managed row removed.
2823
+ */
2754
2824
  function stripManagedBlock(content) {
2755
- const begin = escapeRegex(LAN_BIND_BLOCK_BEGIN);
2756
- const end = escapeRegex(LAN_BIND_BLOCK_END);
2757
- const pattern = new RegExp(`\\r?\\n?${begin}[\\s\\S]*?${end}\\r?\\n?`, "g");
2758
- return content.replace(pattern, "\n");
2825
+ let text = content;
2826
+ for (;;) {
2827
+ const begin = text.indexOf(LAN_BIND_BLOCK_BEGIN);
2828
+ if (begin === -1) return text;
2829
+ const end = text.indexOf(LAN_BIND_BLOCK_END, begin);
2830
+ const blockStart = text.lastIndexOf("\n", begin - 1) + 1;
2831
+ const newlineAfterEnd = end === -1 ? -1 : text.indexOf("\n", end);
2832
+ const resumeAt = end === -1 ? text.length : newlineAfterEnd === -1 ? text.length : newlineAfterEnd + 1;
2833
+ const region = managedRegion(text, begin, end);
2834
+ let kept = "";
2835
+ let cursor = 0;
2836
+ for (const item of blockItems(region)) {
2837
+ if (!isManagedWebserverItem(item.text)) continue;
2838
+ kept += region.slice(cursor, item.start);
2839
+ cursor = item.end;
2840
+ }
2841
+ kept += region.slice(cursor);
2842
+ text = text.slice(0, blockStart) + kept + text.slice(resumeAt);
2843
+ }
2759
2844
  }
2760
2845
  /**
2761
2846
  * Render the managed block for one bind state. The values are static: the
@@ -2827,10 +2912,11 @@ function managedBlock(host, port) {
2827
2912
  function managedBindOf(content) {
2828
2913
  const begin = content.indexOf(LAN_BIND_BLOCK_BEGIN);
2829
2914
  if (begin === -1) return void 0;
2830
- const end = content.indexOf(LAN_BIND_BLOCK_END, begin);
2831
- const block = end === -1 ? content.slice(begin) : content.slice(begin, end);
2832
- const hostMatch = /host:\s*'([^']+)'/.exec(block);
2833
- const portMatch = /port:\s*(\d+)/.exec(block);
2915
+ const region = managedRegion(content, begin, content.indexOf(LAN_BIND_BLOCK_END, begin));
2916
+ const items = blockItems(region).filter((item) => isManagedWebserverItem(item.text));
2917
+ const scope = items.length > 0 ? items.map((item) => item.text).join("") : region;
2918
+ const hostMatch = /host:\s*'([^']+)'/.exec(scope);
2919
+ const portMatch = /port:\s*(\d+)/.exec(scope);
2834
2920
  return {
2835
2921
  host: hostMatch?.[1] ?? "",
2836
2922
  port: portMatch !== null ? Number(portMatch[1]) : void 0
@@ -2851,9 +2937,10 @@ function lanBindState(profile, home = dshHome()) {
2851
2937
  * patch file is preserved; the block is rewritten atomically (unique temp
2852
2938
  * file + rename, so concurrent writers can never rename a half-written
2853
2939
  * file) with the original file's permissions preserved. A hand-truncated
2854
- * unterminated block (BEGIN marker without END, which stripManagedBlock
2855
- * cannot match) is truncated at its BEGIN marker first so the rewrite can
2856
- * never stack a second webserver row onto the orphan.
2940
+ * unterminated block (BEGIN marker without END) is consumed by
2941
+ * stripManagedBlock as well - markers and managed row removed, foreign rows
2942
+ * kept - so the rewrite can never stack a second webserver row onto the
2943
+ * orphan.
2857
2944
  *
2858
2945
  * The written file is always exactly one valid YAML document: a base the
2859
2946
  * plugin manager left in flow style is re-emitted as a block sequence before
@@ -2861,9 +2948,7 @@ function lanBindState(profile, home = dshHome()) {
2861
2948
  */
2862
2949
  function writeLanBind(host, port, profile, home = dshHome()) {
2863
2950
  const file = profilePatchFile(profile, home);
2864
- const stripped = stripManagedBlock(readPatchContent(file));
2865
- const orphanBegin = stripped.indexOf(LAN_BIND_BLOCK_BEGIN);
2866
- const content = mergePatchContent((orphanBegin === -1 ? stripped : stripped.slice(0, orphanBegin)).trimEnd().replace(/\[\s*\]\s*$/, "").trimEnd(), managedBlock(host, port), file);
2951
+ const content = mergePatchContent(stripManagedBlock(readPatchContent(file)).trimEnd().replace(/\[\s*\]\s*$/, "").trimEnd(), managedBlock(host, port), file);
2867
2952
  const mode = existsSync(file) ? statSync(file).mode & 511 : 384;
2868
2953
  mkdirSync(dirname(file), { recursive: true });
2869
2954
  const temp = `${file}.remote-web-ui-tmp-${process.pid.toString(36)}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -3911,6 +3996,61 @@ function mountOnce(packageName, fn) {
3911
3996
  return mount;
3912
3997
  }
3913
3998
  //#endregion
3999
+ //#region src/detached-work.ts
4000
+ /**
4001
+ * Run one piece of work outside the caller's async context.
4002
+ *
4003
+ * A Host mutation that runs inside `hmr.runExclusive` marks the running async
4004
+ * context with an AsyncLocalStorage store. Side effects on that path that write
4005
+ * a file the HMR config watcher refreshes from - `cordis.patch.yml` above all -
4006
+ * must not run under that mark, or the watcher's refresh re-enters the
4007
+ * exclusive transaction and is refused with "HMR transactions cannot be nested"
4008
+ * (#1751, #1754, #1816).
4009
+ *
4010
+ * Deferring is NOT enough, and the earlier belt that this one replaces was
4011
+ * built on a disproven premise: Node propagates an AsyncLocalStorage store into
4012
+ * `setImmediate`, into `node:timers`, into promise continuations, and into any
4013
+ * AsyncResource scoped to the current async id. Measured on Node 24:
4014
+ *
4015
+ * ```
4016
+ * als.run(true, () => setImmediate(() => als.getStore())) // -> true
4017
+ * ```
4018
+ *
4019
+ * What DOES start clean is an AsyncResource whose `triggerAsyncId` is not the
4020
+ * transaction. A resource created once at module scope - that is, before any
4021
+ * transaction existed - carries no store, and everything it schedules inherits
4022
+ * that emptiness rather than the caller's mark:
4023
+ *
4024
+ * ```
4025
+ * const detached = new AsyncResource('...') // module scope
4026
+ * als.run(true, () => detached.runInAsyncScope(() => setImmediate(() => als.getStore())))
4027
+ * // -> undefined
4028
+ * ```
4029
+ *
4030
+ * @module dsh-web-shared/host/detached-work
4031
+ */
4032
+ /**
4033
+ * The escape hatch, created at module scope on purpose: a resource built while
4034
+ * a transaction is running would capture that transaction, so this single
4035
+ * instance is the only one the module ever needs. It carries no context of its
4036
+ * own, so every job run through it starts from the process's root context.
4037
+ */
4038
+ const DETACHED = new AsyncResource("dsh-web:detached-work", { triggerAsyncId: 0 });
4039
+ /**
4040
+ * Run `work` outside the caller's AsyncLocalStorage context.
4041
+ *
4042
+ * The call itself is synchronous - this schedules, it does not defer. Work that
4043
+ * must not block the caller should schedule its own `setImmediate` inside
4044
+ * `work`, which then also starts clean because it inherits this resource's
4045
+ * empty context rather than the caller's.
4046
+ *
4047
+ * @param work - the side effect to detach from the caller's async context.
4048
+ * @returns whatever `work` returns.
4049
+ */
4050
+ function runDetached(work) {
4051
+ return DETACHED.runInAsyncScope(work);
4052
+ }
4053
+ //#endregion
3914
4054
  //#region src/remote-channel-boot.ts
3915
4055
  /**
3916
4056
  * Parse-time remote-channel boot patch (issue #987): the browser-half patch
@@ -3955,7 +4095,7 @@ function buildRemoteChannelBootScript(rules = REMOTE_CHANNEL_RULES) {
3955
4095
  }
3956
4096
  /** Boot-watchdog latch: one self-reload per session while the boot is broken. */
3957
4097
  const BOOT_WATCHDOG_KEY = "dsh-remote-boot-reload";
3958
- /** Total wait for the app's first conversation surface before the reload. */
4098
+ /** Post-load wait for the app's first conversation surface before the reload. */
3959
4099
  const BOOT_WATCHDOG_WAIT_MS = 15e3;
3960
4100
  /** Watchdog poll cadence. */
3961
4101
  const BOOT_WATCHDOG_POLL_MS = 1e3;
@@ -3970,7 +4110,7 @@ const BOOT_WATCHDOG_POLL_MS = 1e3;
3970
4110
  */
3971
4111
  function buildBootWatchdogScript(waitMs = BOOT_WATCHDOG_WAIT_MS, pollMs = BOOT_WATCHDOG_POLL_MS) {
3972
4112
  const key = JSON.stringify(BOOT_WATCHDOG_KEY);
3973
- return ";function wBoot(){try{return !!(w.document&&w.document.querySelector&&(w.document.querySelector(\"[data-conversation-scroll]\")||w.document.querySelector(\"[data-slot=\\\"conversation\\\"]\")))}catch(e){return false}}function wTick(n){try{if(wBoot()){try{w.sessionStorage.removeItem(" + key + ")}catch(e){}return}if(n<" + waitMs + "){w.setTimeout(function(){wTick(n+" + pollMs + ")}," + pollMs + ");return}var done=false;try{done=w.sessionStorage.getItem(" + key + ")===\"1\"}catch(e){}if(done)return;try{w.sessionStorage.setItem(" + key + ",\"1\")}catch(e){}w.location.reload()}catch(e){}}if(typeof w.setTimeout===\"function\")w.setTimeout(function(){wTick(0)}," + pollMs + ");";
4113
+ return ";function wBoot(){try{return !!(w.document&&w.document.querySelector&&(w.document.querySelector(\"[data-conversation-scroll]\")||w.document.querySelector(\"[data-slot=\\\"conversation\\\"]\")))}catch(e){return false}}function wTick(n){try{if(wBoot()){try{w.sessionStorage.removeItem(" + key + ")}catch(e){}return}if(!w.document||w.document.readyState!==\"complete\"){w.setTimeout(function(){wTick(0)}," + pollMs + ");return}if(n<" + waitMs + "){w.setTimeout(function(){wTick(n+" + pollMs + ")}," + pollMs + ");return}var done=false;try{done=w.sessionStorage.getItem(" + key + ")===\"1\"}catch(e){}if(done)return;try{w.sessionStorage.setItem(" + key + ",\"1\")}catch(e){}w.location.reload()}catch(e){}}if(typeof w.setTimeout===\"function\")w.setTimeout(function(){wTick(0)}," + pollMs + ");";
3974
4114
  }
3975
4115
  /** The script this plugin contributes; built once from the live rules. */
3976
4116
  const REMOTE_CHANNEL_BOOT_SCRIPT = buildRemoteChannelBootScript();
@@ -4421,14 +4561,14 @@ function applyImpl(ctx, config) {
4421
4561
  const scheduleLanBindWork = () => {
4422
4562
  if (lanBindWorkPending) return;
4423
4563
  lanBindWorkPending = true;
4424
- setImmediate(() => {
4564
+ runDetached(() => setImmediate(() => {
4425
4565
  lanBindWorkPending = false;
4426
4566
  try {
4427
4567
  applyLanBindWork(resolve());
4428
4568
  } catch (error) {
4429
4569
  console.error(`remote-web-ui: the deferred lan-bind assertion failed: ${error instanceof Error ? error.message : String(error)}`);
4430
4570
  }
4431
- });
4571
+ }));
4432
4572
  };
4433
4573
  ctx.effect(() => () => {
4434
4574
  lanBindWorkPending = false;
@@ -24,6 +24,13 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
24
24
  descriptionKey: TKey;
25
25
  /** Optional rich header description rendered instead of the plain t(descriptionKey) text (the plain text stays as the tooltip). */
26
26
  descriptionNode?: ReactNode;
27
+ /**
28
+ * Optional leading mark identifying the plugin. A card without one keeps the
29
+ * plain official header shape, so the chrome stays a readable sibling of the
30
+ * built-in PluginCard wherever a family plugin lands in the official bundle
31
+ * seat.
32
+ */
33
+ icon?: ReactNode;
27
34
  /** The card's form state: availability, writability, and what a save would do. */
28
35
  state: CardShell;
29
36
  /** Write every staged edit. */
@@ -1 +1 @@
1
- {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,EAAiF,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACrH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAGnD,oGAAoG;AACpG,eAAO,MAAM,cAAc,kMAUjB,CAAA;AAEV,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD,oEAAoE;AACpE,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,GAAG,WAAW,CAAA;AAE9E,wDAAwD;AACxD,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACnE,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;IACnF,uCAAuC;IACvC,QAAQ,EAAE,IAAI,CAAA;IACd,4EAA4E;IAC5E,cAAc,EAAE,IAAI,CAAA;IACpB,mIAAmI;IACnI,eAAe,CAAC,EAAE,SAAS,CAAA;IAC3B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,mFAAmF;IACnF,4BAA4B,CAAC,EAAE,OAAO,CAAA;IACtC,wFAAwF;IACxF,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,uBAAuB,CAAC,IAAI,CAAC,sCAgHpG;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAA;IACnB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IACjB,wBAAwB;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,oEAAoE;IACpE,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG;IAC7C,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,+BAqCA;AAUD,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAID;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAA;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B,+BA2KA;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG;IAC/C,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,+BAoCA;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG;IAC9C,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,OAAO,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACzD,+BAkCA"}
@@ -40,8 +40,8 @@ export function PluginSettingsCard(props) {
40
40
  // With alwaysOpen the nav entry already provides the selection, so the
41
41
  // header is a static title row instead of a disclosure button.
42
42
  const header = alwaysOpen === true
43
- ? (_jsxs("div", { className: css.headerStatic, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: props.descriptionNode ?? description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null] }))
44
- : (_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: props.descriptionNode ?? description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }));
43
+ ? (_jsxs("div", { className: css.headerStatic, children: [props.icon !== undefined ? _jsx("span", { className: css.mark, "aria-hidden": "true", children: props.icon }) : null, _jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: props.descriptionNode ?? description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null] }))
44
+ : (_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [props.icon !== undefined ? _jsx("span", { className: css.mark, "aria-hidden": "true", children: props.icon }) : null, _jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: props.descriptionNode ?? description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }));
45
45
  // The namespace exists but the Host does not serve it to this client (the
46
46
  // official settings allowlist omits third-party namespaces): show a card
47
47
  // that explains the gap instead of vanishing, so a missing card never
@@ -1 +1 @@
1
- {"version":3,"file":"RemoteSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4CAA4C,CAAA;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAElE,OAAO,EAA+D,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAIrK,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6FAA6F;IAC7F,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,uBAAwB,SAAQ,SAAS;IACxD,qBAAqB;IACrB,OAAO,EAAE,cAAc,CAAA;IACvB,sBAAsB;IACtB,UAAU,EAAE,cAAc,CAAA;IAC1B,gCAAgC;IAChC,cAAc,EAAE,cAAc,CAAA;IAC9B,yBAAyB;IACzB,UAAU,EAAE,cAAc,CAAA;IAC1B,0BAA0B;IAC1B,YAAY,EAAE,cAAc,CAAA;IAC5B,0BAA0B;IAC1B,UAAU,EAAE,cAAc,CAAA;IAC1B,sBAAsB;IACtB,oBAAoB,EAAE,cAAc,CAAA;IACpC,kCAAkC;IAClC,aAAa,EAAE,cAAc,CAAA;IAC7B,iCAAiC;IACjC,UAAU,EAAE,cAAc,CAAA;IAC1B,wDAAwD;IACxD,WAAW,EAAE,cAAc,CAAA;IAC3B,kCAAkC;IAClC,KAAK,EAAE,cAAc,CAAA;IACrB,uBAAuB;IACvB,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,gEAAgE;AAChE,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,KAAK,EAAE;QACL,oEAAoE;QACpE,kBAAkB,EAAE,aAAa,CAAC,uBAAuB,CAAC,CAAA;KAC3D,CAAA;CACF;AAED,oEAAoE;AACpE,qBAAa,4BAA4B;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0B;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAE9D,2EAA2E;gBAC/D,KAAK,EAAE,UAAU,CAAC,cAAc,CAAC;IAkB7C,OAAO,CAAC,UAAU;IAkBlB;;;OAGG;IACH,MAAM,IAAI,sBAAsB;IAIhC;;;OAGG;IACH,OAAO,IAAI,IAAI;CAGhB;AAED,4DAA4D;AAC5D,MAAM,MAAM,uBAAuB,GACjC,YAAY,CAAC,oBAAoB,CAAC,GAChC,WAAW,CAAC,QAAQ,CAAC,GACrB,UAAU,CAAC,sBAAsB,CAAC,CAAA;AAEtC;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,+BAwJhE"}
1
+ {"version":3,"file":"RemoteSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4CAA4C,CAAA;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAGlE,OAAO,EAA+D,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAIrK,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6FAA6F;IAC7F,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,uBAAwB,SAAQ,SAAS;IACxD,qBAAqB;IACrB,OAAO,EAAE,cAAc,CAAA;IACvB,sBAAsB;IACtB,UAAU,EAAE,cAAc,CAAA;IAC1B,gCAAgC;IAChC,cAAc,EAAE,cAAc,CAAA;IAC9B,yBAAyB;IACzB,UAAU,EAAE,cAAc,CAAA;IAC1B,0BAA0B;IAC1B,YAAY,EAAE,cAAc,CAAA;IAC5B,0BAA0B;IAC1B,UAAU,EAAE,cAAc,CAAA;IAC1B,sBAAsB;IACtB,oBAAoB,EAAE,cAAc,CAAA;IACpC,kCAAkC;IAClC,aAAa,EAAE,cAAc,CAAA;IAC7B,iCAAiC;IACjC,UAAU,EAAE,cAAc,CAAA;IAC1B,wDAAwD;IACxD,WAAW,EAAE,cAAc,CAAA;IAC3B,kCAAkC;IAClC,KAAK,EAAE,cAAc,CAAA;IACrB,uBAAuB;IACvB,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,gEAAgE;AAChE,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,KAAK,EAAE;QACL,oEAAoE;QACpE,kBAAkB,EAAE,aAAa,CAAC,uBAAuB,CAAC,CAAA;KAC3D,CAAA;CACF;AAED,oEAAoE;AACpE,qBAAa,4BAA4B;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0B;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAE9D,2EAA2E;gBAC/D,KAAK,EAAE,UAAU,CAAC,cAAc,CAAC;IAkB7C,OAAO,CAAC,UAAU;IAkBlB;;;OAGG;IACH,MAAM,IAAI,sBAAsB;IAIhC;;;OAGG;IACH,OAAO,IAAI,IAAI;CAGhB;AAED,4DAA4D;AAC5D,MAAM,MAAM,uBAAuB,GACjC,YAAY,CAAC,oBAAoB,CAAC,GAChC,WAAW,CAAC,QAAQ,CAAC,GACrB,UAAU,CAAC,sBAAsB,CAAC,CAAA;AAEtC;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,+BAyJhE"}
@@ -6,6 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  */
7
7
  import { useEffect, useState } from 'react';
8
8
  import { PluginSettingsCard, ValueField, BooleanField } from "./PluginSettingsCard.js";
9
+ import { PhoneIcon } from "./PhoneIcon.js";
9
10
  import { CardForm, booleanField, numberField, secretField, textField } from "./settings-form.js";
10
11
  import { LanBindStatusError, readLanBindStatus, shouldStopLanBindPoll } from "./pair-api.js";
11
12
  /** Bridges the `remote-web-ui` form onto the card's staged form. */
@@ -77,7 +78,7 @@ export function RemoteSettingsCard(props) {
77
78
  invalidLabel: t('settings.invalidNumber'),
78
79
  disabled,
79
80
  };
80
- return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", defaultOpen: false, state: state, onSave: props.save, onDiscard: props.discard, children: [_jsx(BooleanField, { id: "settings-remote-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(ValueField, { id: "settings-remote-token-ttl", label: t('settings.tokenTtlMs'), hint: t('settings.tokenTtlMsHint'), numeric: true, ...fieldProps, ...state.tokenTtlMs, onEdit: (text) => { props.edit('tokenTtlMs', text); }, onReset: () => { props.resetField('tokenTtlMs'); } }), _jsx(ValueField, { id: "settings-remote-offline", label: t('settings.offlineAfterMs'), hint: t('settings.offlineAfterMsHint'), numeric: true, ...fieldProps, ...state.offlineAfterMs, onEdit: (text) => { props.edit('offlineAfterMs', text); }, onReset: () => { props.resetField('offlineAfterMs'); } }), _jsx(ValueField, { id: "settings-remote-max-devices", label: t('settings.maxDevices'), hint: t('settings.maxDevicesHint'), numeric: true, ...fieldProps, ...state.maxDevices, onEdit: (text) => { props.edit('maxDevices', text); }, onReset: () => { props.resetField('maxDevices'); } }), _jsx(ValueField, { id: "settings-remote-idle-expire", label: t('settings.idleExpireMs'), hint: t('settings.idleExpireMsHint'), numeric: true, ...fieldProps, ...state.idleExpireMs, onEdit: (text) => { props.edit('idleExpireMs', text); }, onReset: () => { props.resetField('idleExpireMs'); } }), _jsx(ValueField, { id: "settings-remote-cookie", label: t('settings.cookieName'), hint: t('settings.cookieNameHint'), ...fieldProps, ...state.cookieName, onEdit: (text) => { props.edit('cookieName', text); }, onReset: () => { props.resetField('cookieName'); } }), _jsx(BooleanField, { id: "settings-remote-fence", label: t('settings.requirePairingForLan'), hint: t('settings.requirePairingForLanHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.requirePairingForLan, onEdit: (text) => { props.edit('requirePairingForLan', text); }, onReset: () => { props.resetField('requirePairingForLan'); } }), _jsx(ValueField, { id: "settings-remote-public-base", label: t('settings.publicBaseUrl'), hint: t('settings.publicBaseUrlHint'), placeholder: "https://example.trycloudflare.com", ...fieldProps, ...state.publicBaseUrl, onEdit: (text) => { props.edit('publicBaseUrl', text); }, onReset: () => { props.resetField('publicBaseUrl'); } }), _jsx(BooleanField, { id: "settings-remote-auto-tunnel", label: t('settings.autoTunnel'), hint: t('settings.autoTunnelHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.autoTunnel, onEdit: (text) => { props.edit('autoTunnel', text); }, onReset: () => { props.resetField('autoTunnel'); } }), _jsx(ValueField, { id: "settings-remote-tunnel-token", label: t('settings.tunnelToken'), hint: t('settings.tunnelTokenHint'), placeholder: "eyJ...", ...fieldProps, ...state.tunnelToken, onEdit: (text) => { props.edit('tunnelToken', text); }, onReset: () => { props.resetField('tunnelToken'); } }), _jsx(BooleanField, { id: "settings-remote-relay", label: t('settings.relay'), hint: t('settings.relayHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.relay, onEdit: (text) => { props.edit('relay', text); }, onReset: () => { props.resetField('relay'); } }), _jsx(LanBindStatus, { t: t }), _jsx(BooleanField, { id: "settings-remote-lan-bind", label: t('settings.lanBind'), hint: t('settings.lanBindHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.lanBind, onEdit: (text) => { props.edit('lanBind', text); }, onReset: () => { props.resetField('lanBind'); } })] }));
81
+ return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", icon: _jsx(PhoneIcon, { size: 18 }), defaultOpen: false, state: state, onSave: props.save, onDiscard: props.discard, children: [_jsx(BooleanField, { id: "settings-remote-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(ValueField, { id: "settings-remote-token-ttl", label: t('settings.tokenTtlMs'), hint: t('settings.tokenTtlMsHint'), numeric: true, ...fieldProps, ...state.tokenTtlMs, onEdit: (text) => { props.edit('tokenTtlMs', text); }, onReset: () => { props.resetField('tokenTtlMs'); } }), _jsx(ValueField, { id: "settings-remote-offline", label: t('settings.offlineAfterMs'), hint: t('settings.offlineAfterMsHint'), numeric: true, ...fieldProps, ...state.offlineAfterMs, onEdit: (text) => { props.edit('offlineAfterMs', text); }, onReset: () => { props.resetField('offlineAfterMs'); } }), _jsx(ValueField, { id: "settings-remote-max-devices", label: t('settings.maxDevices'), hint: t('settings.maxDevicesHint'), numeric: true, ...fieldProps, ...state.maxDevices, onEdit: (text) => { props.edit('maxDevices', text); }, onReset: () => { props.resetField('maxDevices'); } }), _jsx(ValueField, { id: "settings-remote-idle-expire", label: t('settings.idleExpireMs'), hint: t('settings.idleExpireMsHint'), numeric: true, ...fieldProps, ...state.idleExpireMs, onEdit: (text) => { props.edit('idleExpireMs', text); }, onReset: () => { props.resetField('idleExpireMs'); } }), _jsx(ValueField, { id: "settings-remote-cookie", label: t('settings.cookieName'), hint: t('settings.cookieNameHint'), ...fieldProps, ...state.cookieName, onEdit: (text) => { props.edit('cookieName', text); }, onReset: () => { props.resetField('cookieName'); } }), _jsx(BooleanField, { id: "settings-remote-fence", label: t('settings.requirePairingForLan'), hint: t('settings.requirePairingForLanHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.requirePairingForLan, onEdit: (text) => { props.edit('requirePairingForLan', text); }, onReset: () => { props.resetField('requirePairingForLan'); } }), _jsx(ValueField, { id: "settings-remote-public-base", label: t('settings.publicBaseUrl'), hint: t('settings.publicBaseUrlHint'), placeholder: "https://example.trycloudflare.com", ...fieldProps, ...state.publicBaseUrl, onEdit: (text) => { props.edit('publicBaseUrl', text); }, onReset: () => { props.resetField('publicBaseUrl'); } }), _jsx(BooleanField, { id: "settings-remote-auto-tunnel", label: t('settings.autoTunnel'), hint: t('settings.autoTunnelHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.autoTunnel, onEdit: (text) => { props.edit('autoTunnel', text); }, onReset: () => { props.resetField('autoTunnel'); } }), _jsx(ValueField, { id: "settings-remote-tunnel-token", label: t('settings.tunnelToken'), hint: t('settings.tunnelTokenHint'), placeholder: "eyJ...", ...fieldProps, ...state.tunnelToken, onEdit: (text) => { props.edit('tunnelToken', text); }, onReset: () => { props.resetField('tunnelToken'); } }), _jsx(BooleanField, { id: "settings-remote-relay", label: t('settings.relay'), hint: t('settings.relayHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.relay, onEdit: (text) => { props.edit('relay', text); }, onReset: () => { props.resetField('relay'); } }), _jsx(LanBindStatus, { t: t }), _jsx(BooleanField, { id: "settings-remote-lan-bind", label: t('settings.lanBind'), hint: t('settings.lanBindHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.lanBind, onEdit: (text) => { props.edit('lanBind', text); }, onReset: () => { props.resetField('lanBind'); } })] }));
81
82
  }
82
83
  /**
83
84
  * Live LAN-bind facts above the toggle: the managed block's host, the live
@@ -151,9 +151,9 @@ export function apply(ctx) {
151
151
  }
152
152
  const t = ctx.locale.bind(NS);
153
153
  // Hand the `remote` translate seat to the module-scope adaptation layer:
154
- // the whale/compact-picker labels were rendered with the English fallback
155
- // before any dictionary existed; the wiring plus the layer's own sync tick
156
- // re-render them in the active locale.
154
+ // the whale label was rendered with the English fallback before any
155
+ // dictionary existed; the wiring plus the layer's own sync tick re-renders
156
+ // it in the active locale.
157
157
  if (adapt !== undefined)
158
158
  adapt.translate = t;
159
159
  // The settings form this card edits. The family group's binder resolves a
@@ -3,8 +3,6 @@
3
3
  export declare const zh: {
4
4
  'entry.label': string;
5
5
  'mobile.whale.open': string;
6
- 'mobile.composer.pickModel': string;
7
- 'mobile.composer.pickEffort': string;
8
6
  title: string;
9
7
  subtitle: string;
10
8
  'card.title': string;
@@ -129,8 +127,6 @@ export type SettingsCardKey = RemoteKey;
129
127
  export declare const en: {
130
128
  'entry.label': string;
131
129
  'mobile.whale.open': string;
132
- 'mobile.composer.pickModel': string;
133
- 'mobile.composer.pickEffort': string;
134
130
  title: string;
135
131
  subtitle: string;
136
132
  'card.title': string;
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0HmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0HsB,CAAA"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwHmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwHsB,CAAA"}
@@ -3,8 +3,6 @@
3
3
  export const zh = {
4
4
  'entry.label': '远程访问',
5
5
  'mobile.whale.open': '打开侧边栏',
6
- 'mobile.composer.pickModel': '选择模型',
7
- 'mobile.composer.pickEffort': '选择推理等级',
8
6
  'title': '远程访问',
9
7
  'subtitle': '通过手机或另一台电脑配对,远程使用同一份 Web 界面(官方界面 + 移动端适配)',
10
8
  'card.title': '设备配对',
@@ -14,7 +12,7 @@ export const zh = {
14
12
  'status.disconnected': '已配对设备离线',
15
13
  'status.stopped': '已停止远程访问',
16
14
  'status.lanRequired': '此功能需要局域网绑定或公网地址才能使用',
17
- 'status.lanRequiredHint': '当前服务仅绑定在 127.0.0.1 且未配置公网地址,手机无法访问。请在本插件的设置卡片中打开“局域网访问”(装在全家桶里时在「设置 → Web 插件 → 远程访问设置」,单独安装时在设置 → 插件 → 本插件行),或用 dsh web --host 0.0.0.0 重新启动,或填写内网穿透的公网地址。',
15
+ 'status.lanRequiredHint': '当前服务仅绑定在 127.0.0.1 且未配置公网地址,手机无法访问。请在本插件的设置卡片中打开“局域网访问”(装在全家桶里时在「设置 → Web 插件 → 远程访问」,单独安装时在设置 → 插件 → 本插件行),或用 dsh web --host 0.0.0.0 重新启动,或填写内网穿透的公网地址。',
18
16
  'status.loopbackRequired': '配对面板仅限本机使用',
19
17
  'status.loopbackRequiredHint': '请通过 http://127.0.0.1 打开此页面后重试;手机请使用配对链接访问。',
20
18
  'status.unreachable': '无法连接配对服务',
@@ -71,8 +69,8 @@ export const zh = {
71
69
  'relay.failed': '固定域名同步失败:{error}。正在自动重试,期间二维码可能使用临时地址。',
72
70
  'close.label': '关闭远程访问面板',
73
71
  // 插件设置卡片(家族 list 席位,或官方 plugins.bundle.config 席位)。
74
- 'settings.title': '远程访问设置',
75
- 'settings.description': '配对安全与设备限额。',
72
+ 'settings.title': '远程访问',
73
+ 'settings.description': '手机配对、公网隧道与设备限额。',
76
74
  'settings.enabled': '启用远程访问',
77
75
  'settings.enabledHint': '关闭后移除侧边栏入口并停用配对路由与局域网栅栏。',
78
76
  'settings.tokenTtlMs': '配对令牌有效期(毫秒)',
@@ -127,8 +125,6 @@ export const zh = {
127
125
  export const en = {
128
126
  'entry.label': 'Remote access',
129
127
  'mobile.whale.open': 'Open sidebar',
130
- 'mobile.composer.pickModel': 'Pick model',
131
- 'mobile.composer.pickEffort': 'Pick reasoning effort',
132
128
  'title': 'Remote access',
133
129
  'subtitle': 'Pair a phone or another computer to share the same Web GUI (official UI + mobile adaptation)',
134
130
  'card.title': 'Pair a device',
@@ -195,8 +191,8 @@ export const en = {
195
191
  'relay.failed': 'Stable-hostname sync failed: {error}. Retrying automatically; the QR may use the ephemeral address meanwhile.',
196
192
  'close.label': 'Close remote access panel',
197
193
  // Plugin settings card (the family list seat, or the official bundle-config seat).
198
- 'settings.title': 'Remote access settings',
199
- 'settings.description': 'Pairing security and device limits.',
194
+ 'settings.title': 'Remote access',
195
+ 'settings.description': 'Phone pairing, public tunnel and device limits.',
200
196
  'settings.enabled': 'Enable remote access',
201
197
  'settings.enabledHint': 'When off, the sidebar entry is removed and pairing routes plus the LAN fence stop.',
202
198
  'settings.tokenTtlMs': 'Pairing token lifetime (ms)',
@@ -30,8 +30,8 @@ export interface RemoteAdaptGlobal {
30
30
  closeDetails: (() => void) | null;
31
31
  /**
32
32
  * Wired by the plugin apply once the `remote` locale namespace is bound:
33
- * the translate seat the injected surfaces (whale, compact picker) read
34
- * their labels from. Null until then; label reads fall back to English
33
+ * the translate seat the injected surface (the whale) reads its label
34
+ * from. Null until then; label reads fall back to English
35
35
  * (the SDK's universal fallback) and the 600ms sync tick re-renders the
36
36
  * labels once the seat is wired.
37
37
  */
@@ -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;AA8RD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAqhCvC"}
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;AA+RD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAi8BvC"}