@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
@@ -42,12 +42,6 @@ const ROW_SELECTOR = '[class*="_sessionRow"], [class*="_projectRow"]';
42
42
  const STYLE_SELECTOR = `style[data-plugin-css="${ADAPT_CSS_ID}"]`;
43
43
  /** Whale button id. */
44
44
  const WHALE_ID = 'dshRemoteWhale';
45
- /** Compact picker: synthesized model button id. */
46
- const MODEL_BTN_ID = 'dshRemoteModelPick';
47
- /** Compact picker: synthesized effort button id. */
48
- const EFFORT_BTN_ID = 'dshRemoteEffortPick';
49
- /** Body class while the compact picker buttons are wired. */
50
- const COMPACT_CLASS = 'dsh-remote-compact-picker';
51
45
  /** Body class while the header actions are seated in the tabs row. */
52
46
  const HEADER_SEATED_CLASS = 'dsh-remote-header-seated';
53
47
  /**
@@ -56,13 +50,6 @@ const HEADER_SEATED_CLASS = 'dsh-remote-header-seated';
56
50
  * reserving the space with padding.
57
51
  */
58
52
  const HEADER_RESERVE_VAR = '--dsh-remote-header-actions-reserve';
59
- /** Locale-dependent fast path for the official picker cells (zh/en). */
60
- const PICKER_CELL_PATTERN = {
61
- model: /模型|Model/, // i18n-allow: matches official picker cell text, not plugin copy
62
- effort: /推理等级|Reasoning|Effort/i, // i18n-allow: matches official picker cell text, not plugin copy
63
- };
64
- /** Position of each drill cell among the sheet's chevron cells. */
65
- const DRILL_INDEX = { model: 0, effort: 1 };
66
53
  /**
67
54
  * The official application frame. The layout column classes are the anchor:
68
55
  * `_frame` is shared by unrelated official components (the chat turn rail,
@@ -163,14 +150,41 @@ const ADAPT_CSS = [
163
150
  '[class$="_headerUtilities"]{display:none}',
164
151
  // v52 composer: the two lines (permission / model) stay stacked with zero
165
152
  // row gap; the command, context-meter and send/stop buttons float at the
166
- // left/right edges vertically centered over both lines.
153
+ // left/right edges vertically centered over both lines. "Both lines" is what
154
+ // makes _row the containing block for those three floats: _row is the one box
155
+ // that wraps the lines, so nothing between a float and _row may claim it.
167
156
  '[class$="_composerSeat"] [class$="_card"]{margin-bottom:1px}',
168
157
  '[class$="_composerSeat"] [class$="_row"]{flex-wrap:wrap;row-gap:0;padding:2px 8px 1px;position:relative}',
169
- '[class$="_composerSeat"] [class$="_add"]{position:absolute;left:8px;top:50%;transform:translateY(-50%)}',
158
+ // The attach button is the only file-picker entry on the portrait composer,
159
+ // and taking it out of flow costs it the paint order it had inline: a
160
+ // positioned sibling painted after it (the trailing line, see below) covers
161
+ // it and eats the taps on the covered half. Its own z-index makes the hit
162
+ // layer a property of the button instead of a side effect of a sibling
163
+ // (#1829). It clears the composer's own stacking ladder and stays far below
164
+ // the bottom-sheet menus (2147482000), which own the overlay above it.
165
+ '[class$="_composerSeat"] [class$="_add"]{position:absolute;left:8px;top:50%;transform:translateY(-50%);z-index:10}',
170
166
  '[class$="_composerSeat"] [class$="_modes"]{min-width:0;padding-left:38px}',
171
167
  // Model line left-aligned with the permission line (same command-button
172
168
  // clearance), rows stay tightly stacked.
173
- '[class$="_composerSeat"] [class$="_trailing"]{flex-basis:100%;position:relative;min-height:32px;justify-content:flex-start;padding-left:38px;padding-right:78px}',
169
+ // The trailing line is forced onto its own row at flex-basis:100% and carries
170
+ // 116px of side padding (38 left for the command button, 78 right for the
171
+ // send button). Under the default content-box that padding is ADDED to the
172
+ // 100% basis, so the line's own box overflowed the card by exactly 116px and
173
+ // dragged the absolutely-positioned send button out with it: on a 393px phone
174
+ // the card ended at x=367 while the button sat at x=433..467, past the
175
+ // viewport's right edge, which is the unreachable button the reporter
176
+ // measured (#1818). border-box contains the padding inside the basis, so the
177
+ // line stays within the card and the button lands back inside it.
178
+ //
179
+ // The line stays UNPOSITIONED on purpose. position:relative arrived here with
180
+ // the compact picker and outlived it, but this box is the LAST line only: as
181
+ // the nearest positioned ancestor it took the containing block from _row, so
182
+ // the send button and the context meter centered inside the trailing line
183
+ // instead of over both lines - the send button rode the input's bottom edge,
184
+ // out of line with the attach button - and, painted after the out-of-flow
185
+ // attach button, the line covered that button's lower half and swallowed the
186
+ // taps there (#1829).
187
+ '[class$="_composerSeat"] [class$="_trailing"]{box-sizing:border-box;flex-basis:100%;min-height:32px;justify-content:flex-start;padding-left:38px;padding-right:78px}',
174
188
  '[class$="_composerSeat"] [class$="_trailing"] *{font-size:12px}',
175
189
  // v54: smaller permission/model buttons (font + height). v79: the
176
190
  // permission trigger collapses to its shield icon on phones — the label
@@ -193,24 +207,12 @@ const ADAPT_CSS = [
193
207
  '[class$="_composerSeat"]{transform:none !important}',
194
208
  '[class$="_composerSeat"] [class$="_menu"]{position:fixed !important;left:8px !important;right:8px !important;top:auto !important;bottom:calc(8px + env(safe-area-inset-bottom)) !important;width:auto !important;max-width:none !important;max-height:70dvh !important;overflow-y:auto !important;z-index:2147482000}',
195
209
  '[class$="_composerSeat"] [class$="_menu"] [class$="_cell"]{height:44px;min-height:44px;font-size:13px}',
196
- // v79 (compact picker): when the row is too narrow for the desktop text
197
- // triggers, the phone falls back to icon entries — the context ring stays
198
- // official, and two synthesized buttons open the picker sheet straight on
199
- // the model list and the effort list (drill-through, so one tap lands on
200
- // the same list the user's mock shows). The original text trigger hides
201
- // only while the wired buttons exist (body class): a failed wiring
202
- // degrades back to the usable text trigger instead of no picker.
203
- `body.${COMPACT_CLASS} [class$="_composerSeat"] [class$="_trailing"] [class$="_trigger"]:has([class$="_triggerEffort"]){display:none}`,
204
- // The icon buttons sit inline in the tools row (parallel to the
205
- // permission trigger), so the trailing line collapses to zero and the
206
- // context ring + send re-anchor to the row itself. The ring shifts a
207
- // few px right: at its desktop offset its hit box kisses the effort
208
- // button.
209
- `body.${COMPACT_CLASS} [class$="_composerSeat"] [class$="_trailing"]{flex-basis:auto;position:static;min-height:0;padding:0;width:0}`,
210
- `body.${COMPACT_CLASS} [class$="_composerSeat"] [class$="_trailing"] > [class$="_root"]:has([class$="_track"]){right:44px}`,
211
- `#${MODEL_BTN_ID},#${EFFORT_BTN_ID}{width:26px;height:32px;min-width:26px;padding:0;border-radius:9px;background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:center;cursor:pointer;flex:none;margin-left:4px}`,
212
- `#${MODEL_BTN_ID} svg,#${EFFORT_BTN_ID} svg{width:16px;height:16px;display:block}`,
213
- `#${MODEL_BTN_ID}:active,#${EFFORT_BTN_ID}:active{opacity:.7}`,
210
+ // v79's compact picker (two synthesized model/effort icon buttons in the
211
+ // tools row, the trailing text trigger hidden behind a body class, and the
212
+ // trailing line collapsed to zero width) was removed at the user's request.
213
+ // The portrait composer keeps the official text trigger with the trailing
214
+ // line rules above; the bottom-sheet rules immediately above still apply to
215
+ // the official menu.
214
216
  // Bottom stats line (N rounds / M steps) two sizes below the tabs; v55
215
217
  // wraps freely but clamps at two lines (the official rule is nowrap +
216
218
  // single-line ellipsis); v60 drops the official 32px right padding.
@@ -284,18 +286,22 @@ const ADAPT_CSS = [
284
286
  // v68: settings modal on mobile — the official panel is a fixed 800px
285
287
  // two-column layout (nav + content); switch to a column layout: the
286
288
  // section nav becomes a horizontal scrollable row on top.
287
- '[class$="_overlay"] [class$="_panel"]{flex-direction:column;max-height:calc(100dvh - 32px)}',
288
- '[class$="_overlay"] [class$="_panel"] [class$="_nav"]{flex-direction:row;gap:4px;width:100%;padding:12px 12px 0;overflow-x:auto;overflow-y:hidden}',
289
- '[class$="_overlay"] [class$="_panel"] [class$="_navTitle"]{display:none}',
290
- '[class$="_overlay"] [class$="_panel"] [class$="_navList"]{flex-direction:row;gap:4px}',
291
- '[class$="_overlay"] [class$="_panel"] [class$="_navCell"]{height:34px;padding:0 12px;gap:6px;flex:none;border-radius:10px}',
292
- '[class$="_overlay"] [class$="_panel"] [class$="_navLabel"]{font-size:13px}',
293
- '[class$="_overlay"] [class$="_panel"] [class$="_content"]{flex:1;min-height:0}',
289
+ // Scoped to the portrait body class like the suppressions above: these
290
+ // are the only rules in this array that reshape an official two-column
291
+ // surface, and the overlay portal is shared with every other dialog.
292
+ // Unscoped, the tag — which lives in <head> for as long as the layer
293
+ // is active and is restored by the sync tick after a revert —
294
+ // collapsed the DESKTOP settings panel into a stacked column (measured
295
+ // on a real 1440px host: panel flex-direction row -> column, nav 800px
296
+ // tall -> 236px, content 612px -> 800px wide and pushed below the nav).
297
+ `body.${ACTIVE_CLASS} [class$="_overlay"] [class$="_panel"]{flex-direction:column;max-height:calc(100dvh - 32px)}`,
298
+ `body.${ACTIVE_CLASS} [class$="_overlay"] [class$="_panel"] [class$="_nav"]{flex-direction:row;gap:4px;width:100%;padding:12px 12px 0;overflow-x:auto;overflow-y:hidden}`,
299
+ `body.${ACTIVE_CLASS} [class$="_overlay"] [class$="_panel"] [class$="_navTitle"]{display:none}`,
300
+ `body.${ACTIVE_CLASS} [class$="_overlay"] [class$="_panel"] [class$="_navList"]{flex-direction:row;gap:4px}`,
301
+ `body.${ACTIVE_CLASS} [class$="_overlay"] [class$="_panel"] [class$="_navCell"]{height:34px;padding:0 12px;gap:6px;flex:none;border-radius:10px}`,
302
+ `body.${ACTIVE_CLASS} [class$="_overlay"] [class$="_panel"] [class$="_navLabel"]{font-size:13px}`,
303
+ `body.${ACTIVE_CLASS} [class$="_overlay"] [class$="_panel"] [class$="_content"]{flex:1;min-height:0}`,
294
304
  ];
295
- /** Cube glyph for the compact model button (a plain box outline). */
296
- const CUBE_ICON = '<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>';
297
- /** Level glyph for the compact effort button (three rising bars). */
298
- const LEVELS_ICON = '<path d="M4 6h16"/><path d="M7 12h10"/><path d="M10 18h4"/>';
299
305
  /** The DeepSeek fish glyph (the official brand mark path). */
