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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -4496,7 +4496,7 @@ window.__ModuleLoader__.load({
4496
4496
  /** The building package's version, when the bundle carries it. */
4497
4497
  function bakedVersion() {
4498
4498
  try {
4499
- return "0.3.23";
4499
+ return "0.3.24";
4500
4500
  } catch {
4501
4501
  return;
4502
4502
  }
@@ -4580,6 +4580,12 @@ window.__ModuleLoader__.load({
4580
4580
  const COMPACT_CLASS = "dsh-remote-compact-picker";
4581
4581
  /** Body class while the header actions are seated in the tabs row. */
4582
4582
  const HEADER_SEATED_CLASS = "dsh-remote-header-seated";
4583
+ /**
4584
+ * Width the seated header actions paint over the tabs row, as a CSS variable
4585
+ * on <html>. The row caps its own width with it (see the v80 rules) instead of
4586
+ * reserving the space with padding.
4587
+ */
4588
+ const HEADER_RESERVE_VAR = "--dsh-remote-header-actions-reserve";
4583
4589
  /** Locale-dependent fast path for the official picker cells (zh/en). */
4584
4590
  const PICKER_CELL_PATTERN = {
4585
4591
  model: /模型|Model/,
@@ -4678,6 +4684,9 @@ window.__ModuleLoader__.load({
4678
4684
  "[class$=\"_composerSeat\"] [class$=\"_frame\"] [class$=\"_card\"]{max-width:none;width:100%}",
4679
4685
  `body.${HEADER_SEATED_CLASS} [class$="_header"] [class$="_titleCluster"] [class$="_headerActions"]{position:absolute;left:0;top:0;margin:0;display:flex;align-items:center;gap:6px;flex:none;z-index:2}`,
4680
4686
  "[class$=\"_header\"] [class$=\"_tabs\"] [class*=\"_tab\"]{font-size:12px;white-space:nowrap}",
4687
+ `body.${HEADER_SEATED_CLASS} [class$="_header"] [class$="_tabs"]{max-width:max(0px,calc(100% - var(${HEADER_RESERVE_VAR},0px)));overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-webkit-overflow-scrolling:touch;padding-bottom:2px;margin-bottom:-2px}`,
4688
+ `body.${HEADER_SEATED_CLASS} [class$="_header"] [class$="_tabs"]::-webkit-scrollbar{display:none}`,
4689
+ `body.${HEADER_SEATED_CLASS} [class$="_header"] [class$="_tabs"] > [class*="_tab"]{flex:0 0 auto}`,
4681
4690
  `body.${ACTIVE_CLASS} [class$=\"_detailsCol\"]{display:none !important}`,
4682
4691
  `body.${ACTIVE_CLASS} [data-dsh-plugin=\"ssh\"],`,
4683
4692
  `body.${ACTIVE_CLASS} [data-dsh-plugin=\"skill-explorer\"],`,
@@ -5096,7 +5105,9 @@ window.__ModuleLoader__.load({
5096
5105
  let dx = translate !== null ? parseFloat(translate[1] ?? "0") : 0;
5097
5106
  let dy = translate !== null ? parseFloat(translate[2] ?? "0") : 0;
5098
5107
  const actionsRect = actions.getBoundingClientRect();
5099
- const rightDiff = tabs.getBoundingClientRect().right - actionsRect.right;
5108
+ const headerRect = header.getBoundingClientRect();
5109
+ const headerStyle = getComputedStyle(header);
5110
+ const rightDiff = headerRect.right - (parseFloat(headerStyle.paddingRight) || 0) - (parseFloat(headerStyle.borderRightWidth) || 0) - actionsRect.right;
5100
5111
  if (Math.abs(rightDiff) >= .5) dx = Math.round((dx + rightDiff) * 10) / 10;
5101
5112
  const tabBottom = textBottom(tabBtn);
5102
5113
  if (tabBottom !== null) {
@@ -5113,10 +5124,12 @@ window.__ModuleLoader__.load({
5113
5124
  const next = `translate(${dx}px, ${dy}px)`;
5114
5125
  if (actions.style.transform !== next) actions.style.transform = next;
5115
5126
  const reserve = `${Math.ceil(actionsRect.width) + 8}px`;
5116
- if (tabs.style.paddingRight !== reserve) tabs.style.paddingRight = reserve;
5127
+ const rootStyle = document.documentElement.style;
5128
+ if (rootStyle.getPropertyValue(HEADER_RESERVE_VAR) !== reserve) rootStyle.setProperty(HEADER_RESERVE_VAR, reserve);
5117
5129
  }
5118
5130
  function unseatHeaderActions() {
5119
5131
  document.body.classList.remove(HEADER_SEATED_CLASS);
5132
+ document.documentElement.style.removeProperty(HEADER_RESERVE_VAR);
5120
5133
  const header = document.querySelector("[class$=\"_header\"]");
5121
5134
  const tabs = header !== null ? header.querySelector("[class$=\"_tabs\"]") : null;
5122
5135
  const actions = header !== null ? header.querySelector("[class$=\"_titleCluster\"] [class$=\"_headerActions\"]") : null;
@@ -5425,6 +5438,129 @@ window.__ModuleLoader__.load({
5425
5438
  };
5426
5439
  }
5427
5440
  //#endregion
5441
+ //#region src/client/plugin-card-seat.ts
5442
+ /**
5443
+ * Family plugin-card seat.
5444
+ *
5445
+ * A family plugin contributes its settings card to whichever plugin-card seat
5446
+ * the running host actually renders:
5447
+ *
5448
+ * - `web-ui.plugin.item` — the list seat declared by the dsh-web-settings
5449
+ * group section (this family's own first-level "Web UI plugins" section);
5450
+ * - `settings.plugin.item` — the official keyed seat of the harness's
5451
+ * `ui-settings-plugins` tab, keyed by the settings namespace the card edits.
5452
+ *
5453
+ * SEAT SELECTION IS NOT A DECLARATION PROBE. The official `ui-settings-plugins`
5454
+ * row belongs to the harness bundle and its `configurable` tab always declares
5455
+ * `settings.plugin.item` before any external plugin's `apply()` runs, so
5456
+ * "is the official seat declared?" answers yes even in the deployment whose
5457
+ * whole point is the family group. Choosing on that probe sends every family
5458
+ * card to the official Plugins tab and leaves the group's own section
5459
+ * permanently empty — the family of reports where the section renders its
5460
+ * heading and zero cards.
5461
+ *
5462
+ * The signal that actually distinguishes the two deployments is whether
5463
+ * dsh-web-settings is loaded: it is the package that owns the group section and
5464
+ * it publishes the `webUiSettings` service during `apply()`, which every
5465
+ * family plugin already reads for its settings scope. Group loaded -> the family
5466
+ * seat; group absent -> the official seat.
5467
+ *
5468
+ * The decision is re-evaluated on every `slots/changed` because the group may
5469
+ * apply after this plugin (the family aggregate orders it first, a profile that
5470
+ * installs the group separately need not): the initial contribution goes to the
5471
+ * official seat, then moves to the family seat the moment the group's section
5472
+ * registers. The entry is disposed before the replacement is registered, so a
5473
+ * card is never in two seats at once.
5474
+ *
5475
+ * The shared tree has no client-SDK dependency, so this module reads its
5476
+ * context through the structural shape below; callers pass the plugin's own
5477
+ * `ctx`.
5478
+ */
5479
+ /** The family list seat key. */
5480
+ const FAMILY_PLUGIN_CARD_SEAT = "web-ui.plugin.item";
5481
+ /** The official keyed plugin-card seat key. */
5482
+ const OFFICIAL_PLUGIN_CARD_SEAT = "settings.plugin.item";
5483
+ /** The service dsh-web-settings publishes while it is loaded. */
5484
+ const FAMILY_GROUP_SERVICE = "webUiSettings";
5485
+ /**
5486
+ * Whether the family group (dsh-web-settings) is loaded in this page. The
5487
+ * service is the group package's own contract, so the probe cannot be fooled
5488
+ * by a harness release that starts declaring the official seat differently.
5489
+ */
5490
+ function familyGroupLoaded(ctx) {
5491
+ const get = ctx.get;
5492
+ if (typeof get !== "function") return false;
5493
+ try {
5494
+ return get.call(ctx, FAMILY_GROUP_SERVICE) !== void 0;
5495
+ } catch {
5496
+ return false;
5497
+ }
5498
+ }
5499
+ /** Report a refused registration instead of leaving the user with no card. */
5500
+ function warnRefusedSeat(seat, error) {
5501
+ try {
5502
+ console.warn(`[dsh-web] plugin card registration into "${seat}" was refused; the card will not render`, error);
5503
+ } catch {}
5504
+ }
5505
+ /**
5506
+ * Contribute one family plugin card to the seat this host renders, following
5507
+ * the group if it loads later. The entry is disposed and re-registered on a
5508
+ * seat change, never duplicated.
5509
+ * @param ctx - client context (its slot registry decides the seat).
5510
+ * @param seat - the card contribution.
5511
+ */
5512
+ function installPluginCard(ctx, seat) {
5513
+ const slots = ctx.slots;
5514
+ const component = seat.component;
5515
+ const inject = seat.inject;
5516
+ let dispose;
5517
+ let current;
5518
+ /**
5519
+ * Re-entrancy latch. The registry emits a change event synchronously from
5520
+ * inside both `register` and the previous entry's disposer, so an unguarded
5521
+ * reconcile would re-enter itself mid-move and register the card twice into
5522
+ * the seat it is leaving ("already has an entry for key ...").
5523
+ */
5524
+ let reconciling = false;
5525
+ /** Reconcile the contribution with the currently live seat (no-op when unchanged). */
5526
+ const reconcile = () => {
5527
+ if (reconciling) return;
5528
+ const target = familyGroupLoaded(ctx) ? FAMILY_PLUGIN_CARD_SEAT : OFFICIAL_PLUGIN_CARD_SEAT;
5529
+ if (current === target) return;
5530
+ reconciling = true;
5531
+ const previous = dispose;
5532
+ dispose = void 0;
5533
+ current = void 0;
5534
+ previous?.();
5535
+ try {
5536
+ dispose = slots.register(target === "web-ui.plugin.item" ? {
5537
+ name: FAMILY_PLUGIN_CARD_SEAT,
5538
+ id: seat.id,
5539
+ ...seat.order === void 0 ? {} : { order: seat.order },
5540
+ ...seat.label === void 0 ? {} : { label: seat.label },
5541
+ locale: seat.locale,
5542
+ ...seat.inject === void 0 ? {} : { inject }
5543
+ } : {
5544
+ name: OFFICIAL_PLUGIN_CARD_SEAT,
5545
+ key: seat.namespace,
5546
+ locale: seat.locale,
5547
+ ...seat.inject === void 0 ? {} : { inject }
5548
+ }, component);
5549
+ current = target;
5550
+ } catch (error) {
5551
+ warnRefusedSeat(target, error);
5552
+ } finally {
5553
+ reconciling = false;
5554
+ }
5555
+ };
5556
+ if (typeof ctx.on === "function") try {
5557
+ ctx.on("slots/changed", () => {
5558
+ reconcile();
5559
+ });
5560
+ } catch {}
5561
+ reconcile();
5562
+ }
5563
+ //#endregion
5428
5564
  //#region src/client/index.ts
5429
5565
  /**
5430
5566
  * Remote control — browser half. Registers the `remote` dictionaries, the
@@ -5525,23 +5661,17 @@ window.__ModuleLoader__.load({
5525
5661
  };
5526
5662
  });
5527
5663
  const remoteSettings = new RemoteSettingsCardController(settingsScope);
5528
- ctx.slots.inject("web-ui.plugin.item", () => {
5529
- try {
5530
- const unregister = ctx.slots.register({
5531
- name: "web-ui.plugin.item",
5532
- id: "remote-web-ui",
5533
- order: 90,
5534
- locale: NS,
5535
- inject: () => remoteSettings.inject()
5536
- }, RemoteSettingsCard);
5537
- return () => {
5538
- remoteSettings.dispose();
5539
- unregister();
5540
- };
5541
- } catch {
5542
- return () => {};
5543
- }
5664
+ installPluginCard(ctx, {
5665
+ namespace: REMOTE_WEB_UI_NS,
5666
+ id: "remote-web-ui",
5667
+ order: 90,
5668
+ locale: NS,
5669
+ inject: () => remoteSettings.inject(),
5670
+ component: RemoteSettingsCard
5544
5671
  });
5672
+ ctx.effect(() => () => {
5673
+ remoteSettings.dispose();
5674
+ }, "remote-web-ui: settings card");
5545
5675
  let disposeRuntime;
5546
5676
  const syncRuntime = () => {
5547
5677
  if (enabled() && disposeRuntime === void 0) disposeRuntime = ctx.effect(() => {