300
306
  const FISH_PATH = 'M22.9168 1.43018C22.6713 1.31018 22.5658 1.53918 22.4223 1.65519C22.3733 1.69269 22.3318 1.74169 22.2903 1.78669C21.9317 2.1697 21.5127 2.42121 20.9657 2.39121C20.1657 2.34621 19.4827 2.59771 18.8787 3.20973C18.7502 2.45521 18.3236 2.0047 17.6746 1.71569C17.3351 1.56568 16.9916 1.41518 16.7536 1.08867C16.5876 0.856163 16.5421 0.597155 16.4591 0.341647C16.4061 0.187643 16.3536 0.0301382 16.1761 0.00363739C15.9836 -0.0263635 15.9081 0.135141 15.8326 0.270145C15.5306 0.822162 15.4136 1.43018 15.4251 2.0462C15.4516 3.43174 16.0366 4.53527 17.1991 5.3203C17.3311 5.4103 17.3651 5.5003 17.3236 5.63181C17.2441 5.90231 17.1501 6.16482 17.0671 6.43533C17.0141 6.60784 16.9351 6.64584 16.7501 6.57033C16.1121 6.30383 15.5611 5.90931 15.074 5.4328C14.2475 4.63328 13.5 3.75075 12.568 3.05973C12.349 2.89822 12.13 2.74822 11.9034 2.60522C10.9524 1.68169 12.028 0.923165 12.277 0.833162C12.5375 0.739159 12.3675 0.41615 11.5259 0.42015C10.6844 0.42365 9.91439 0.705658 8.93286 1.08117C8.78935 1.13767 8.63835 1.17867 8.48384 1.21267C7.59332 1.04367 6.66829 1.00617 5.70226 1.11517C3.88321 1.31768 2.43016 2.1777 1.36213 3.64575C0.0790928 5.4103 -0.222916 7.41536 0.146595 9.50642C0.535106 11.7105 1.66014 13.535 3.38869 14.9616C5.18125 16.4406 7.24581 17.1657 9.60138 17.0266C11.0319 16.9441 12.6245 16.7526 14.421 15.2321C14.874 15.4576 15.3496 15.5476 16.1381 15.6151C16.7456 15.6716 17.3306 15.5851 17.7836 15.4911C18.4931 15.3411 18.4441 14.6841 18.1876 14.5636C16.1081 13.595 16.5646 13.9891 16.1496 13.67C17.2061 12.42 18.8202 10.1979 19.3182 7.17235C19.3672 6.83834 19.4297 6.36783 19.4222 6.09732C19.4182 5.93231 19.4562 5.86831 19.6447 5.84931C20.1657 5.78931 20.6712 5.64681 21.1357 5.3913C22.4833 4.65528 23.0268 3.44624 23.1548 1.9972C23.1738 1.77569 23.1508 1.54668 22.9168 1.43018Z';
301
307
  /**
@@ -408,7 +414,6 @@ export function startMobileAdapt() {
408
414
  active = false;
409
415
  unseatHeaderActions();
410
416
  restoreRowDrag();
411
- removeCompactPicker();
412
417
  document.body.classList.remove(ACTIVE_CLASS);
413
418
  document.body.classList.remove(RAIL_HIDDEN_CLASS);
414
419
  const tag = document.querySelector(`style[data-plugin-css="${ADAPT_CSS_ID}"]`);
@@ -478,90 +483,6 @@ export function startMobileAdapt() {
478
483
  whaleTimer = null;
479
484
  }
480
485
  }
481
- /**
482
- * Compact picker (v79): a phone row cannot fit the desktop text triggers,
483
- * so the model/effort entries become two icon buttons in the trailing
484
- * row. Both forward to the official picker trigger (its menu renders as
485
- * the bottom sheet) and then drill straight into the asked cell — model
486
- * list or effort list — so one tap lands on the list, matching the
487
- * cube-model / brain-effort mapping. The official context ring next to
488
- * the send button keeps its own semantics untouched.
489
- */
490
- function removeCompactPicker() {
491
- document.body.classList.remove(COMPACT_CLASS);
492
- document.getElementById(MODEL_BTN_ID)?.remove();
493
- document.getElementById(EFFORT_BTN_ID)?.remove();
494
- }
495
- function drillIntoPicker(kind) {
496
- const trigger = document.querySelector('[class$="_composerSeat"] [class$="_trailing"] [class$="_trigger"]:has([class$="_triggerEffort"])');
497
- if (trigger === null)
498
- return;
499
- trigger.click();
500
- // The sheet mount takes a beat (observed ~0.2-0.6s on a cold phone
501
- // mirror); poll until the asked cell exists instead of a fixed delay.
502
- let tries = 0;
503
- const tapCell = () => {
504
- tries += 1;
505
- const cells = Array.from(document.querySelectorAll('[class$="_composerSeat"] [class$="_menu"] [class$="_cell"]'));
506
- // The official cell copy is localized (zh/en/ru), so the label match is
507
- // only the fast path; the sheet's drill cells (label + value + chevron)
508
- // are the structural anchor: model first, effort second, on every locale.
509
- const byLabel = cells.find((c) => PICKER_CELL_PATTERN[kind].test(c.textContent ?? ''));
510
- const drillable = cells.filter((c) => c.querySelector('[class*="_cellChevron"], [class*="_chevron"]'));
511
- const cell = byLabel ?? drillable[DRILL_INDEX[kind]];
512
- if (cell !== undefined) {
513
- ;
514
- cell.click();
515
- return;
516
- }
517
- if (tries < 8)
518
- window.setTimeout(tapCell, 150);
519
- };
520
- window.setTimeout(tapCell, 150);
521
- }
522
- function makeCompactButton(id, title, icon, kind) {
523
- const btn = document.createElement('button');
524
- btn.id = id;
525
- btn.type = 'button';
526
- btn.dataset.dshPlugin = 'remote-web-ui';
527
- btn.title = title;
528
- btn.setAttribute('aria-label', title);
529
- btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${icon}</svg>`;
530
- btn.addEventListener('click', () => { drillIntoPicker(kind); });
531
- return btn;
532
- }
533
- function syncCompactPicker() {
534
- if (!active)
535
- return;
536
- const tools = nodeOf('[class$="_composerSeat"] [class$="_tools"]');
537
- const trigger = tools?.parentElement?.querySelector('[class$="_triggerEffort"]')?.parentElement;
538
- if (tools === null || trigger === null) {
539
- removeCompactPicker();
540
- return;
541
- }
542
- if (document.getElementById(MODEL_BTN_ID) === null) {
543
- // The cell pattern matches the OFFICIAL picker cell text (the official
544
- // client's own zh/en copy), not plugin-owned text.
545
- tools.appendChild(makeCompactButton(MODEL_BTN_ID, surfaceText('mobile.composer.pickModel', 'Pick model'), CUBE_ICON, 'model'));
546
- }
547
- if (document.getElementById(EFFORT_BTN_ID) === null) {
548
- tools.appendChild(makeCompactButton(EFFORT_BTN_ID, surfaceText('mobile.composer.pickEffort', 'Pick reasoning effort'), LEVELS_ICON, 'effort'));
549
- }
550
- // Titles re-read every tick so a locale switch (or the translate seat
551
- // arriving after this layer installed) is picked up without a reload.
552
- for (const [id, key, fallback] of [
553
- [MODEL_BTN_ID, 'mobile.composer.pickModel', 'Pick model'],
554
- [EFFORT_BTN_ID, 'mobile.composer.pickEffort', 'Pick reasoning effort'],
555
- ]) {
556
- const btn = document.getElementById(id);
557
- if (btn === null)
558
- continue;
559
- const label = surfaceText(key, fallback);
560
- btn.title = label;
561
- btn.setAttribute('aria-label', label);
562
- }
563
- document.body.classList.add(COMPACT_CLASS);
564
- }
565
486
  /**
566
487
  * The official sidebar toggle in the logo row. The row's own `_toggle` class
567
488
  * is the precise anchor; the row's last button is the fallback for cohorts
@@ -913,7 +834,6 @@ export function startMobileAdapt() {
913
834
  disableRowDrag();
914
835
  seatHeaderActions();
915
836
  alignActionsText();
916
- syncCompactPicker();
917
837
  }
918
838
  // v67: on mobile the header actions (agent-preset mode label + background
919
839
  // task badge) move from the title row into the tabs row. React re-creates
@@ -78,6 +78,11 @@ export interface PluginCardSeat {
78
78
  label?: () => string;
79
79
  /** Locale namespace the card renders with. */
80
80
  locale: string;
81
+ /**
82
+ * Child slots this card declares and renders. Forwarded to whichever seat
83
+ * the host renders; absent for a card that declares none.
84
+ */
85
+ children?: Record<string, unknown>;
81
86
  /** Business-face factory of the registration. */
82
87
  inject?: () => object;
83
88
  /**
@@ -1 +1 @@
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"}
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;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,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,CA8DpF"}
@@ -111,12 +111,14 @@ export function installPluginCard(ctx, seat) {
111
111
  ...(seat.order === undefined ? {} : { order: seat.order }),
112
112
  ...(seat.label === undefined ? {} : { label: seat.label }),
113
113
  locale: seat.locale,
114
+ ...(seat.children === undefined ? {} : { children: seat.children }),
114
115
  ...(seat.inject === undefined ? {} : { inject }),
115
116
  }
116
117
  : {
117
118
  name: OFFICIAL_PLUGIN_CARD_SEAT,
118
119
  key: seat.bundle,
119
120
  locale: seat.locale,
121
+ ...(seat.children === undefined ? {} : { children: seat.children }),
120
122
  ...(seat.inject === undefined ? {} : { inject }),
121
123
  }), component);
122
124
  current = target;
@@ -134,6 +134,8 @@ export declare class CardForm<T> {
134
134
  private readonly disposeForm;
135
135
  private disposed;
136
136
  private saving;
137
+ /** A save asked for while one was in flight; it runs once the first settles (#1754). */
138
+ private saveQueued;
137
139
  private failed;
138
140
  private failedReason;
139
141
  /** @param scope - the bound configuration form for this card's namespace. */
@@ -167,6 +169,20 @@ export declare class CardForm<T> {
167
169
  * the user can correct them instead of retyping.
168
170
  * @returns settlement after the mutation and the read-back.
169
171
  */
172
+ /**
173
+ * Run one save, and re-run it once if another was asked for while this one
174
+ * was still in flight.
175
+ *
176
+ * A save is a Host round trip that also drives the profile reconcile, and
177
+ * the Host runs that write inside one exclusive transaction. Answering a
178
+ * press that arrives mid-flight by returning immediately dropped the edit
179
+ * with no explanation, which is the "the save button stops working after a
180
+ * few rounds" report (#1754). Serializing instead means a save pressed while
181
+ * another is still settling runs against the settled state - which is what
182
+ * the operator meant by pressing it again.
183
+ * @returns settlement after the mutation and the read-back.
184
+ */
185
+ requestSave(): Promise<void>;
170
186
  save(): Promise<void>;
171
187
  /**
172
188
  * Every staged edit a save would write. An entry whose draft is not a value
@@ -1 +1 @@
1
- {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAsB,MAAM,4CAA4C,CAAA;AAChG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAGlE,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErB,0EAA0E;AAC1E,MAAM,WAAW,SAAS;IACxB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,2FAA2F;IAC3F,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;IAClC;;;OAGG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAA;CAChD;AAED,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAA;IACnB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,oDAAoD;AACpD,MAAM,WAAW,SAAS;IACxB,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAA;IACjB,4DAA4D;IAC5D,KAAK,EAAE,OAAO,CAAA;IACd,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAA;IAChB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAA;IACf,sFAAsF;IACtF,MAAM,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,uDAAuD;AACvD,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3C,gFAAgF;IAChF,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,yEAAyE;IACzE,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAwBD,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,EAAE,EAAE,KAAK,GAAG,OAAO,CAAA;IACnB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,6JAA6J;AAC7J,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,GAAE,iBAAsB,GAAG,SAAS,CAezF;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CASlD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAEpD;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAYrD;AAED,yGAAyG;AACzG,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAShF;AAED;;;;;;;GAOG;AACH,qBAAa,QAAQ,CAAC,CAAC;IAenB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAdxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAY;IACxC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAQ;IACtB,wFAAwF;IACxF,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,YAAY,CAAoB;IAExC,6EAA6E;gBAE1D,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EACrC,KAAK,EAAE,SAAS,EAAE;IAMpB;;;OAGG;IACH,OAAO,IAAI,IAAI;IAOf,uFAAuF;IACvF,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC;IAM3C,iFAAiF;IACjF,KAAK,IAAI,SAAS;IAelB,8EAA8E;IAC9E,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU;IAchC,wDAAwD;IACxD,OAAO,IAAI,WAAW;IAiBtB;;;;;;;;;;;;;;;OAeG;IACH;;;;;;;;;;;;OAYG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA4C3B;;;;;;OAMG;IACH,OAAO,CAAC,IAAI;IAiBZ;;;OAGG;IACH,OAAO,CAAC,SAAS;IAUjB;;;OAGG;IACH,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,KAAK;IAOb,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,OAAO;CAGhB"}
@@ -95,6 +95,8 @@ export class CardForm {
95
95
  disposeForm;
96
96
  disposed = false;
97
97
  saving = false;
98
+ /** A save asked for while one was in flight; it runs once the first settles (#1754). */
99
+ saveQueued = false;
98
100
  failed = false;
99
101
  failedReason;
100
102
  /** @param scope - the bound configuration form for this card's namespace. */
@@ -156,7 +158,7 @@ export class CardForm {
156
158
  resetField: (field) => {
157
159
  this.stage(field, { text: this.specOf(field).format(this.baseValue(field)), clear: true });
158
160
  },
159
- save: () => { void this.save(); },
161
+ save: () => { void this.requestSave(); },
160
162
  discard: () => {
161
163
  if (this.staged.size === 0 && !this.failed)
162
164
  return;
@@ -183,6 +185,30 @@ export class CardForm {
183
185
  * the user can correct them instead of retyping.
184
186
  * @returns settlement after the mutation and the read-back.
185
187
  */
188
+ /**
189
+ * Run one save, and re-run it once if another was asked for while this one
190
+ * was still in flight.
191
+ *
192
+ * A save is a Host round trip that also drives the profile reconcile, and
193
+ * the Host runs that write inside one exclusive transaction. Answering a
194
+ * press that arrives mid-flight by returning immediately dropped the edit
195
+ * with no explanation, which is the "the save button stops working after a
196
+ * few rounds" report (#1754). Serializing instead means a save pressed while
197
+ * another is still settling runs against the settled state - which is what
198
+ * the operator meant by pressing it again.
199
+ * @returns settlement after the mutation and the read-back.
200
+ */
201
+ async requestSave() {
202
+ if (this.saving) {
203
+ this.saveQueued = true;
204
+ return;
205
+ }
206
+ await this.save();
207
+ if (!this.saveQueued)
208
+ return;
209
+ this.saveQueued = false;
210
+ await this.requestSave();
211
+ }
186
212
  async save() {
187
213
  const plan = this.plan();
188
214
  const valid = plan.filter((item) => item.judge !== undefined);
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Run one piece of work outside the caller's async context.
3
+ *
4
+ * A Host mutation that runs inside `hmr.runExclusive` marks the running async
5
+ * context with an AsyncLocalStorage store. Side effects on that path that write
6
+ * a file the HMR config watcher refreshes from - `cordis.patch.yml` above all -
7
+ * must not run under that mark, or the watcher's refresh re-enters the
8
+ * exclusive transaction and is refused with "HMR transactions cannot be nested"
9
+ * (#1751, #1754, #1816).
10
+ *
11
+ * Deferring is NOT enough, and the earlier belt that this one replaces was
12
+ * built on a disproven premise: Node propagates an AsyncLocalStorage store into
13
+ * `setImmediate`, into `node:timers`, into promise continuations, and into any
14
+ * AsyncResource scoped to the current async id. Measured on Node 24:
15
+ *
16
+ * ```
17
+ * als.run(true, () => setImmediate(() => als.getStore())) // -> true
18
+ * ```
19
+ *
20
+ * What DOES start clean is an AsyncResource whose `triggerAsyncId` is not the
21
+ * transaction. A resource created once at module scope - that is, before any
22
+ * transaction existed - carries no store, and everything it schedules inherits
23
+ * that emptiness rather than the caller's mark:
24
+ *
25
+ * ```
26
+ * const detached = new AsyncResource('...') // module scope
27
+ * als.run(true, () => detached.runInAsyncScope(() => setImmediate(() => als.getStore())))
28
+ * // -> undefined
29
+ * ```
30
+ *
31
+ * @module dsh-web-shared/host/detached-work
32
+ */
33
+ /**
34
+ * Run `work` outside the caller's AsyncLocalStorage context.
35
+ *
36
+ * The call itself is synchronous - this schedules, it does not defer. Work that
37
+ * must not block the caller should schedule its own `setImmediate` inside
38
+ * `work`, which then also starts clean because it inherits this resource's
39
+ * empty context rather than the caller's.
40
+ *
41
+ * @param work - the side effect to detach from the caller's async context.
42
+ * @returns whatever `work` returns.
43
+ */
44
+ export declare function runDetached<T>(work: () => T): T;
45
+ //# sourceMappingURL=detached-work.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detached-work.d.ts","sourceRoot":"","sources":["../../src/detached-work.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAYH;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAE/C"}
@@ -0,0 +1,55 @@
1
+ // Generated by scripts/sync-shared.mjs from shared/host/detached-work.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
2
+ /**
3
+ * Run one piece of work outside the caller's async context.
4
+ *
5
+ * A Host mutation that runs inside `hmr.runExclusive` marks the running async
6
+ * context with an AsyncLocalStorage store. Side effects on that path that write
7
+ * a file the HMR config watcher refreshes from - `cordis.patch.yml` above all -
8
+ * must not run under that mark, or the watcher's refresh re-enters the
9
+ * exclusive transaction and is refused with "HMR transactions cannot be nested"
10
+ * (#1751, #1754, #1816).
11
+ *
12
+ * Deferring is NOT enough, and the earlier belt that this one replaces was
13
+ * built on a disproven premise: Node propagates an AsyncLocalStorage store into
14
+ * `setImmediate`, into `node:timers`, into promise continuations, and into any
15
+ * AsyncResource scoped to the current async id. Measured on Node 24:
16
+ *
17
+ * ```
18
+ * als.run(true, () => setImmediate(() => als.getStore())) // -> true
19
+ * ```
20
+ *
21
+ * What DOES start clean is an AsyncResource whose `triggerAsyncId` is not the
22
+ * transaction. A resource created once at module scope - that is, before any
23
+ * transaction existed - carries no store, and everything it schedules inherits
24
+ * that emptiness rather than the caller's mark:
25
+ *
26
+ * ```
27
+ * const detached = new AsyncResource('...') // module scope
28
+ * als.run(true, () => detached.runInAsyncScope(() => setImmediate(() => als.getStore())))
29
+ * // -> undefined
30
+ * ```
31
+ *
32
+ * @module dsh-web-shared/host/detached-work
33
+ */
34
+ import { AsyncResource } from 'node:async_hooks';
35
+ /**
36
+ * The escape hatch, created at module scope on purpose: a resource built while
37
+ * a transaction is running would capture that transaction, so this single
38
+ * instance is the only one the module ever needs. It carries no context of its
39
+ * own, so every job run through it starts from the process's root context.
40
+ */
41
+ const DETACHED = new AsyncResource('dsh-web:detached-work', { triggerAsyncId: 0 });
42
+ /**
43
+ * Run `work` outside the caller's AsyncLocalStorage context.
44
+ *
45
+ * The call itself is synchronous - this schedules, it does not defer. Work that
46
+ * must not block the caller should schedule its own `setImmediate` inside
47
+ * `work`, which then also starts clean because it inherits this resource's
48
+ * empty context rather than the caller's.
49
+ *
50
+ * @param work - the side effect to detach from the caller's async context.
51
+ * @returns whatever `work` returns.
52
+ */
53
+ export function runDetached(work) {
54
+ return DETACHED.runInAsyncScope(work);
55
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -29,6 +29,7 @@ import { withIdentityEncoding } from "./http.js";
29
29
  import { TunnelManager } from "./tunnel.js";
30
30
  import { PublicBaseKeeper } from "./public-base.js";
31
31
  import { mountOnce } from "./mount-once.js";
32
+ import { runDetached } from "./detached-work.js";
32
33
  import { REMOTE_CHANNEL_BOOT_SCRIPT } from "./remote-channel-boot.js";
33
34
  import { UUID_POLYFILL_SCRIPT } from "./uuid-polyfill.js";
34
35
  /** Stable cordis plugin name. */
@@ -543,14 +544,26 @@ function applyImpl(ctx, config) {
543
544
  // the re-assert at every boot keeps it in sync with both the toggle and
544
545
  // the flags.
545
546
  //
546
- // This work must leave the caller's async context first: a settings save
547
- // runs inside the Host's hmr.runExclusive, and cordis.patch.yml is exactly
548
- // the file the HMR config watcher refreshes from. A write issued on that
549
- // same context makes the watcher's refresh re-enter runExclusive, which
550
- // rejects with "HMR transactions cannot be nested" and fails the user's
551
- // save (issue #1751). setImmediate starts a fresh AsyncLocalStorage store,
552
- // so the assertion runs outside the transaction; it is idempotent, so the
553
- // coalesced follow-up sync() re-reading the same block writes nothing.
547
+ // cordis.patch.yml is exactly the file the Host's HMR config watcher
548
+ // refreshes from, and a settings save runs inside the Host's exclusive HMR
549
+ // transaction. Writing that file while the caller's transaction is open makes
550
+ // the watcher's refresh re-enter it, which the Host refuses with "HMR
551
+ // transactions cannot be nested" and which surfaces on the user's save
552
+ // (#1751, #1754).
553
+ //
554
+ // Deferring alone does NOT escape that context, and the earlier belt rested
555
+ // on a premise that measurement disproves: AsyncLocalStorage is propagated
556
+ // into setImmediate, into node:timers, and into AsyncResource scopes (a
557
+ // plain resource built while the transaction runs inherits it), so
558
+ // "setImmediate starts a fresh store" is false. What does start clean is an
559
+ // AsyncResource created at module scope, before any transaction existed -
560
+ // see detached-work.ts. scheduleLanBindWork runs the scheduling through it,
561
+ // so the write lands outside the caller's transaction.
562
+ //
563
+ // The assertion is also idempotent on its own: the write is guarded by the
564
+ // current-vs-desired comparison below, so once the profile carries the block
565
+ // a coalesced follow-up sync() finds nothing to do. That guard is a second
566
+ // belt, not the fix.
554
567
  const applyLanBindWork = (value) => {
555
568
  if (value.lanBind !== undefined) {
556
569
  const startup = ctx.get('webStartup');
@@ -597,12 +610,20 @@ function applyImpl(ctx, config) {
597
610
  // The LAN bind assertion never blocks the caller: one pending work item is
598
611
  // enough, and it always re-reads the config through `resolve()` at run time
599
612
  // so a toggle flipped twice before the tick lands on the last value.
613
+ //
614
+ // The scheduling itself runs through runDetached: the settings save that
615
+ // drives sync() holds the Host's exclusive HMR transaction in its async
616
+ // context, and a `setImmediate` scheduled directly from here would inherit
617
+ // that mark (measured on Node 24), so the write would still land inside the
618
+ // transaction the watcher then tries to re-enter. Scheduling from the
619
+ // module-scope resource starts the timer - and therefore the write - with no
620
+ // store at all.
600
621
  let lanBindWorkPending = false;
601
622
  const scheduleLanBindWork = () => {
602
623
  if (lanBindWorkPending)
603
624
  return;
604
625
  lanBindWorkPending = true;
605
- setImmediate(() => {
626
+ runDetached(() => setImmediate(() => {
606
627
  lanBindWorkPending = false;
607
628
  try {
608
629
  applyLanBindWork(resolve());
@@ -613,7 +634,7 @@ function applyImpl(ctx, config) {
613
634
  // reads the live block state back on its own poll.
614
635
  console.error(`remote-web-ui: the deferred lan-bind assertion failed: ${error instanceof Error ? error.message : String(error)}`);
615
636
  }
616
- });
637
+ }));
617
638
  };
618
639
  ctx.effect(() => () => { lanBindWorkPending = false; }, 'remote-web-ui: lan-bind work');
619
640
  // Apply the effective configuration to the running surfaces once per
@@ -30,7 +30,23 @@ export type LanBindHost = '0.0.0.0' | '127.0.0.1';
30
30
  * profiles directory.
31
31
  */
32
32
  export declare function profilePatchFile(profile: string, home?: string): string;
33
- /** Remove the managed block (and its markers) from patch content. */
33
+ /**
34
+ * Remove the managed block (its markers and the webserver row this plugin
35
+ * wrote) from patch content, keeping every other row.
36
+ *
37
+ * The markers travel with the file through writers that append rows through
38
+ * the YAML document API: the appended rows land at the end of the sequence,
39
+ * which is BEFORE a trailing END comment, so the region between the markers
40
+ * can hold rows this plugin never wrote. Deleting the whole region silently
41
+ * removed the user's model, plugin and remote-control rows on the next rewrite
42
+ * - the remote-control row carries the LAN toggle itself, so the feature reset
43
+ * to `undefined` and the phone was locked out again after a restart (DSH
44
+ * Desktop report, 2026-09-30). Only the managed item is removed here; every
45
+ * foreign row is kept in place and the fresh block is appended after them (the
46
+ * same-id row last wins).
47
+ * @param content - the patch file text.
48
+ * @returns the text with the markers and the managed row removed.
49
+ */
34
50
  export declare function stripManagedBlock(content: string): string;
35
51
  /**
36
52
  * Render the managed block for one bind state. The values are static: the
@@ -61,9 +77,10 @@ export declare function lanBindState(profile: string, home?: string): {
61
77
  * patch file is preserved; the block is rewritten atomically (unique temp
62
78
  * file + rename, so concurrent writers can never rename a half-written
63
79
  * file) with the original file's permissions preserved. A hand-truncated
64
- * unterminated block (BEGIN marker without END, which stripManagedBlock
65
- * cannot match) is truncated at its BEGIN marker first so the rewrite can
66
- * never stack a second webserver row onto the orphan.
80
+ * unterminated block (BEGIN marker without END) is consumed by
81
+ * stripManagedBlock as well - markers and managed row removed, foreign rows
82
+ * kept - so the rewrite can never stack a second webserver row onto the
83
+ * orphan.
67
84
  *
68
85
  * The written file is always exactly one valid YAML document: a base the
69
86
  * plugin manager left in flow style is re-emitted as a block sequence before
@@ -1 +1 @@
1
- {"version":3,"file":"lan-bind.d.ts","sourceRoot":"","sources":["../../src/lan-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,eAAO,MAAM,oBAAoB,mEAAmE,CAAA;AACpG,eAAO,MAAM,kBAAkB,+CAA+C,CAAA;AAE9E,iDAAiD;AACjD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,CAAA;AAEjD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAUlF;AAYD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKzD;AA0DD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAcpE;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACjC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAW3E;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAI/H;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,IAAI,CAa7G"}
1
+ {"version":3,"file":"lan-bind.d.ts","sourceRoot":"","sources":["../../src/lan-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,eAAO,MAAM,oBAAoB,mEAAmE,CAAA;AACpG,eAAO,MAAM,kBAAkB,+CAA+C,CAAA;AAE9E,iDAAiD;AACjD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,CAAA;AAEjD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAUlF;AAqED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoBzD;AA0DD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAcpE;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACjC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAkB3E;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAI/H;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,IAAI,CAW7G"}