dsh-plugin-shop 0.8.4-beta.0 → 0.8.4-beta.1

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
@@ -4515,7 +4515,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4515
4515
  },
4516
4516
  sourceLocation: {
4517
4517
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4518
- "line": 1134,
4518
+ "line": 1253,
4519
4519
  "column": 9
4520
4520
  }
4521
4521
  },
@@ -4534,7 +4534,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4534
4534
  },
4535
4535
  sourceLocation: {
4536
4536
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4537
- "line": 1494,
4537
+ "line": 1720,
4538
4538
  "column": 9
4539
4539
  }
4540
4540
  },
@@ -4553,7 +4553,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4553
4553
  },
4554
4554
  sourceLocation: {
4555
4555
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4556
- "line": 1485,
4556
+ "line": 1711,
4557
4557
  "column": 9
4558
4558
  }
4559
4559
  },
@@ -4583,7 +4583,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4583
4583
  },
4584
4584
  sourceLocation: {
4585
4585
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4586
- "line": 1260,
4586
+ "line": 1379,
4587
4587
  "column": 9
4588
4588
  }
4589
4589
  },
@@ -4612,7 +4612,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4612
4612
  },
4613
4613
  sourceLocation: {
4614
4614
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4615
- "line": 1461,
4615
+ "line": 1687,
4616
4616
  "column": 3
4617
4617
  }
4618
4618
  },
@@ -4631,7 +4631,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4631
4631
  },
4632
4632
  sourceLocation: {
4633
4633
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4634
- "line": 1680,
4634
+ "line": 1916,
4635
4635
  "column": 9
4636
4636
  }
4637
4637
  },
@@ -4660,7 +4660,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4660
4660
  },
4661
4661
  sourceLocation: {
4662
4662
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4663
- "line": 887,
4663
+ "line": 924,
4664
4664
  "column": 9
4665
4665
  }
4666
4666
  },
@@ -4690,7 +4690,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4690
4690
  },
4691
4691
  sourceLocation: {
4692
4692
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4693
- "line": 1601,
4693
+ "line": 1827,
4694
4694
  "column": 9
4695
4695
  }
4696
4696
  },
@@ -4719,7 +4719,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4719
4719
  },
4720
4720
  sourceLocation: {
4721
4721
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4722
- "line": 1777,
4722
+ "line": 2020,
4723
4723
  "column": 9
4724
4724
  }
4725
4725
  },
@@ -4738,7 +4738,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4738
4738
  },
4739
4739
  sourceLocation: {
4740
4740
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4741
- "line": 1735,
4741
+ "line": 1978,
4742
4742
  "column": 9
4743
4743
  }
4744
4744
  }
@@ -6630,7 +6630,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6630
6630
  kind: "done",
6631
6631
  activation: status.activation ?? "restart",
6632
6632
  log: status.log,
6633
- ...status.restartReason !== void 0 ? { restartReason: status.restartReason } : {}
6633
+ ...status.restartReason !== void 0 ? { restartReason: status.restartReason } : {},
6634
+ ...status.detail !== void 0 ? { detail: status.detail } : {}
6634
6635
  };
6635
6636
  return {
6636
6637
  kind: "failed",
@@ -6984,7 +6985,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6984
6985
  github: "GitHub",
6985
6986
  enabledSwitch: "启用",
6986
6987
  toggleFailed: "设置失败,请重试。",
6987
- hotApplyNote: "已生效,无需重启"
6988
+ hotApplyNote: "已生效,无需重启",
6989
+ toggleRestartNote: "已保存,重启 dsh 后生效"
6988
6990
  };
6989
6991
  /** English dictionary checked against the Chinese key set. */
6990
6992
  const en = {
@@ -7100,7 +7102,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7100
7102
  github: "GitHub",
7101
7103
  enabledSwitch: "Enable",
7102
7104
  toggleFailed: "Failed to update. Please retry.",
7103
- hotApplyNote: "applied without a restart"
7105
+ hotApplyNote: "applied without a restart",
7106
+ toggleRestartNote: "saved; it takes effect after dsh restarts"
7104
7107
  };
7105
7108
  //#endregion
7106
7109
  //#region src/client/module-table.ts
@@ -7291,40 +7294,23 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7291
7294
  }
7292
7295
  //#endregion
7293
7296
  //#region src/client/restart-monitor.ts
7294
- /**
7295
- * The restart handoff's browser half (design §8), held by the page rather
7296
- * than by the tab that confirmed the restart.
7297
- *
7298
- * It lived in a RestartPanel effect until 2026-09-27, and a self-update
7299
- * killed it. dsh's client HMR (`@deepseek-ai/dsh-client-hmr`, mounted by the
7300
- * web bundle unconditionally) stat-polls every client bundle and hot-swaps
7301
- * one whose file changed; a self-update rewrites this package's own
7302
- * `lib/client.js`, so the swap disposes the tab's fiber and mounts a fresh
7303
- * instance of a freshly imported module. The effect's cleanup then stopped
7304
- * the probe, and once the restart finished nothing reloaded the page: it sat
7305
- * on the old process's answers while dsh's socket quietly reconnected to the
7306
- * new one. Measured on dsh 0.1.5-rc.3 updating 0.8.2 to 0.8.3; with the
7307
- * `/plugins/events` channel blocked the same flow reloaded.
7308
- *
7309
- * So the state sits on `globalThis` under a registry symbol — the one thing a
7310
- * swapped-in module instance shares with the one it replaced — and the probe
7311
- * loop is plain timers owned by no fiber. Any tab reads the state, so the
7312
- * replacement renders the restart under way instead of offering another.
7313
- */
7314
7297
  /** Registry symbol, so every module instance on the page finds the same store. */
7315
7298
  const STORE_KEY = Symbol.for("dsh-plugin-shop.restart-monitor");
7316
7299
  function monitorStore() {
7317
7300
  const page = globalThis;
7318
- let store = page[STORE_KEY];
7319
- if (store === void 0) {
7320
- store = {
7301
+ let stored = page[STORE_KEY];
7302
+ if (stored === void 0) {
7303
+ stored = {
7321
7304
  state: null,
7322
7305
  listeners: /* @__PURE__ */ new Set(),
7323
7306
  stop: null
7324
7307
  };
7325
- page[STORE_KEY] = store;
7308
+ page[STORE_KEY] = stored;
7326
7309
  }
7327
- return store;
7310
+ return Object.assign(stored, {
7311
+ carriers: stored.carriers ?? [],
7312
+ attempt: stored.attempt ?? null
7313
+ });
7328
7314
  }
7329
7315
  function settle(store, state) {
7330
7316
  store.state = state;
@@ -7410,6 +7396,114 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7410
7396
  logFile
7411
7397
  });
7412
7398
  }
7399
+ /**
7400
+ * Register one module instance's path to the host; the client half's apply
7401
+ * does, with the `restart` it hands the tab. The returned function withdraws
7402
+ * it, and has to run BEFORE the mount it rides is disposed: a request still in
7403
+ * flight on it is then asked again through the newest live path — at once if
7404
+ * one is registered, or as soon as one is — rather than waiting on a call the
7405
+ * dispose is about to abort.
7406
+ */
7407
+ function registerRestartCarrier(restart) {
7408
+ const store = monitorStore();
7409
+ const carrier = {
7410
+ restart,
7411
+ live: true
7412
+ };
7413
+ store.carriers.push(carrier);
7414
+ const attempt = store.attempt;
7415
+ if (attempt !== null && !attempt.carrier.live) ask(store, attempt, carrier);
7416
+ return () => {
7417
+ if (!carrier.live) return;
7418
+ carrier.live = false;
7419
+ const current = monitorStore();
7420
+ current.carriers = current.carriers.filter((each) => each !== carrier);
7421
+ const orphaned = current.attempt;
7422
+ const next = current.carriers.at(-1);
7423
+ if (orphaned !== null && orphaned.carrier === carrier && next !== void 0) ask(current, orphaned, next);
7424
+ };
7425
+ }
7426
+ /**
7427
+ * Ask the host to restart, for the page (design §8). The request is the
7428
+ * page's from the confirm on, not from the answer: every offer renders it as
7429
+ * under way, and a second press joins it rather than asking twice. It rides
7430
+ * the newest path a module instance registered, or — where no client half
7431
+ * registered one — the pressing tab's own `restart`.
7432
+ *
7433
+ * Resolves, never rejects, with what the press should show. A commit starts
7434
+ * the monitor and is `started`. A refusal and an unreachable host leave
7435
+ * nothing under way. A path withdrawn mid-call decides nothing but a commit;
7436
+ * the ask on the path that replaced it decides the rest, and that ask
7437
+ * finding no host is followed as a restart, since the first ask may have
7438
+ * committed and taken its host down with it.
7439
+ */
7440
+ function requestRestart(options) {
7441
+ const store = monitorStore();
7442
+ if (store.state?.kind === "restarting") return Promise.resolve({ kind: "started" });
7443
+ return new Promise((resolve) => {
7444
+ const pending = store.attempt;
7445
+ if (pending !== null) {
7446
+ pending.waiters.push(resolve);
7447
+ return;
7448
+ }
7449
+ const carrier = store.carriers.at(-1) ?? {
7450
+ restart: options.restart,
7451
+ live: true
7452
+ };
7453
+ const attempt = {
7454
+ carrier,
7455
+ reload: options.reload,
7456
+ reissued: false,
7457
+ waiters: [resolve]
7458
+ };
7459
+ store.attempt = attempt;
7460
+ settle(store, { kind: "requesting" });
7461
+ ask(store, attempt, carrier);
7462
+ });
7463
+ }
7464
+ /** Carry the request on one path. Its answer decides only while the request
7465
+ * is still the page's and that path still carries it — a commit excepted. */
7466
+ function ask(store, attempt, carrier) {
7467
+ if (attempt.carrier !== carrier) {
7468
+ attempt.carrier = carrier;
7469
+ attempt.reissued = true;
7470
+ }
7471
+ (async () => carrier.restart())().then((result) => {
7472
+ if (store.attempt !== attempt) return;
7473
+ if (result.ok) {
7474
+ finish(store, attempt, { kind: "started" });
7475
+ startRestartMonitor({
7476
+ reload: attempt.reload,
7477
+ ...typeof result.logFile === "string" ? { logFile: result.logFile } : {}
7478
+ });
7479
+ return;
7480
+ }
7481
+ if (superseded(attempt, carrier)) return;
7482
+ finish(store, attempt, {
7483
+ kind: "refused",
7484
+ detail: result.detail
7485
+ });
7486
+ settle(store, null);
7487
+ }, () => {
7488
+ if (store.attempt !== attempt || superseded(attempt, carrier)) return;
7489
+ if (attempt.reissued) {
7490
+ finish(store, attempt, { kind: "started" });
7491
+ startRestartMonitor({ reload: attempt.reload });
7492
+ return;
7493
+ }
7494
+ finish(store, attempt, { kind: "unreachable" });
7495
+ settle(store, null);
7496
+ });
7497
+ }
7498
+ /** Whether a path's answer is one the request has moved past: the path was
7499
+ * withdrawn, so the dispose aborted it or the ask that replaced it decides. */
7500
+ function superseded(attempt, carrier) {
7501
+ return !carrier.live || attempt.carrier !== carrier;
7502
+ }
7503
+ function finish(store, attempt, outcome) {
7504
+ store.attempt = null;
7505
+ for (const waiter of attempt.waiters) waiter(outcome);
7506
+ }
7413
7507
  //#endregion
7414
7508
  //#region src/client/useFlows.ts
7415
7509
  /**
@@ -7690,121 +7784,121 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7690
7784
  };
7691
7785
  }
7692
7786
  //#endregion
7693
- //#region \0dsh-plugin-shop-css:c4028823
7694
- const css = "/* Shop tab styles — a market shelf inside the settings surface. The visual\n * language is the dsh theme's alias tokens ONLY (the four tokens the previous\n * styles referenced that do not exist — bg-layer-3, interactive-bg-hover,\n * state-error, state-success — are corrected to bg-layer-2 and the\n * state-*-primary set). The signature is the category spine: one HUE per\n * category (seven fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so seven brand opacities read as seven\n * shades of gray), with the category name as the cover label in the same hue. Class names are mapped to content-derived names at bundle time\n * (tsdown.sc389ac_client.sc41672_config.s8eefa1_ts) and stubbed in tests; only the keys are relied\n * on.\n *\n * One token rule the theme forces: the background LAYERS do not carry\n * contrast. In the light theme bg-base, bg-layer-1, bg-layer-2 and\n * bg-layer-3 all resolve to the same white; only the dark theme spreads them\n * (950/875/850/800). So a layer-2 fill on a layer-1 ground is invisible for\n * half of all users, and anything whose fill is its ONLY affordance — the\n * loading skeleton, the borderless capability chips — derives its fill from\n * label-primary instead, which inverts with the theme by construction.\n * Elements that also carry a border or text may keep a layer fill.\n * `css-tokens.sc389ac_client.sfe36ad_spec.s8eefa1_ts` pins this. */\n\n.s688753_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n /* The shelf never scrolls sideways: a card too wide for its track wraps\n * its badges rather than widening the panel.\n *\n * Clipped on BOTH axes, with a margin, because `.s9f28cb_searchInput` sits at\n * exactly (0, 0) of this box — it is the first child of `.sbc2e10_toolbar`, itself\n * the first child here, and this rule carries no padding — so its focus\n * ring paints ENTIRELY outside the panel.\n *\n * Both axes is the load-bearing part: `overflow-clip-margin` is inert\n * unless the element clips on both. Clipping x alone left the margin doing\n * nothing — measured against the live app on 0.7.1, the ring reached 0 of\n * its 3px on the left at every device pixel ratio (1, 1.25, 1.5, 2), while\n * keeping the full 3px on the right, where the 280px input stops short of\n * the panel and never meets the clip edge. Raising the margin to 6px\n * changed nothing; clipping both axes with the SAME 3px restored the whole\n * ring. A test that reads declarations cannot see that difference, which is\n * why the first attempt at this shipped broken.\n *\n * `clip` is not a scroll container, so this does not bring back the\n * accidental scrolling `hidden` caused by coercing the other axis to\n * `auto`. Nothing is lost vertically: the panel's scrollHeight equals its\n * height and no descendant paints outside the clip rect (measured).\n * `overflow-x: hidden` stays as the fallback for engines without `clip`,\n * where the ring is clipped as it was before, never worse. */\n overflow-x: hidden;\n overflow: clip;\n overflow-clip-margin: 3px;\n}\n\n.s7237a9_stateLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* Visually hidden but screen-reader readable (the loading copy while the\n * skeleton stands in visually). */\n.s601d42_srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The loading shelf: ghost cards with the same geometry as the real grid, so\n * the swap to content is a same-shape handoff instead of a jump. */\n.sa26d1f_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.sceffd1_skeletonCard {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* The shimmer sweep: one gradient band travels across each ghost card while\n * the bars breathe, so the loading state reads as \"the shelf is scanning\". */\n.sceffd1_skeletonCard::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 100deg,\n transparent 25%,\n color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent) 50%,\n transparent 75%\n );\n transform: translateX(-100%);\n animation: dshShopSkeletonSweep 1.4s ease-in-out infinite;\n pointer-events: none;\n}\n\n@keyframes dshShopSkeletonSweep {\n to { transform: translateX(100%); }\n}\n\n.s6f1efd_skeletonName {\n width: 55%;\n height: 14px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s4db475_skeletonSummary {\n width: 92%;\n height: 12px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.se9c3a9_skeletonSummaryShort {\n width: 68%;\n height: 12px;\n margin: 6px 12px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n/* The ghost of the action line, right where the real buttons land. */\n.s3cbb84_skeletonActions {\n display: block;\n width: 84px;\n height: 24px;\n margin: 0 12px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sceffd1_skeletonCard span {\n animation: dshShopSkeletonPulse 1.6s ease-in-out infinite;\n}\n\n@keyframes dshShopSkeletonPulse {\n 0%, 100% { opacity: 0.5; }\n 50% { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sceffd1_skeletonCard span { animation: none; opacity: 0.75; }\n .sceffd1_skeletonCard::after { content: none; }\n}\n\n.se4623a_actionButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.se4623a_actionButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.se4623a_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sbc2e10_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.sb9d854_toolbarRight {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n/* The shop's own version, right of the search box: `v0.4.4`, plus the\n * update button when the version check found a newer release. */\n.s17e39d_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.sff4480_versionText {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf22160_updateSelfButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.sf22160_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.sf22160_updateSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The on-demand re-check next to the version number: a quiet pill; the\n * border and text carry it in the light theme where bg layers are white. */\n.s1abadc_checkUpdateButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s1abadc_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s1abadc_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s1abadc_checkUpdateButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The project's GitHub mark, right of the version row: a quiet icon link.\n * The octocat carries itself — color shift on hover, no text, no border. */\n.se9af4e_githubLink {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n border-radius: 6px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.se9af4e_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.se9af4e_githubLink:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The self-update's expanded states (running log / done + restart /\n * failure detail) sit under the toolbar, above the category bar. */\n.s11efce_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.s9f28cb_searchInput {\n width: min(280px, 100%);\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n.s9f28cb_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.scfe278_staleBadge {\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 12px;\n}\n\n.sfd2800_categoryBar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.saa7982_categoryButton {\n /* The hue this button carries when selected. `All` and `Installed` select by\n * something other than a category and keep the brand token; the six category\n * buttons override it from the shared hue table below, so a pressed Tool tab\n * turns the same blue as a tool card's spine. */\n --category-hue: var(--dsw-alias-brand-primary);\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n/* Blend text with the theme foreground: darker on light backgrounds, lighter\n * on dark ones. The unblended category hue belongs on the border and fill. */\n.saa7982_categoryButton:hover {\n border-color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-border-l2));\n color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-label-primary) 55%);\n}\n\n/* Selected. Written `.saa7982_categoryButton.s47a604_categoryButtonOn` — TWO classes — so it\n * matches `.saa7982_categoryButton:hover`'s specificity and, coming after it, wins.\n * As a bare `.s47a604_categoryButtonOn` it lost both `border-color` and `color` to the\n * hover rule for as long as the pointer stayed on the tab that had just been\n * clicked, which is precisely when a reader looks for confirmation.\n *\n * EVERY declaration here is a colour or a shadow: a pressed tab must occupy\n * exactly the same box as an unpressed one, so the bar does not reflow — and\n * with ~9 pills on a wrapping row, one that grows can push the rest onto\n * another line and move the tab out from under the pointer that just clicked\n * it. That is what `font-weight: 600` did (bold metrics are wider than\n * regular), and it is why nothing may be added here that resolves to a length:\n * no padding, no border-width, no font-size, no letter-spacing.\n * `css-tokens.sc389ac_client.sfe36ad_spec.s8eefa1_ts` fails the build on one.\n *\n * `box-shadow` is the exception that earns its place: it paints outside the\n * layout box, so an inset ring doubles the border's weight at zero cost to the\n * metrics. It is what `font-weight: 600` used to be — a second, thickness-\n * based signal that hover does not also produce — and the `other` tab needs it\n * most: its hue IS the neutral gray this file uses for \"no category\", so on\n * the light theme hue alone barely separates pressed from unpressed. */\n.saa7982_categoryButton.s47a604_categoryButtonOn {\n border-color: var(--category-hue);\n background: color-mix(in srgb, var(--category-hue) 14%, transparent);\n color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-label-primary) 55%);\n box-shadow: inset 0 0 0 1px var(--category-hue);\n}\n\n/* Forced colours replace every colour above with the system palette, which\n * would leave the three colour declarations saying nothing and the pressed tab\n * indistinguishable from its neighbours. `box-shadow` is dropped in this mode\n * too, so the ring has to come back as an outline. */\n@media (forced-colors: active) {\n .saa7982_categoryButton.s47a604_categoryButtonOn {\n outline: 2px solid ButtonText;\n outline-offset: -4px;\n }\n}\n\n.saa7982_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The incompatible filter, at the right edge of the category bar: `margin-left:\n * auto` pushes it past the pills, and the bar wraps, so on a narrow panel it\n * drops to its own line rather than squeezing them. It is a MODIFIER, not a\n * ninth category — the categories select what to show and this one subtracts\n * from whatever they selected — so it takes the error hue it filters on rather\n * than joining the hue table.\n *\n * And it is a SWITCH, which is the difference the shape now carries. It used\n * to wear `.saa7982_categoryButton`: same pill, same row, same size as the eight tabs,\n * differing only in being red. That reads as a ninth tab, and it is not one —\n * the tabs are choose-one and this is on-or-off. So it drops the pill chrome\n * entirely (no border, no fill, nothing to compete with the row it sits\n * beside) and puts the state in a track, the same one every installed card\n * carries. Nothing here restates the switch geometry; `--switch-hue` is the\n * whole of what this control says about it, and it reaches the label too, so\n * one declaration keeps the word and the track the same colour. */\n.s6000bb_incompatibleFilter {\n --switch-hue: var(--dsw-alias-state-error-primary);\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Matches the pills' vertical padding so the bar keeps one line box whether\n * this control wraps onto a row of its own or shares theirs. */\n padding: 3px 0;\n border: 0;\n background: none;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n/* Hover lifts the word to full contrast and warms the track's border. Scoped\n * to the OFF track: `.s6000bb_incompatibleFilter:hover .s21d1d6_switch` outranks `.sfaf305_switchOn`\n * by two classes, so unscoped it would strip the hue from the border at\n * exactly the moment a reader has the pointer on it to check the state. */\n.s6000bb_incompatibleFilter:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n.s6000bb_incompatibleFilter:hover .s21d1d6_switch:not(.sfaf305_switchOn) {\n border-color: color-mix(in srgb, var(--switch-hue) 45%, var(--dsw-alias-border-l2));\n}\n\n/* On. Written with both classes for the reason `.saa7982_categoryButton.s47a604_categoryButtonOn`\n * is: it has to match `:hover`'s specificity and come after it, or the colour\n * reverts under a pointer that has not moved since the click. */\n.s6000bb_incompatibleFilter.s9fb743_incompatibleFilterOn {\n color: color-mix(in srgb, var(--switch-hue) 45%, var(--dsw-alias-label-primary) 55%);\n}\n\n/* Off here means \"nothing is being taken away\" — the resting state — so the\n * knob steps back to the secondary label. The enable switch keeps the full\n * strength dot, where off means \"this plugin is disabled\" and is worth\n * noticing. In this bar it would be the heaviest mark in a row of hairline\n * pills, and on the light theme it is the WHOLE control: bg-layer-2 is the\n * same white as the panel there, so an off track is a hairline outline with a\n * near-black dot sitting in it. */\n.s6000bb_incompatibleFilter .s21d1d6_switch:not(.sfaf305_switchOn) .s3e9566_switchKnob {\n background: var(--dsw-alias-label-secondary);\n}\n\n/* The ring has no border to trace, so it takes an offset and the pill radius\n * of the tabs it sits beside rather than hugging the text. */\n.s6000bb_incompatibleFilter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 999px;\n}\n\n.s3627e7_updatableHeading {\n margin: 2px 0 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-primary);\n}\n\n.s879949_catalogStatsRow {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 6px 0 0;\n}\n\n.s7f1200_catalogStats {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* Sits beside the 12px stats caption, so it is quieter than `.se4623a_actionButton`:\n * this is an affordance, not a call to action — the catalog reloads on its\n * own at mount and the shelf is already usable without it. Named\n * `catalogRefresh*` (not `reload*`) so it cannot collide with the §4 page\n * reload offer below, which reloads the whole tab rather than refetching\n * the catalog data. */\n.s7f50e0_catalogRefreshButton {\n padding: 1px 9px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 17px;\n cursor: pointer;\n}\n\n.s7f50e0_catalogRefreshButton:hover:not(:disabled) {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s7f50e0_catalogRefreshButton:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.s7f50e0_catalogRefreshButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s284f4c_catalogRefreshFailed {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s894d35_emptyLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* The shelf: a responsive grid of cards. */\n/* Single-line rows: one full-width column, one plugin per line. */\n.s73be4c_cards {\n display: grid;\n grid-template-columns: 1fr;\n max-width: 100%;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s73be4c_cards { animation: none; }\n}\n\n.s7ce0b5_card {\n position: relative;\n display: flex;\n flex-direction: column;\n /* A grid item's min-width is auto by default: its content would widen the\n * 1fr track past the panel and overflow horizontally. */\n min-width: 0;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 140px;\n}\n\n/* The sentinel that triggers the next shelf batch: zero visual footprint. */\n.se9f199_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s571a2d_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s7ce0b5_card:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l1));\n transform: translateY(-1px);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s7ce0b5_card { transition: none; }\n .s7ce0b5_card:hover { transform: none; }\n}\n\n/* The signature: a spine and cover in the category's own hue. The hues are\n * fixed mid-bright colors that read on both themes; `other` deliberately\n * stays a neutral gray so the fallback category does not compete. */\n.s3afd93_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--category-hue, #8B8E96);\n}\n\n/* The hue table, keyed by `data-category` and read by BOTH surfaces that\n * speak in category colour: a card's spine and badge, and the category bar's\n * own tab. One table, so a tab can never disagree with the cards it filters to\n * — which is the whole point of colouring the tab. `.saa7982_categoryButton` sets the\n * brand fallback for `All`/`Installed`; these attribute selectors are one\n * specificity step above it and win wherever the attribute is present. */\n.s7ce0b5_card[data-category='tool'], .saa7982_categoryButton[data-category='tool'] { --category-hue: #4C8DFF; }\n.s7ce0b5_card[data-category='provider'], .saa7982_categoryButton[data-category='provider'] { --category-hue: #A78BFA; }\n.s7ce0b5_card[data-category='ui'], .saa7982_categoryButton[data-category='ui'] { --category-hue: #2DD4BF; }\n.s7ce0b5_card[data-category='workflow'], .saa7982_categoryButton[data-category='workflow'] { --category-hue: #F59E0B; }\n.s7ce0b5_card[data-category='integration'], .saa7982_categoryButton[data-category='integration'] { --category-hue: #34D399; }\n.s7ce0b5_card[data-category='theme'], .saa7982_categoryButton[data-category='theme'] { --category-hue: #F472B6; }\n.s7ce0b5_card[data-category='other'], .saa7982_categoryButton[data-category='other'] { --category-hue: #8B8E96; }\n\n/* The category sits in the badge row as a quiet chip in the category's own\n * hue — the border carries it where the light theme's white layers cannot\n * (the same lesson as the check button). */\n.sda4302_categoryBadge {\n padding: 1px 7px;\n border: 1px solid color-mix(in srgb, var(--category-hue, #8B8E96) 45%, transparent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--category-hue, #8B8E96);\n white-space: nowrap;\n}\n\n.sf9a029_entryHeader {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px 2px;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.sf9a029_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name fills the row and truncates rather than wraps; the badges stay\n * intact beside it. */\n.scde6f0_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n flex: 1 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-primary);\n}\n\n.s5b12f5_badges {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* The action line: buttons side by side; an active install/uninstall flow\n * (gate, log, notices) takes the full width below them. */\n.sbfb66e_cardActions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 10px;\n}\n\n.sbfb66e_cardActions > .sbf0f49_gate,\n.sbfb66e_cardActions > .se45c46_installPanel,\n.sbfb66e_cardActions > .s6a7b50_installedActions {\n flex-basis: 100%;\n}\n\n/* The entry's version, always visible on the card (the expanded detail\n * repeats it in the version row) — the same quiet mono as the toolbar. */\n.sa3f608_cardVersion {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The octocat marking a github-sourced entry, beside its short commit. */\n.s908192_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sda8b40_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.sda8b40_tierBadge[data-tier='verified'] {\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n}\n\n.sda8b40_tierBadge[data-tier='verified-stale'] {\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.sda8b40_tierBadge[data-tier='community'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.s6eb4e5_incompatibleBadge {\n flex-shrink: 0;\n padding: 3px 9px;\n border-radius: 999px;\n font-size: 12px;\n white-space: nowrap;\n color: var(--dsw-alias-state-error-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 40%, transparent);\n}\n\n.s3e85ad_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sef80ea_chevron, .sc0d8c8_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.sc0d8c8_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .sef80ea_chevron, .sc0d8c8_chevronOpen { transition: none; }\n}\n\n.safe8c4_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sbd2517_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.s15225a_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Expanded cards show the full summary: the clamp is lifted, never raised —\n * a higher number would still truncate the author's text, just later. */\n.s273e04_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.s20dbc5_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n/* `white-space: pre-line` so the newline in the copy is the copy's own: the\n dictionary decides where the line breaks, and a translator changes it\n without touching the component. Collapses runs of spaces like `normal`, so\n only the explicit \\n survives. */\n.s927eb9_incompatibleDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre-line;\n color: var(--dsw-alias-state-error-primary);\n}\n\n/* The way out of dsh's own refusal: the lead-in, then the exemption command\n * on its own line. */\n.sa35ed7_remedy {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s2fcacb_remedyLead {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n\n/* The exemption command, bound for a terminal unchanged: one click selects\n * all of it, and it wraps anywhere rather than widening the card. A border,\n * not a fill, marks the box: the bg layers are one white in the light theme. */\n.se87ea1_allowCommand {\n display: block;\n padding: 6px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n white-space: pre-wrap;\n word-break: break-all;\n user-select: all;\n color: var(--dsw-alias-label-primary);\n}\n\n/* A refused install on a surface that does not state its blockers — the\n * outdated row — writes them out on their own full-width line under the\n * disabled button, the command that clears dsh's refusal included. */\n.sbccf5d_refusal {\n display: flex;\n flex-direction: column;\n gap: 6px;\n flex-basis: 100%;\n}\n\n.sca4074_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s3ed23b_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sbd7053_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.sbd7053_capabilities li {\n padding: 1px 6px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.se69ccd_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.s71ed47_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.s488064_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.s488064_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.s488064_detailRow dd {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n overflow-wrap: anywhere;\n text-align: right;\n color: var(--dsw-alias-label-primary);\n}\n\n.s488064_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.s488064_detailRow a:hover {\n text-decoration: underline;\n}\n\n.s488064_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* What the entry IS rather than what you can do with it — how big it is and\n * who published it — at the right edge of the action row. `margin-left: auto`\n * is what pushes it there, and it lives on this wrapper rather than on either\n * label so the group stays flush right when only one of the two has a value —\n * a packument that named no account has no author, and an entry the registry\n * could not measure honestly has no size. Rarer than it was (every live entry\n * now carries a size, github included), but the wrapper is not what makes it\n * rare: without one, an EMPTY meta group still costs 8px, because\n * `.sbfb66e_cardActions` is a flex row with `gap: 8px` and a zero-width item is still\n * an item. The row is a wrapping flex, so an active install flow (which claims\n * the full width) drops the whole group to the next line rather than squeezing\n * it. */\n.sd941dc_cardMeta {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-left: auto;\n min-width: 0;\n}\n\n/* The on-disk size, left of the author. Mono and tabular so the digits of one\n * figure line up with the digits of another WHEREVER the two land: `.sd941dc_cardMeta`\n * is flush right and the author sits outside the size, so a size's x position\n * moves with the author's width and is different again on the many entries\n * that carry no author at all. There is no column to align to, which is why\n * the reason given here is per-figure legibility rather than the column the\n * first version of this comment claimed. Secondary colour, because it\n * qualifies the entry rather than announcing it.\n *\n * `font-variant-numeric` is safe HERE and would not be on a pill: this element\n * has no state rules, so nothing it does can reflow a wrapping row. */\n.sff4055_size {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n letter-spacing: 0.02em;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The publishing account, at the outer end of `.sd941dc_cardMeta` on the action row —\n * NOT beside the npm link in the expanded detail, which is where it used to\n * live and what this comment described until the accurate half was deleted\n * with the `margin-left: auto` that moved to `.sd941dc_cardMeta`. There is no middot:\n * `.sd941dc_cardMeta`'s 8px gap separates it from the size.\n *\n * Secondary label colour carries the distinction — never a background fill,\n * which is invisible in the light theme where every bg-layer token is the\n * same white. */\n.s74b808_author {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.s5a1b52_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n/* Install flow. */\n.se45c46_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.s7bcc5e_installButton, .sb977c6_confirmButton, .s3cfd9a_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.s7bcc5e_installButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.s7bcc5e_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sb977c6_confirmButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.sb977c6_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s3cfd9a_cancelButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n\n.s3cfd9a_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s7bcc5e_installButton:focus-visible, .sb977c6_confirmButton:focus-visible, .s3cfd9a_cancelButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The shelf card of a plugin that is installed and current carries this\n * non-interactive label instead of an install button. Border + text do the\n * work in the light theme, where every bg-layer token resolves to white. */\n.sfaf388_installedLabel {\n align-self: flex-start;\n margin: 0;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* An installed card's controls: the installed label or the update button,\n * plus the uninstall button. The install panel's running/failed states are\n * column layouts and sit as one item of this row. */\n.s6a7b50_installedActions {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Uninstall revokes privilege; the quiet outline keeps it from competing\n * with the primary action, and the error tint says \"removes\". */\n.sb0c03c_uninstallButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 55%, transparent);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.sb0c03c_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.sb0c03c_uninstallButton:focus-visible {\n outline: 2px solid var(--dsw-alias-state-error-primary);\n outline-offset: 1px;\n}\n\n/* The restart dsh button on a done install: quiet outline, same rank as\n * the other secondary actions. */\n.se1e373_restartSelfButton {\n /* The version row's third state. Geometry matches .s1abadc_checkUpdateButton and\n * .sf22160_updateSelfButton exactly — the row is one evolving control and a\n * different pill would read as a different kind of thing. It takes the\n * emphasised fill rather than the neutral one because at that moment the\n * restart is the only thing left to do, the same reason Update has it. */\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.se1e373_restartSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The two activation offers share one recipe: a border in an alias token,\n * never a `bg-layer` fill alone — all four bg-layer tokens collapse to the\n * same white under `body` in the light theme, and a fill-only button would\n * vanish there. Grouped rather than copied, because a copy is a rule that\n * can be corrected on one button and left wrong on the other, silently. */\n.s785196_restartButton,\n.s578d0b_reloadButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s785196_restartButton:hover,\n.s578d0b_reloadButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s785196_restartButton:focus-visible,\n.s578d0b_reloadButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The §4 reload offer: a notice plus its button, one item of\n * `.s6a7b50_installedActions` the way the restart gate is — a column of its own\n * rather than inline with the row. */\n.s168719_reloadPanel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n\n.sbf0f49_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.s62b4ea_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s8a8304_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s9a546e_gateWarning {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n white-space: pre-line;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.secbb11_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s2fe11f_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s597ed4_log {\n max-height: 160px;\n overflow: auto;\n margin: 0;\n padding: 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n list-style: none;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n}\n\n.s2fbc07_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.se1ef74_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.s878e49_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.scaf26f_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sb46af7_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.se50b2b_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* The Updatable section. The class keeps the Host's field name; only the\n heading the reader sees was renamed. */\n.s103f71_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.s2ba5be_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s2283c2_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.se41dca_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.se41dca_outdatedInfo strong {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n word-break: break-all;\n color: var(--dsw-alias-label-primary);\n}\n\n.s970b51_outdatedVersions {\n /* Two inline labels with nothing between them in the markup ran together as\n * `installed v1.0.0latest v2.0.0`: JSX drops the whitespace-only line, and\n * the per-label class the markup asked for was never defined (singular\n * against this rule's plural). The container owns the gap, the same idiom\n * as .s5b12f5_badges — the labels need no class of their own. */\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sfe5725_outdatedActions {\n display: flex;\n align-items: center;\n /* The reload offer (§4) mounts inside the switch beside the Update button\n * and is a two-line block, not a control: without wrapping, a row reading\n * [switch][notice + Reload][Update] overflows the settings dialog. */\n flex-wrap: wrap;\n gap: 10px;\n}\n\n/* The shop's one switch. It is worn by two controls that mean different\n * things — the enable/disable toggle on an installed card, and the shelf's\n * incompatible filter — so the geometry lives here once and each caller states\n * only its hue.\n *\n * `--switch-hue` is that knob, and it is read from the ANCESTOR rather than\n * declared per state: `.s6000bb_incompatibleFilter` sets it once and it reaches the\n * track and the label together, which is what keeps a red switch from sitting\n * under a neutral word. A declaration on `.s21d1d6_switch` itself would shadow the\n * inherited value (an element's own rule beats what it inherits), so the\n * default rides in the `var()` fallback and is resolved once into\n * `--track-hue` instead of being spelled out at each use.\n *\n * `box-sizing` and `padding` are stated because the two callers are different\n * elements — a `<button>`, which the UA gives padding, and a `<span>`, which\n * it does not. Without them the same class renders two sizes, and the knob\n * sits 2px above centre in the taller one. */\n.s21d1d6_switch {\n --track-hue: var(--switch-hue, var(--dsw-alias-state-success-primary));\n box-sizing: border-box;\n display: inline-block;\n position: relative;\n width: 32px;\n height: 18px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n/* The knob's POSITION is the state, which is the reason this control needs no\n * `forced-colors` fallback the way the category pills do: a system palette\n * replaces every colour below and moves nothing. */\n.sfaf305_switchOn {\n border-color: var(--track-hue);\n background: color-mix(in srgb, var(--track-hue) 55%, transparent);\n}\n\n.s3e9566_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.sfaf305_switchOn .s3e9566_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s21d1d6_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s21d1d6_switch, .s3e9566_switchKnob { transition: none; }\n}\n\n/* The hot enable/disable switch on installed rows: the knob button plus the\n * §8 hot-apply / failure notices rendered beside it. */\n.s98de45_switchWrap {\n display: inline-flex;\n align-items: center;\n /* Same reason as `.sfe5725_outdatedActions`: `.s168719_reloadPanel`'s own comment calls\n * itself \"a column of its own rather than inline with the row\", which held\n * for `.s6a7b50_installedActions` and not here until this line. */\n flex-wrap: wrap;\n gap: 8px;\n}\n";
7695
- if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"c4028823\"]")) {
7787
+ //#region \0dsh-plugin-shop-css:201c343a
7788
+ const css = "/* Shop tab styles — a market shelf inside the settings surface. The visual\n * language is the dsh theme's alias tokens ONLY (the four tokens the previous\n * styles referenced that do not exist — bg-layer-3, interactive-bg-hover,\n * state-error, state-success — are corrected to bg-layer-2 and the\n * state-*-primary set). The signature is the category spine: one HUE per\n * category (seven fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so seven brand opacities read as seven\n * shades of gray), with the category name as the cover label in the same hue. Class names are mapped to content-derived names at bundle time\n * (tsdown.se24648_client.s4c8568_config.s64bf36_ts) and stubbed in tests; only the keys are relied\n * on.\n *\n * One token rule the theme forces: the background LAYERS do not carry\n * contrast. In the light theme bg-base, bg-layer-1, bg-layer-2 and\n * bg-layer-3 all resolve to the same white; only the dark theme spreads them\n * (950/875/850/800). So a layer-2 fill on a layer-1 ground is invisible for\n * half of all users, and anything whose fill is its ONLY affordance — the\n * loading skeleton, the borderless capability chips — derives its fill from\n * label-primary instead, which inverts with the theme by construction.\n * Elements that also carry a border or text may keep a layer fill.\n * `css-tokens.se24648_client.scd01a1_spec.s64bf36_ts` pins this. */\n\n.sa18b47_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n /* The shelf never scrolls sideways: a card too wide for its track wraps\n * its badges rather than widening the panel.\n *\n * Clipped on BOTH axes, with a margin, because `.seba6a3_searchInput` sits at\n * exactly (0, 0) of this box — it is the first child of `.s7df13e_toolbar`, itself\n * the first child here, and this rule carries no padding — so its focus\n * ring paints ENTIRELY outside the panel.\n *\n * Both axes is the load-bearing part: `overflow-clip-margin` is inert\n * unless the element clips on both. Clipping x alone left the margin doing\n * nothing — measured against the live app on 0.7.1, the ring reached 0 of\n * its 3px on the left at every device pixel ratio (1, 1.25, 1.5, 2), while\n * keeping the full 3px on the right, where the 280px input stops short of\n * the panel and never meets the clip edge. Raising the margin to 6px\n * changed nothing; clipping both axes with the SAME 3px restored the whole\n * ring. A test that reads declarations cannot see that difference, which is\n * why the first attempt at this shipped broken.\n *\n * `clip` is not a scroll container, so this does not bring back the\n * accidental scrolling `hidden` caused by coercing the other axis to\n * `auto`. Nothing is lost vertically: the panel's scrollHeight equals its\n * height and no descendant paints outside the clip rect (measured).\n * `overflow-x: hidden` stays as the fallback for engines without `clip`,\n * where the ring is clipped as it was before, never worse. */\n overflow-x: hidden;\n overflow: clip;\n overflow-clip-margin: 3px;\n}\n\n.sd85001_stateLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* Visually hidden but screen-reader readable (the loading copy while the\n * skeleton stands in visually). */\n.se60107_srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The loading shelf: ghost cards with the same geometry as the real grid, so\n * the swap to content is a same-shape handoff instead of a jump. */\n.sba4a38_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s5aa954_skeletonCard {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* The shimmer sweep: one gradient band travels across each ghost card while\n * the bars breathe, so the loading state reads as \"the shelf is scanning\". */\n.s5aa954_skeletonCard::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 100deg,\n transparent 25%,\n color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent) 50%,\n transparent 75%\n );\n transform: translateX(-100%);\n animation: dshShopSkeletonSweep 1.4s ease-in-out infinite;\n pointer-events: none;\n}\n\n@keyframes dshShopSkeletonSweep {\n to { transform: translateX(100%); }\n}\n\n.s7a0318_skeletonName {\n width: 55%;\n height: 14px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s7a5df4_skeletonSummary {\n width: 92%;\n height: 12px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s8d4020_skeletonSummaryShort {\n width: 68%;\n height: 12px;\n margin: 6px 12px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n/* The ghost of the action line, right where the real buttons land. */\n.sbc0e1a_skeletonActions {\n display: block;\n width: 84px;\n height: 24px;\n margin: 0 12px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s5aa954_skeletonCard span {\n animation: dshShopSkeletonPulse 1.6s ease-in-out infinite;\n}\n\n@keyframes dshShopSkeletonPulse {\n 0%, 100% { opacity: 0.5; }\n 50% { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s5aa954_skeletonCard span { animation: none; opacity: 0.75; }\n .s5aa954_skeletonCard::after { content: none; }\n}\n\n.s4a2332_actionButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s4a2332_actionButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s4a2332_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s7df13e_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s20709b_toolbarRight {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n/* The shop's own version, right of the search box: `v0.4.4`, plus the\n * update button when the version check found a newer release. */\n.s98c547_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s2b9f1d_versionText {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s394a1f_updateSelfButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.s394a1f_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.s394a1f_updateSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The on-demand re-check next to the version number: a quiet pill; the\n * border and text carry it in the light theme where bg layers are white. */\n.s511e9b_checkUpdateButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s511e9b_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s511e9b_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s511e9b_checkUpdateButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The project's GitHub mark, right of the version row: a quiet icon link.\n * The octocat carries itself — color shift on hover, no text, no border. */\n.se3bcc2_githubLink {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n border-radius: 6px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.se3bcc2_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.se3bcc2_githubLink:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The self-update's expanded states (running log / done + restart /\n * failure detail) sit under the toolbar, above the category bar. */\n.s8c6b17_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.seba6a3_searchInput {\n width: min(280px, 100%);\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n.seba6a3_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sc9d2d3_staleBadge {\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 12px;\n}\n\n.sb4e3b8_categoryBar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s85e01e_categoryButton {\n /* The hue this button carries when selected. `All` and `Installed` select by\n * something other than a category and keep the brand token; the six category\n * buttons override it from the shared hue table below, so a pressed Tool tab\n * turns the same blue as a tool card's spine. */\n --category-hue: var(--dsw-alias-brand-primary);\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n/* Blend text with the theme foreground: darker on light backgrounds, lighter\n * on dark ones. The unblended category hue belongs on the border and fill. */\n.s85e01e_categoryButton:hover {\n border-color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-border-l2));\n color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-label-primary) 55%);\n}\n\n/* Selected. Written `.s85e01e_categoryButton.s833fec_categoryButtonOn` — TWO classes — so it\n * matches `.s85e01e_categoryButton:hover`'s specificity and, coming after it, wins.\n * As a bare `.s833fec_categoryButtonOn` it lost both `border-color` and `color` to the\n * hover rule for as long as the pointer stayed on the tab that had just been\n * clicked, which is precisely when a reader looks for confirmation.\n *\n * EVERY declaration here is a colour or a shadow: a pressed tab must occupy\n * exactly the same box as an unpressed one, so the bar does not reflow — and\n * with ~9 pills on a wrapping row, one that grows can push the rest onto\n * another line and move the tab out from under the pointer that just clicked\n * it. That is what `font-weight: 600` did (bold metrics are wider than\n * regular), and it is why nothing may be added here that resolves to a length:\n * no padding, no border-width, no font-size, no letter-spacing.\n * `css-tokens.se24648_client.scd01a1_spec.s64bf36_ts` fails the build on one.\n *\n * `box-shadow` is the exception that earns its place: it paints outside the\n * layout box, so an inset ring doubles the border's weight at zero cost to the\n * metrics. It is what `font-weight: 600` used to be — a second, thickness-\n * based signal that hover does not also produce — and the `other` tab needs it\n * most: its hue IS the neutral gray this file uses for \"no category\", so on\n * the light theme hue alone barely separates pressed from unpressed. */\n.s85e01e_categoryButton.s833fec_categoryButtonOn {\n border-color: var(--category-hue);\n background: color-mix(in srgb, var(--category-hue) 14%, transparent);\n color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-label-primary) 55%);\n box-shadow: inset 0 0 0 1px var(--category-hue);\n}\n\n/* Forced colours replace every colour above with the system palette, which\n * would leave the three colour declarations saying nothing and the pressed tab\n * indistinguishable from its neighbours. `box-shadow` is dropped in this mode\n * too, so the ring has to come back as an outline. */\n@media (forced-colors: active) {\n .s85e01e_categoryButton.s833fec_categoryButtonOn {\n outline: 2px solid ButtonText;\n outline-offset: -4px;\n }\n}\n\n.s85e01e_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The incompatible filter, at the right edge of the category bar: `margin-left:\n * auto` pushes it past the pills, and the bar wraps, so on a narrow panel it\n * drops to its own line rather than squeezing them. It is a MODIFIER, not a\n * ninth category — the categories select what to show and this one subtracts\n * from whatever they selected — so it takes the error hue it filters on rather\n * than joining the hue table.\n *\n * And it is a SWITCH, which is the difference the shape now carries. It used\n * to wear `.s85e01e_categoryButton`: same pill, same row, same size as the eight tabs,\n * differing only in being red. That reads as a ninth tab, and it is not one —\n * the tabs are choose-one and this is on-or-off. So it drops the pill chrome\n * entirely (no border, no fill, nothing to compete with the row it sits\n * beside) and puts the state in a track, the same one every installed card\n * carries. Nothing here restates the switch geometry; `--switch-hue` is the\n * whole of what this control says about it, and it reaches the label too, so\n * one declaration keeps the word and the track the same colour. */\n.sab8b1c_incompatibleFilter {\n --switch-hue: var(--dsw-alias-state-error-primary);\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Matches the pills' vertical padding so the bar keeps one line box whether\n * this control wraps onto a row of its own or shares theirs. */\n padding: 3px 0;\n border: 0;\n background: none;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n/* Hover lifts the word to full contrast and warms the track's border. Scoped\n * to the OFF track: `.sab8b1c_incompatibleFilter:hover .sc4c0fe_switch` outranks `.s383bcc_switchOn`\n * by two classes, so unscoped it would strip the hue from the border at\n * exactly the moment a reader has the pointer on it to check the state. */\n.sab8b1c_incompatibleFilter:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n.sab8b1c_incompatibleFilter:hover .sc4c0fe_switch:not(.s383bcc_switchOn) {\n border-color: color-mix(in srgb, var(--switch-hue) 45%, var(--dsw-alias-border-l2));\n}\n\n/* On. Written with both classes for the reason `.s85e01e_categoryButton.s833fec_categoryButtonOn`\n * is: it has to match `:hover`'s specificity and come after it, or the colour\n * reverts under a pointer that has not moved since the click. */\n.sab8b1c_incompatibleFilter.s6dac80_incompatibleFilterOn {\n color: color-mix(in srgb, var(--switch-hue) 45%, var(--dsw-alias-label-primary) 55%);\n}\n\n/* Off here means \"nothing is being taken away\" — the resting state — so the\n * knob steps back to the secondary label. The enable switch keeps the full\n * strength dot, where off means \"this plugin is disabled\" and is worth\n * noticing. In this bar it would be the heaviest mark in a row of hairline\n * pills, and on the light theme it is the WHOLE control: bg-layer-2 is the\n * same white as the panel there, so an off track is a hairline outline with a\n * near-black dot sitting in it. */\n.sab8b1c_incompatibleFilter .sc4c0fe_switch:not(.s383bcc_switchOn) .s43a686_switchKnob {\n background: var(--dsw-alias-label-secondary);\n}\n\n/* The ring has no border to trace, so it takes an offset and the pill radius\n * of the tabs it sits beside rather than hugging the text. */\n.sab8b1c_incompatibleFilter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 999px;\n}\n\n.sefee3c_updatableHeading {\n margin: 2px 0 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-primary);\n}\n\n.sff4c1e_catalogStatsRow {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 6px 0 0;\n}\n\n.s927a53_catalogStats {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* Sits beside the 12px stats caption, so it is quieter than `.s4a2332_actionButton`:\n * this is an affordance, not a call to action — the catalog reloads on its\n * own at mount and the shelf is already usable without it. Named\n * `catalogRefresh*` (not `reload*`) so it cannot collide with the §4 page\n * reload offer below, which reloads the whole tab rather than refetching\n * the catalog data. */\n.s68cab8_catalogRefreshButton {\n padding: 1px 9px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 17px;\n cursor: pointer;\n}\n\n.s68cab8_catalogRefreshButton:hover:not(:disabled) {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s68cab8_catalogRefreshButton:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.s68cab8_catalogRefreshButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s6dceed_catalogRefreshFailed {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s5748b0_emptyLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* The shelf: a responsive grid of cards. */\n/* Single-line rows: one full-width column, one plugin per line. */\n.sb2eae9_cards {\n display: grid;\n grid-template-columns: 1fr;\n max-width: 100%;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sb2eae9_cards { animation: none; }\n}\n\n.sa1c2f7_card {\n position: relative;\n display: flex;\n flex-direction: column;\n /* A grid item's min-width is auto by default: its content would widen the\n * 1fr track past the panel and overflow horizontally. */\n min-width: 0;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 140px;\n}\n\n/* The sentinel that triggers the next shelf batch: zero visual footprint. */\n.s203f20_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s795be6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa1c2f7_card:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l1));\n transform: translateY(-1px);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sa1c2f7_card { transition: none; }\n .sa1c2f7_card:hover { transform: none; }\n}\n\n/* The signature: a spine and cover in the category's own hue. The hues are\n * fixed mid-bright colors that read on both themes; `other` deliberately\n * stays a neutral gray so the fallback category does not compete. */\n.sb00bbe_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--category-hue, #8B8E96);\n}\n\n/* The hue table, keyed by `data-category` and read by BOTH surfaces that\n * speak in category colour: a card's spine and badge, and the category bar's\n * own tab. One table, so a tab can never disagree with the cards it filters to\n * — which is the whole point of colouring the tab. `.s85e01e_categoryButton` sets the\n * brand fallback for `All`/`Installed`; these attribute selectors are one\n * specificity step above it and win wherever the attribute is present. */\n.sa1c2f7_card[data-category='tool'], .s85e01e_categoryButton[data-category='tool'] { --category-hue: #4C8DFF; }\n.sa1c2f7_card[data-category='provider'], .s85e01e_categoryButton[data-category='provider'] { --category-hue: #A78BFA; }\n.sa1c2f7_card[data-category='ui'], .s85e01e_categoryButton[data-category='ui'] { --category-hue: #2DD4BF; }\n.sa1c2f7_card[data-category='workflow'], .s85e01e_categoryButton[data-category='workflow'] { --category-hue: #F59E0B; }\n.sa1c2f7_card[data-category='integration'], .s85e01e_categoryButton[data-category='integration'] { --category-hue: #34D399; }\n.sa1c2f7_card[data-category='theme'], .s85e01e_categoryButton[data-category='theme'] { --category-hue: #F472B6; }\n.sa1c2f7_card[data-category='other'], .s85e01e_categoryButton[data-category='other'] { --category-hue: #8B8E96; }\n\n/* The category sits in the badge row as a quiet chip in the category's own\n * hue — the border carries it where the light theme's white layers cannot\n * (the same lesson as the check button). */\n.see02fd_categoryBadge {\n padding: 1px 7px;\n border: 1px solid color-mix(in srgb, var(--category-hue, #8B8E96) 45%, transparent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--category-hue, #8B8E96);\n white-space: nowrap;\n}\n\n.sd53924_entryHeader {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px 2px;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.sd53924_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name fills the row and truncates rather than wraps; the badges stay\n * intact beside it. */\n.sc2b129_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n flex: 1 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-primary);\n}\n\n.scd0548_badges {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* The action line: buttons side by side; an active install/uninstall flow\n * (gate, log, notices) takes the full width below them. */\n.s4f6385_cardActions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 10px;\n}\n\n.s4f6385_cardActions > .sc1afc6_gate,\n.s4f6385_cardActions > .s65b9eb_installPanel,\n.s4f6385_cardActions > .s4fbfe0_installedActions {\n flex-basis: 100%;\n}\n\n/* The entry's version, always visible on the card (the expanded detail\n * repeats it in the version row) — the same quiet mono as the toolbar. */\n.s4b6469_cardVersion {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The octocat marking a github-sourced entry, beside its short commit. */\n.s5a7549_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s23bf0e_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s23bf0e_tierBadge[data-tier='verified'] {\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n}\n\n.s23bf0e_tierBadge[data-tier='verified-stale'] {\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.s23bf0e_tierBadge[data-tier='community'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.sadc37f_incompatibleBadge {\n flex-shrink: 0;\n padding: 3px 9px;\n border-radius: 999px;\n font-size: 12px;\n white-space: nowrap;\n color: var(--dsw-alias-state-error-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 40%, transparent);\n}\n\n.s6ba7cc_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s80dda2_chevron, .sde820b_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.sde820b_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s80dda2_chevron, .sde820b_chevronOpen { transition: none; }\n}\n\n.sa4ad4d_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.s7a8821_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.seab456_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Expanded cards show the full summary: the clamp is lifted, never raised —\n * a higher number would still truncate the author's text, just later. */\n.s5ab512_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.seca366_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n/* `white-space: pre-line` so the newline in the copy is the copy's own: the\n dictionary decides where the line breaks, and a translator changes it\n without touching the component. Collapses runs of spaces like `normal`, so\n only the explicit \\n survives. */\n.sa6eb0b_incompatibleDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre-line;\n color: var(--dsw-alias-state-error-primary);\n}\n\n/* The way out of dsh's own refusal: the lead-in, then the exemption command\n * on its own line. */\n.s92e71f_remedy {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.sb320b4_remedyLead {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n\n/* The exemption command, bound for a terminal unchanged: one click selects\n * all of it, and it wraps anywhere rather than widening the card. A border,\n * not a fill, marks the box: the bg layers are one white in the light theme. */\n.s2c3062_allowCommand {\n display: block;\n padding: 6px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n white-space: pre-wrap;\n word-break: break-all;\n user-select: all;\n color: var(--dsw-alias-label-primary);\n}\n\n/* A refused install on a surface that does not state its blockers — the\n * outdated row — writes them out on their own full-width line under the\n * disabled button, the command that clears dsh's refusal included. */\n.s5fe511_refusal {\n display: flex;\n flex-direction: column;\n gap: 6px;\n flex-basis: 100%;\n}\n\n.scaa43b_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s121391_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s57f233_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s57f233_capabilities li {\n padding: 1px 6px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sbcd943_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.s59866d_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.s4662cb_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.s4662cb_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.s4662cb_detailRow dd {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n overflow-wrap: anywhere;\n text-align: right;\n color: var(--dsw-alias-label-primary);\n}\n\n.s4662cb_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.s4662cb_detailRow a:hover {\n text-decoration: underline;\n}\n\n.s4662cb_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* What the entry IS rather than what you can do with it — how big it is and\n * who published it — at the right edge of the action row. `margin-left: auto`\n * is what pushes it there, and it lives on this wrapper rather than on either\n * label so the group stays flush right when only one of the two has a value —\n * a packument that named no account has no author, and an entry the registry\n * could not measure honestly has no size. Rarer than it was (every live entry\n * now carries a size, github included), but the wrapper is not what makes it\n * rare: without one, an EMPTY meta group still costs 8px, because\n * `.s4f6385_cardActions` is a flex row with `gap: 8px` and a zero-width item is still\n * an item. The row is a wrapping flex, so an active install flow (which claims\n * the full width) drops the whole group to the next line rather than squeezing\n * it. */\n.s6e38b0_cardMeta {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-left: auto;\n min-width: 0;\n}\n\n/* The on-disk size, left of the author. Mono and tabular so the digits of one\n * figure line up with the digits of another WHEREVER the two land: `.s6e38b0_cardMeta`\n * is flush right and the author sits outside the size, so a size's x position\n * moves with the author's width and is different again on the many entries\n * that carry no author at all. There is no column to align to, which is why\n * the reason given here is per-figure legibility rather than the column the\n * first version of this comment claimed. Secondary colour, because it\n * qualifies the entry rather than announcing it.\n *\n * `font-variant-numeric` is safe HERE and would not be on a pill: this element\n * has no state rules, so nothing it does can reflow a wrapping row. */\n.s51bec8_size {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n letter-spacing: 0.02em;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The publishing account, at the outer end of `.s6e38b0_cardMeta` on the action row —\n * NOT beside the npm link in the expanded detail, which is where it used to\n * live and what this comment described until the accurate half was deleted\n * with the `margin-left: auto` that moved to `.s6e38b0_cardMeta`. There is no middot:\n * `.s6e38b0_cardMeta`'s 8px gap separates it from the size.\n *\n * Secondary label colour carries the distinction — never a background fill,\n * which is invisible in the light theme where every bg-layer token is the\n * same white. */\n.s7503fc_author {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sf73a51_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n/* Install flow. */\n.s65b9eb_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.sd07a79_installButton, .s1e8df4_confirmButton, .saa2e75_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.sd07a79_installButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.sd07a79_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s1e8df4_confirmButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.s1e8df4_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.saa2e75_cancelButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n\n.saa2e75_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.sd07a79_installButton:focus-visible, .s1e8df4_confirmButton:focus-visible, .saa2e75_cancelButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The shelf card of a plugin that is installed and current carries this\n * non-interactive label instead of an install button. Border + text do the\n * work in the light theme, where every bg-layer token resolves to white. */\n.s849adb_installedLabel {\n align-self: flex-start;\n margin: 0;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* An installed card's controls: the installed label or the update button,\n * plus the uninstall button. The install panel's running/failed states are\n * column layouts and sit as one item of this row. */\n.s4fbfe0_installedActions {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Uninstall revokes privilege; the quiet outline keeps it from competing\n * with the primary action, and the error tint says \"removes\". */\n.sd93778_uninstallButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 55%, transparent);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.sd93778_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.sd93778_uninstallButton:focus-visible {\n outline: 2px solid var(--dsw-alias-state-error-primary);\n outline-offset: 1px;\n}\n\n/* The restart dsh button on a done install: quiet outline, same rank as\n * the other secondary actions. */\n.s3d7804_restartSelfButton {\n /* The version row's third state. Geometry matches .s511e9b_checkUpdateButton and\n * .s394a1f_updateSelfButton exactly — the row is one evolving control and a\n * different pill would read as a different kind of thing. It takes the\n * emphasised fill rather than the neutral one because at that moment the\n * restart is the only thing left to do, the same reason Update has it. */\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.s3d7804_restartSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The two activation offers share one recipe: a border in an alias token,\n * never a `bg-layer` fill alone — all four bg-layer tokens collapse to the\n * same white under `body` in the light theme, and a fill-only button would\n * vanish there. Grouped rather than copied, because a copy is a rule that\n * can be corrected on one button and left wrong on the other, silently. */\n.s66e9a9_restartButton,\n.s4ca1f6_reloadButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s66e9a9_restartButton:hover,\n.s4ca1f6_reloadButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s66e9a9_restartButton:focus-visible,\n.s4ca1f6_reloadButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The §4 reload offer: a notice plus its button, one item of\n * `.s4fbfe0_installedActions` the way the restart gate is — a column of its own\n * rather than inline with the row. */\n.s42eadb_reloadPanel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n\n.sc1afc6_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.sbbe486_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s119e47_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sadd68a_gateWarning {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n white-space: pre-line;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.s7ac0e2_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.scee95e_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s7bfdc8_log {\n max-height: 160px;\n overflow: auto;\n margin: 0;\n padding: 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n list-style: none;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n}\n\n.s4bc9ba_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s8692d7_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.s537eee_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.scdb145_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sdaa6f0_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.s1c1027_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* The Updatable section. The class keeps the Host's field name; only the\n heading the reader sees was renamed. */\n.sc8d6dc_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.se27588_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s46601e_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.s54c287_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s54c287_outdatedInfo strong {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n word-break: break-all;\n color: var(--dsw-alias-label-primary);\n}\n\n.sa02632_outdatedVersions {\n /* Two inline labels with nothing between them in the markup ran together as\n * `installed v1.0.0latest v2.0.0`: JSX drops the whitespace-only line, and\n * the per-label class the markup asked for was never defined (singular\n * against this rule's plural). The container owns the gap, the same idiom\n * as .scd0548_badges — the labels need no class of their own. */\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s59c2df_outdatedActions {\n display: flex;\n align-items: center;\n /* The reload offer (§4) mounts inside the switch beside the Update button\n * and is a two-line block, not a control: without wrapping, a row reading\n * [switch][notice + Reload][Update] overflows the settings dialog. */\n flex-wrap: wrap;\n gap: 10px;\n}\n\n/* The shop's one switch. It is worn by two controls that mean different\n * things — the enable/disable toggle on an installed card, and the shelf's\n * incompatible filter — so the geometry lives here once and each caller states\n * only its hue.\n *\n * `--switch-hue` is that knob, and it is read from the ANCESTOR rather than\n * declared per state: `.sab8b1c_incompatibleFilter` sets it once and it reaches the\n * track and the label together, which is what keeps a red switch from sitting\n * under a neutral word. A declaration on `.sc4c0fe_switch` itself would shadow the\n * inherited value (an element's own rule beats what it inherits), so the\n * default rides in the `var()` fallback and is resolved once into\n * `--track-hue` instead of being spelled out at each use.\n *\n * `box-sizing` and `padding` are stated because the two callers are different\n * elements — a `<button>`, which the UA gives padding, and a `<span>`, which\n * it does not. Without them the same class renders two sizes, and the knob\n * sits 2px above centre in the taller one. */\n.sc4c0fe_switch {\n --track-hue: var(--switch-hue, var(--dsw-alias-state-success-primary));\n box-sizing: border-box;\n display: inline-block;\n position: relative;\n width: 32px;\n height: 18px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n/* The knob's POSITION is the state, which is the reason this control needs no\n * `forced-colors` fallback the way the category pills do: a system palette\n * replaces every colour below and moves nothing. */\n.s383bcc_switchOn {\n border-color: var(--track-hue);\n background: color-mix(in srgb, var(--track-hue) 55%, transparent);\n}\n\n.s43a686_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.s383bcc_switchOn .s43a686_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.sc4c0fe_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sc4c0fe_switch, .s43a686_switchKnob { transition: none; }\n}\n\n/* The hot enable/disable switch on installed rows: the knob button plus the\n * §8 hot-apply / failure notices rendered beside it. */\n.sd9a4e2_switchWrap {\n display: inline-flex;\n align-items: center;\n /* Same reason as `.s59c2df_outdatedActions`: `.s42eadb_reloadPanel`'s own comment calls\n * itself \"a column of its own rather than inline with the row\", which held\n * for `.s4fbfe0_installedActions` and not here until this line. */\n flex-wrap: wrap;\n gap: 8px;\n}\n";
7789
+ if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"201c343a\"]")) {
7696
7790
  const tag = document.createElement("style");
7697
- tag.dataset.pluginCss = "c4028823";
7791
+ tag.dataset.pluginCss = "201c343a";
7698
7792
  tag.textContent = css;
7699
7793
  document.head.appendChild(tag);
7700
7794
  }
7701
- var _dsh_plugin_shop_css_c4028823_default = {
7702
- "client": "sc389ac_client",
7703
- "config": "sc41672_config",
7704
- "ts": "s8eefa1_ts",
7705
- "spec": "sfe36ad_spec",
7706
- "panel": "s688753_panel",
7707
- "searchInput": "s9f28cb_searchInput",
7708
- "toolbar": "sbc2e10_toolbar",
7709
- "stateLine": "s7237a9_stateLine",
7710
- "srOnly": "s601d42_srOnly",
7711
- "skeletonGrid": "sa26d1f_skeletonGrid",
7712
- "skeletonCard": "sceffd1_skeletonCard",
7713
- "skeletonName": "s6f1efd_skeletonName",
7714
- "skeletonSummary": "s4db475_skeletonSummary",
7715
- "skeletonSummaryShort": "se9c3a9_skeletonSummaryShort",
7716
- "skeletonActions": "s3cbb84_skeletonActions",
7717
- "actionButton": "se4623a_actionButton",
7718
- "toolbarRight": "sb9d854_toolbarRight",
7719
- "versionBlock": "s17e39d_versionBlock",
7720
- "versionText": "sff4480_versionText",
7721
- "updateSelfButton": "sf22160_updateSelfButton",
7722
- "checkUpdateButton": "s1abadc_checkUpdateButton",
7723
- "githubLink": "se9af4e_githubLink",
7724
- "selfUpdatePanel": "s11efce_selfUpdatePanel",
7725
- "staleBadge": "scfe278_staleBadge",
7726
- "categoryBar": "sfd2800_categoryBar",
7727
- "categoryButton": "saa7982_categoryButton",
7728
- "categoryButtonOn": "s47a604_categoryButtonOn",
7729
- "incompatibleFilter": "s6000bb_incompatibleFilter",
7730
- "switch": "s21d1d6_switch",
7731
- "switchOn": "sfaf305_switchOn",
7732
- "incompatibleFilterOn": "s9fb743_incompatibleFilterOn",
7733
- "switchKnob": "s3e9566_switchKnob",
7734
- "updatableHeading": "s3627e7_updatableHeading",
7735
- "catalogStatsRow": "s879949_catalogStatsRow",
7736
- "catalogStats": "s7f1200_catalogStats",
7737
- "catalogRefreshButton": "s7f50e0_catalogRefreshButton",
7738
- "catalogRefreshFailed": "s284f4c_catalogRefreshFailed",
7739
- "emptyLine": "s894d35_emptyLine",
7740
- "cards": "s73be4c_cards",
7741
- "card": "s7ce0b5_card",
7742
- "cardsSentry": "se9f199_cardsSentry",
7743
- "showingLine": "s571a2d_showingLine",
7744
- "cardSpine": "s3afd93_cardSpine",
7745
- "categoryBadge": "sda4302_categoryBadge",
7746
- "entryHeader": "sf9a029_entryHeader",
7747
- "name": "scde6f0_name",
7748
- "badges": "s5b12f5_badges",
7749
- "cardActions": "sbfb66e_cardActions",
7750
- "gate": "sbf0f49_gate",
7751
- "installPanel": "se45c46_installPanel",
7752
- "installedActions": "s6a7b50_installedActions",
7753
- "cardVersion": "sa3f608_cardVersion",
7754
- "sourceBadge": "s908192_sourceBadge",
7755
- "tierBadge": "sda8b40_tierBadge",
7756
- "incompatibleBadge": "s6eb4e5_incompatibleBadge",
7757
- "starsBadge": "s3e85ad_starsBadge",
7758
- "chevron": "sef80ea_chevron",
7759
- "chevronOpen": "sc0d8c8_chevronOpen",
7760
- "body": "safe8c4_body",
7761
- "summary": "sbd2517_summary",
7762
- "summaryZh": "s15225a_summaryZh",
7763
- "summaryExpanded": "s273e04_summaryExpanded",
7764
- "summaryZhExpanded": "s20dbc5_summaryZhExpanded",
7765
- "incompatibleDetail": "s927eb9_incompatibleDetail",
7766
- "remedy": "sa35ed7_remedy",
7767
- "remedyLead": "s2fcacb_remedyLead",
7768
- "allowCommand": "se87ea1_allowCommand",
7769
- "refusal": "sbccf5d_refusal",
7770
- "capabilitiesBlock": "sca4074_capabilitiesBlock",
7771
- "capabilitiesNote": "s3ed23b_capabilitiesNote",
7772
- "capabilities": "sbd7053_capabilities",
7773
- "detail": "se69ccd_detail",
7774
- "detailRows": "s71ed47_detailRows",
7775
- "detailRow": "s488064_detailRow",
7776
- "cardMeta": "sd941dc_cardMeta",
7777
- "size": "sff4055_size",
7778
- "author": "s74b808_author",
7779
- "reviewedLine": "s5a1b52_reviewedLine",
7780
- "installButton": "s7bcc5e_installButton",
7781
- "confirmButton": "sb977c6_confirmButton",
7782
- "cancelButton": "s3cfd9a_cancelButton",
7783
- "installedLabel": "sfaf388_installedLabel",
7784
- "uninstallButton": "sb0c03c_uninstallButton",
7785
- "restartSelfButton": "se1e373_restartSelfButton",
7786
- "restartButton": "s785196_restartButton",
7787
- "reloadButton": "s578d0b_reloadButton",
7788
- "reloadPanel": "s168719_reloadPanel",
7789
- "gateTitle": "s62b4ea_gateTitle",
7790
- "gateBody": "s8a8304_gateBody",
7791
- "gateWarning": "s9a546e_gateWarning",
7792
- "gateActions": "secbb11_gateActions",
7793
- "installing": "s2fe11f_installing",
7794
- "log": "s597ed4_log",
7795
- "logLine": "s2fbc07_logLine",
7796
- "notice": "se1ef74_notice",
7797
- "failedHeading": "s878e49_failedHeading",
7798
- "failedDetail": "scaf26f_failedDetail",
7799
- "rejectedCode": "sb46af7_rejectedCode",
7800
- "rejectedDetail": "se50b2b_rejectedDetail",
7801
- "outdatedSection": "s103f71_outdatedSection",
7802
- "outdatedList": "s2ba5be_outdatedList",
7803
- "outdatedRow": "s2283c2_outdatedRow",
7804
- "outdatedInfo": "se41dca_outdatedInfo",
7805
- "outdatedVersions": "s970b51_outdatedVersions",
7806
- "outdatedActions": "sfe5725_outdatedActions",
7807
- "switchWrap": "s98de45_switchWrap"
7795
+ var _dsh_plugin_shop_css_201c343a_default = {
7796
+ "client": "se24648_client",
7797
+ "config": "s4c8568_config",
7798
+ "ts": "s64bf36_ts",
7799
+ "spec": "scd01a1_spec",
7800
+ "panel": "sa18b47_panel",
7801
+ "searchInput": "seba6a3_searchInput",
7802
+ "toolbar": "s7df13e_toolbar",
7803
+ "stateLine": "sd85001_stateLine",
7804
+ "srOnly": "se60107_srOnly",
7805
+ "skeletonGrid": "sba4a38_skeletonGrid",
7806
+ "skeletonCard": "s5aa954_skeletonCard",
7807
+ "skeletonName": "s7a0318_skeletonName",
7808
+ "skeletonSummary": "s7a5df4_skeletonSummary",
7809
+ "skeletonSummaryShort": "s8d4020_skeletonSummaryShort",
7810
+ "skeletonActions": "sbc0e1a_skeletonActions",
7811
+ "actionButton": "s4a2332_actionButton",
7812
+ "toolbarRight": "s20709b_toolbarRight",
7813
+ "versionBlock": "s98c547_versionBlock",
7814
+ "versionText": "s2b9f1d_versionText",
7815
+ "updateSelfButton": "s394a1f_updateSelfButton",
7816
+ "checkUpdateButton": "s511e9b_checkUpdateButton",
7817
+ "githubLink": "se3bcc2_githubLink",
7818
+ "selfUpdatePanel": "s8c6b17_selfUpdatePanel",
7819
+ "staleBadge": "sc9d2d3_staleBadge",
7820
+ "categoryBar": "sb4e3b8_categoryBar",
7821
+ "categoryButton": "s85e01e_categoryButton",
7822
+ "categoryButtonOn": "s833fec_categoryButtonOn",
7823
+ "incompatibleFilter": "sab8b1c_incompatibleFilter",
7824
+ "switch": "sc4c0fe_switch",
7825
+ "switchOn": "s383bcc_switchOn",
7826
+ "incompatibleFilterOn": "s6dac80_incompatibleFilterOn",
7827
+ "switchKnob": "s43a686_switchKnob",
7828
+ "updatableHeading": "sefee3c_updatableHeading",
7829
+ "catalogStatsRow": "sff4c1e_catalogStatsRow",
7830
+ "catalogStats": "s927a53_catalogStats",
7831
+ "catalogRefreshButton": "s68cab8_catalogRefreshButton",
7832
+ "catalogRefreshFailed": "s6dceed_catalogRefreshFailed",
7833
+ "emptyLine": "s5748b0_emptyLine",
7834
+ "cards": "sb2eae9_cards",
7835
+ "card": "sa1c2f7_card",
7836
+ "cardsSentry": "s203f20_cardsSentry",
7837
+ "showingLine": "s795be6_showingLine",
7838
+ "cardSpine": "sb00bbe_cardSpine",
7839
+ "categoryBadge": "see02fd_categoryBadge",
7840
+ "entryHeader": "sd53924_entryHeader",
7841
+ "name": "sc2b129_name",
7842
+ "badges": "scd0548_badges",
7843
+ "cardActions": "s4f6385_cardActions",
7844
+ "gate": "sc1afc6_gate",
7845
+ "installPanel": "s65b9eb_installPanel",
7846
+ "installedActions": "s4fbfe0_installedActions",
7847
+ "cardVersion": "s4b6469_cardVersion",
7848
+ "sourceBadge": "s5a7549_sourceBadge",
7849
+ "tierBadge": "s23bf0e_tierBadge",
7850
+ "incompatibleBadge": "sadc37f_incompatibleBadge",
7851
+ "starsBadge": "s6ba7cc_starsBadge",
7852
+ "chevron": "s80dda2_chevron",
7853
+ "chevronOpen": "sde820b_chevronOpen",
7854
+ "body": "sa4ad4d_body",
7855
+ "summary": "s7a8821_summary",
7856
+ "summaryZh": "seab456_summaryZh",
7857
+ "summaryExpanded": "s5ab512_summaryExpanded",
7858
+ "summaryZhExpanded": "seca366_summaryZhExpanded",
7859
+ "incompatibleDetail": "sa6eb0b_incompatibleDetail",
7860
+ "remedy": "s92e71f_remedy",
7861
+ "remedyLead": "sb320b4_remedyLead",
7862
+ "allowCommand": "s2c3062_allowCommand",
7863
+ "refusal": "s5fe511_refusal",
7864
+ "capabilitiesBlock": "scaa43b_capabilitiesBlock",
7865
+ "capabilitiesNote": "s121391_capabilitiesNote",
7866
+ "capabilities": "s57f233_capabilities",
7867
+ "detail": "sbcd943_detail",
7868
+ "detailRows": "s59866d_detailRows",
7869
+ "detailRow": "s4662cb_detailRow",
7870
+ "cardMeta": "s6e38b0_cardMeta",
7871
+ "size": "s51bec8_size",
7872
+ "author": "s7503fc_author",
7873
+ "reviewedLine": "sf73a51_reviewedLine",
7874
+ "installButton": "sd07a79_installButton",
7875
+ "confirmButton": "s1e8df4_confirmButton",
7876
+ "cancelButton": "saa2e75_cancelButton",
7877
+ "installedLabel": "s849adb_installedLabel",
7878
+ "uninstallButton": "sd93778_uninstallButton",
7879
+ "restartSelfButton": "s3d7804_restartSelfButton",
7880
+ "restartButton": "s66e9a9_restartButton",
7881
+ "reloadButton": "s4ca1f6_reloadButton",
7882
+ "reloadPanel": "s42eadb_reloadPanel",
7883
+ "gateTitle": "sbbe486_gateTitle",
7884
+ "gateBody": "s119e47_gateBody",
7885
+ "gateWarning": "sadd68a_gateWarning",
7886
+ "gateActions": "s7ac0e2_gateActions",
7887
+ "installing": "scee95e_installing",
7888
+ "log": "s7bfdc8_log",
7889
+ "logLine": "s4bc9ba_logLine",
7890
+ "notice": "s8692d7_notice",
7891
+ "failedHeading": "s537eee_failedHeading",
7892
+ "failedDetail": "scdb145_failedDetail",
7893
+ "rejectedCode": "sdaa6f0_rejectedCode",
7894
+ "rejectedDetail": "s1c1027_rejectedDetail",
7895
+ "outdatedSection": "sc8d6dc_outdatedSection",
7896
+ "outdatedList": "se27588_outdatedList",
7897
+ "outdatedRow": "s46601e_outdatedRow",
7898
+ "outdatedInfo": "s54c287_outdatedInfo",
7899
+ "outdatedVersions": "sa02632_outdatedVersions",
7900
+ "outdatedActions": "s59c2df_outdatedActions",
7901
+ "switchWrap": "sd9a4e2_switchWrap"
7808
7902
  };
7809
7903
  //#endregion
7810
7904
  //#region src/client/ShopTab.tsx
@@ -7821,14 +7915,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7821
7915
  * those nodes, and React's next commit throws on the ones it no longer finds
7822
7916
  * (design 2026-09-26-market-borrowings §4). The tab renders no portals, so its
7823
7917
  * root is its whole surface. */
7824
- const PANEL_ROOT = `${_dsh_plugin_shop_css_c4028823_default.panel} notranslate`;
7918
+ const PANEL_ROOT = `${_dsh_plugin_shop_css_201c343a_default.panel} notranslate`;
7825
7919
  function ChevronIcon({ open }) {
7826
7920
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
7827
7921
  viewBox: "0 0 16 16",
7828
7922
  width: "14",
7829
7923
  height: "14",
7830
7924
  "aria-hidden": "true",
7831
- className: open ? _dsh_plugin_shop_css_c4028823_default.chevronOpen : _dsh_plugin_shop_css_c4028823_default.chevron,
7925
+ className: open ? _dsh_plugin_shop_css_201c343a_default.chevronOpen : _dsh_plugin_shop_css_201c343a_default.chevron,
7832
7926
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
7833
7927
  fill: "currentColor",
7834
7928
  d: "M3.5 6 8 10.5 12.5 6l1 1L8 12.5 2.5 7l1-1Z"
@@ -7858,32 +7952,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7858
7952
  subdir: entry.subdir
7859
7953
  };
7860
7954
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7861
- className: _dsh_plugin_shop_css_c4028823_default.card,
7955
+ className: _dsh_plugin_shop_css_201c343a_default.card,
7862
7956
  "data-shop-entry": entry.name,
7863
7957
  "data-category": category,
7864
7958
  children: [
7865
7959
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7866
- className: _dsh_plugin_shop_css_c4028823_default.cardSpine,
7960
+ className: _dsh_plugin_shop_css_201c343a_default.cardSpine,
7867
7961
  "aria-hidden": "true"
7868
7962
  }),
7869
7963
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7870
7964
  type: "button",
7871
- className: _dsh_plugin_shop_css_c4028823_default.entryHeader,
7965
+ className: _dsh_plugin_shop_css_201c343a_default.entryHeader,
7872
7966
  "aria-expanded": open,
7873
7967
  "aria-controls": detailId,
7874
7968
  onClick: () => setOpen((current) => !current),
7875
7969
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7876
- className: _dsh_plugin_shop_css_c4028823_default.name,
7970
+ className: _dsh_plugin_shop_css_201c343a_default.name,
7877
7971
  children: entry.name
7878
7972
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7879
- className: _dsh_plugin_shop_css_c4028823_default.badges,
7973
+ className: _dsh_plugin_shop_css_201c343a_default.badges,
7880
7974
  children: [
7881
7975
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7882
- className: _dsh_plugin_shop_css_c4028823_default.categoryBadge,
7976
+ className: _dsh_plugin_shop_css_201c343a_default.categoryBadge,
7883
7977
  children: t(categoryKey(entry))
7884
7978
  }),
7885
7979
  entry.source === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7886
- className: _dsh_plugin_shop_css_c4028823_default.sourceBadge,
7980
+ className: _dsh_plugin_shop_css_201c343a_default.sourceBadge,
7887
7981
  "data-shop-source-npm": true,
7888
7982
  role: "img",
7889
7983
  "aria-label": t("npmSource"),
@@ -7900,7 +7994,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7900
7994
  })
7901
7995
  }),
7902
7996
  hasGithubHome(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7903
- className: _dsh_plugin_shop_css_c4028823_default.sourceBadge,
7997
+ className: _dsh_plugin_shop_css_201c343a_default.sourceBadge,
7904
7998
  "data-shop-source-github": true,
7905
7999
  role: "img",
7906
8000
  "aria-label": t("githubSource"),
@@ -7914,17 +8008,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7914
8008
  })
7915
8009
  }),
7916
8010
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7917
- className: _dsh_plugin_shop_css_c4028823_default.cardVersion,
8011
+ className: _dsh_plugin_shop_css_201c343a_default.cardVersion,
7918
8012
  "data-card-version": true,
7919
8013
  children: ["v", displayVersion(entry)]
7920
8014
  }),
7921
8015
  entry.tier !== "community" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7922
- className: _dsh_plugin_shop_css_c4028823_default.tierBadge,
8016
+ className: _dsh_plugin_shop_css_201c343a_default.tierBadge,
7923
8017
  "data-tier": entry.tier,
7924
8018
  children: t(tierKey(entry.tier))
7925
8019
  }),
7926
8020
  stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7927
- className: _dsh_plugin_shop_css_c4028823_default.starsBadge,
8021
+ className: _dsh_plugin_shop_css_201c343a_default.starsBadge,
7928
8022
  role: "img",
7929
8023
  "aria-label": t("stars", { count: stars }),
7930
8024
  children: ["★ ", formatStars(stars)]
@@ -7934,39 +8028,39 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7934
8028
  })]
7935
8029
  }),
7936
8030
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7937
- className: _dsh_plugin_shop_css_c4028823_default.body,
8031
+ className: _dsh_plugin_shop_css_201c343a_default.body,
7938
8032
  children: [
7939
8033
  summary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7940
- className: open ? `${_dsh_plugin_shop_css_c4028823_default.summary} ${_dsh_plugin_shop_css_c4028823_default.summaryExpanded}` : _dsh_plugin_shop_css_c4028823_default.summary,
8034
+ className: open ? `${_dsh_plugin_shop_css_201c343a_default.summary} ${_dsh_plugin_shop_css_201c343a_default.summaryExpanded}` : _dsh_plugin_shop_css_201c343a_default.summary,
7941
8035
  children: summary.en
7942
8036
  }),
7943
8037
  summary?.zh !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7944
- className: open ? `${_dsh_plugin_shop_css_c4028823_default.summaryZh} ${_dsh_plugin_shop_css_c4028823_default.summaryZhExpanded}` : _dsh_plugin_shop_css_c4028823_default.summaryZh,
8038
+ className: open ? `${_dsh_plugin_shop_css_201c343a_default.summaryZh} ${_dsh_plugin_shop_css_201c343a_default.summaryZhExpanded}` : _dsh_plugin_shop_css_201c343a_default.summaryZh,
7945
8039
  children: summary.zh
7946
8040
  }),
7947
8041
  blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
7948
8042
  blocker,
7949
- className: _dsh_plugin_shop_css_c4028823_default.incompatibleDetail,
8043
+ className: _dsh_plugin_shop_css_201c343a_default.incompatibleDetail,
7950
8044
  surface: "detail"
7951
8045
  }, blocker.kind)),
7952
8046
  open && entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7953
- className: _dsh_plugin_shop_css_c4028823_default.capabilitiesBlock,
8047
+ className: _dsh_plugin_shop_css_201c343a_default.capabilitiesBlock,
7954
8048
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7955
- className: _dsh_plugin_shop_css_c4028823_default.capabilitiesNote,
8049
+ className: _dsh_plugin_shop_css_201c343a_default.capabilitiesNote,
7956
8050
  children: t("capabilitiesNote")
7957
8051
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
7958
- className: _dsh_plugin_shop_css_c4028823_default.capabilities,
8052
+ className: _dsh_plugin_shop_css_201c343a_default.capabilities,
7959
8053
  children: entry.catalog.capabilities.map((capability) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: capability }, capability))
7960
8054
  })]
7961
8055
  }),
7962
8056
  open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
7963
8057
  id: detailId,
7964
- className: _dsh_plugin_shop_css_c4028823_default.detail,
8058
+ className: _dsh_plugin_shop_css_201c343a_default.detail,
7965
8059
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
7966
- className: _dsh_plugin_shop_css_c4028823_default.detailRows,
8060
+ className: _dsh_plugin_shop_css_201c343a_default.detailRows,
7967
8061
  children: [
7968
8062
  npmUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7969
- className: _dsh_plugin_shop_css_c4028823_default.detailRow,
8063
+ className: _dsh_plugin_shop_css_201c343a_default.detailRow,
7970
8064
  "data-shop-npm": true,
7971
8065
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("npmPage") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
7972
8066
  href: npmUrl,
@@ -7976,7 +8070,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7976
8070
  }) })]
7977
8071
  }),
7978
8072
  entry.repository !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7979
- className: _dsh_plugin_shop_css_c4028823_default.detailRow,
8073
+ className: _dsh_plugin_shop_css_201c343a_default.detailRow,
7980
8074
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("repository") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /^https?:\/\//.test(entry.repository) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
7981
8075
  href: entry.repository,
7982
8076
  target: "_blank",
@@ -7985,12 +8079,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7985
8079
  }) : entry.repository })]
7986
8080
  }),
7987
8081
  entry.license !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7988
- className: _dsh_plugin_shop_css_c4028823_default.detailRow,
8082
+ className: _dsh_plugin_shop_css_201c343a_default.detailRow,
7989
8083
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("license") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: isCustomLicense(entry.license) ? t("customLicense") : entry.license })]
7990
8084
  })
7991
8085
  ]
7992
8086
  }), entry.tier === "verified-stale" && entry.review !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7993
- className: _dsh_plugin_shop_css_c4028823_default.reviewedLine,
8087
+ className: _dsh_plugin_shop_css_201c343a_default.reviewedLine,
7994
8088
  children: entry.review.reviewedVersion !== void 0 ? t("reviewedVersionLine", {
7995
8089
  reviewed: entry.review.reviewedVersion,
7996
8090
  current: entry.version
@@ -8003,7 +8097,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8003
8097
  ]
8004
8098
  }),
8005
8099
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8006
- className: _dsh_plugin_shop_css_c4028823_default.cardActions,
8100
+ className: _dsh_plugin_shop_css_201c343a_default.cardActions,
8007
8101
  "data-shop-actions": true,
8008
8102
  children: [installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [!inInstalledView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
8009
8103
  target: installTarget,
@@ -8035,7 +8129,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8035
8129
  restartBlocked,
8036
8130
  reload
8037
8131
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8038
- className: _dsh_plugin_shop_css_c4028823_default.installedLabel,
8132
+ className: _dsh_plugin_shop_css_201c343a_default.installedLabel,
8039
8133
  "data-shop-installed": true,
8040
8134
  children: t("installed")
8041
8135
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
@@ -8046,6 +8140,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8046
8140
  row: installed,
8047
8141
  t,
8048
8142
  setEnabled,
8143
+ restart,
8144
+ restartBlocked,
8049
8145
  reload
8050
8146
  }),
8051
8147
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
@@ -8057,16 +8153,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8057
8153
  flow: uninstallFlow
8058
8154
  })
8059
8155
  ] }), (size !== void 0 || author !== null) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8060
- className: _dsh_plugin_shop_css_c4028823_default.cardMeta,
8156
+ className: _dsh_plugin_shop_css_201c343a_default.cardMeta,
8061
8157
  children: [size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8062
- className: _dsh_plugin_shop_css_c4028823_default.size,
8158
+ className: _dsh_plugin_shop_css_201c343a_default.size,
8063
8159
  "data-shop-size": true,
8064
8160
  role: "img",
8065
8161
  "aria-label": t("sizeLabel", { size }),
8066
8162
  title: t("sizeLabel", { size }),
8067
8163
  children: size
8068
8164
  }), author !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8069
- className: _dsh_plugin_shop_css_c4028823_default.author,
8165
+ className: _dsh_plugin_shop_css_201c343a_default.author,
8070
8166
  "data-shop-author": true,
8071
8167
  children: t("authorLine", { author })
8072
8168
  })]
@@ -8115,13 +8211,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8115
8211
  ...attribute,
8116
8212
  children: blocker.text
8117
8213
  }), blocker.remedy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8118
- className: _dsh_plugin_shop_css_c4028823_default.remedy,
8214
+ className: _dsh_plugin_shop_css_201c343a_default.remedy,
8119
8215
  "data-shop-remedy": blocker.kind,
8120
8216
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8121
- className: _dsh_plugin_shop_css_c4028823_default.remedyLead,
8217
+ className: _dsh_plugin_shop_css_201c343a_default.remedyLead,
8122
8218
  children: blocker.remedy.lead
8123
8219
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
8124
- className: _dsh_plugin_shop_css_c4028823_default.allowCommand,
8220
+ className: _dsh_plugin_shop_css_201c343a_default.allowCommand,
8125
8221
  "data-shop-allow-command": true,
8126
8222
  children: blocker.remedy.command
8127
8223
  })]
@@ -8159,7 +8255,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8159
8255
  if (lead === void 0 || word === null) return null;
8160
8256
  const detail = blockers.map((blocker) => blocker.text).join("\n");
8161
8257
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8162
- className: _dsh_plugin_shop_css_c4028823_default.incompatibleBadge,
8258
+ className: _dsh_plugin_shop_css_201c343a_default.incompatibleBadge,
8163
8259
  "data-shop-blocker": lead.kind,
8164
8260
  role: "img",
8165
8261
  "aria-label": detail,
@@ -8176,33 +8272,40 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8176
8272
  const [gateOpen, setGateOpen] = (0, react.useState)(false);
8177
8273
  const { view, start } = flow;
8178
8274
  if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8179
- className: _dsh_plugin_shop_css_c4028823_default.installPanel,
8275
+ className: _dsh_plugin_shop_css_201c343a_default.installPanel,
8180
8276
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8181
- className: _dsh_plugin_shop_css_c4028823_default.installing,
8277
+ className: _dsh_plugin_shop_css_201c343a_default.installing,
8182
8278
  children: t(installPhaseKey(view.phase))
8183
8279
  }), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8184
- className: _dsh_plugin_shop_css_c4028823_default.log,
8280
+ className: _dsh_plugin_shop_css_201c343a_default.log,
8185
8281
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8186
- className: _dsh_plugin_shop_css_c4028823_default.logLine,
8282
+ className: _dsh_plugin_shop_css_201c343a_default.logLine,
8283
+ "data-shop-log-line": true,
8187
8284
  children: line
8188
8285
  }, index))
8189
8286
  })]
8190
8287
  });
8191
8288
  if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8192
- className: _dsh_plugin_shop_css_c4028823_default.installedActions,
8289
+ className: _dsh_plugin_shop_css_201c343a_default.installedActions,
8193
8290
  children: [
8194
8291
  view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8195
- className: _dsh_plugin_shop_css_c4028823_default.log,
8292
+ className: _dsh_plugin_shop_css_201c343a_default.log,
8196
8293
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8197
- className: _dsh_plugin_shop_css_c4028823_default.logLine,
8294
+ className: _dsh_plugin_shop_css_201c343a_default.logLine,
8295
+ "data-shop-log-line": true,
8198
8296
  children: line
8199
8297
  }, index))
8200
8298
  }),
8201
8299
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8202
- className: _dsh_plugin_shop_css_c4028823_default.notice,
8300
+ className: _dsh_plugin_shop_css_201c343a_default.notice,
8203
8301
  "data-shop-restart-notice": true,
8204
8302
  children: t(activationNoticeKey(view.activation, view.restartReason))
8205
8303
  }),
8304
+ view.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8305
+ className: _dsh_plugin_shop_css_201c343a_default.notice,
8306
+ "data-shop-done-note": true,
8307
+ children: view.detail
8308
+ }),
8206
8309
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationOffer, {
8207
8310
  activation: view.activation,
8208
8311
  t,
@@ -8214,56 +8317,57 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8214
8317
  ]
8215
8318
  });
8216
8319
  if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8217
- className: _dsh_plugin_shop_css_c4028823_default.installPanel,
8320
+ className: _dsh_plugin_shop_css_201c343a_default.installPanel,
8218
8321
  children: [
8219
8322
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8220
- className: _dsh_plugin_shop_css_c4028823_default.failedHeading,
8323
+ className: _dsh_plugin_shop_css_201c343a_default.failedHeading,
8221
8324
  children: t("installFailed")
8222
8325
  }),
8223
8326
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8224
- className: _dsh_plugin_shop_css_c4028823_default.log,
8327
+ className: _dsh_plugin_shop_css_201c343a_default.log,
8225
8328
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8226
- className: _dsh_plugin_shop_css_c4028823_default.logLine,
8329
+ className: _dsh_plugin_shop_css_201c343a_default.logLine,
8330
+ "data-shop-log-line": true,
8227
8331
  children: line
8228
8332
  }, index))
8229
8333
  }),
8230
8334
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8231
- className: _dsh_plugin_shop_css_c4028823_default.failedDetail,
8335
+ className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
8232
8336
  children: view.detail === "" ? t("installTransportFailed") : view.detail
8233
8337
  })
8234
8338
  ]
8235
8339
  });
8236
8340
  if (view.kind === "rejected") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8237
- className: _dsh_plugin_shop_css_c4028823_default.installPanel,
8341
+ className: _dsh_plugin_shop_css_201c343a_default.installPanel,
8238
8342
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8239
- className: _dsh_plugin_shop_css_c4028823_default.rejectedCode,
8343
+ className: _dsh_plugin_shop_css_201c343a_default.rejectedCode,
8240
8344
  children: t(rejectionCodeKey(view.code))
8241
8345
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8242
- className: _dsh_plugin_shop_css_c4028823_default.rejectedDetail,
8346
+ className: _dsh_plugin_shop_css_201c343a_default.rejectedDetail,
8243
8347
  children: view.detail
8244
8348
  })]
8245
8349
  });
8246
8350
  if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8247
- className: _dsh_plugin_shop_css_c4028823_default.gate,
8351
+ className: _dsh_plugin_shop_css_201c343a_default.gate,
8248
8352
  children: [
8249
8353
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8250
- className: _dsh_plugin_shop_css_c4028823_default.gateTitle,
8354
+ className: _dsh_plugin_shop_css_201c343a_default.gateTitle,
8251
8355
  children: t("acknowledgementTitle")
8252
8356
  }),
8253
8357
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8254
- className: _dsh_plugin_shop_css_c4028823_default.gateBody,
8358
+ className: _dsh_plugin_shop_css_201c343a_default.gateBody,
8255
8359
  children: t("acknowledgementBody")
8256
8360
  }),
8257
8361
  !blockersStated && blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
8258
8362
  blocker,
8259
- className: _dsh_plugin_shop_css_c4028823_default.gateWarning,
8363
+ className: _dsh_plugin_shop_css_201c343a_default.gateWarning,
8260
8364
  surface: "warning"
8261
8365
  }, blocker.kind)),
8262
8366
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8263
- className: _dsh_plugin_shop_css_c4028823_default.gateActions,
8367
+ className: _dsh_plugin_shop_css_201c343a_default.gateActions,
8264
8368
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8265
8369
  type: "button",
8266
- className: _dsh_plugin_shop_css_c4028823_default.confirmButton,
8370
+ className: _dsh_plugin_shop_css_201c343a_default.confirmButton,
8267
8371
  "data-shop-confirm": true,
8268
8372
  onClick: () => {
8269
8373
  setGateOpen(false);
@@ -8275,7 +8379,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8275
8379
  children: t("confirm")
8276
8380
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8277
8381
  type: "button",
8278
- className: _dsh_plugin_shop_css_c4028823_default.cancelButton,
8382
+ className: _dsh_plugin_shop_css_201c343a_default.cancelButton,
8279
8383
  onClick: () => setGateOpen(false),
8280
8384
  children: t("cancel")
8281
8385
  })]
@@ -8287,7 +8391,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8287
8391
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8288
8392
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8289
8393
  type: "button",
8290
- className: _dsh_plugin_shop_css_c4028823_default.installButton,
8394
+ className: _dsh_plugin_shop_css_201c343a_default.installButton,
8291
8395
  disabled: refused,
8292
8396
  ...refused ? { "data-shop-refused": true } : {},
8293
8397
  ...update ? { "data-shop-update": true } : { "data-shop-install": true },
@@ -8305,11 +8409,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8305
8409
  t
8306
8410
  }),
8307
8411
  refused && !blockersStated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8308
- className: _dsh_plugin_shop_css_c4028823_default.refusal,
8412
+ className: _dsh_plugin_shop_css_201c343a_default.refusal,
8309
8413
  "data-shop-refusal": true,
8310
8414
  children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
8311
8415
  blocker,
8312
- className: _dsh_plugin_shop_css_c4028823_default.gateWarning,
8416
+ className: _dsh_plugin_shop_css_201c343a_default.gateWarning,
8313
8417
  surface: "warning"
8314
8418
  }, blocker.kind))
8315
8419
  })
@@ -8324,23 +8428,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8324
8428
  function UninstallPanel({ name, t, restart, restartBlocked, reload, flow }) {
8325
8429
  const { view, start, reset } = flow;
8326
8430
  if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8327
- className: _dsh_plugin_shop_css_c4028823_default.installPanel,
8431
+ className: _dsh_plugin_shop_css_201c343a_default.installPanel,
8328
8432
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8329
- className: _dsh_plugin_shop_css_c4028823_default.installing,
8433
+ className: _dsh_plugin_shop_css_201c343a_default.installing,
8330
8434
  children: t("uninstalling")
8331
8435
  }), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8332
- className: _dsh_plugin_shop_css_c4028823_default.log,
8436
+ className: _dsh_plugin_shop_css_201c343a_default.log,
8333
8437
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8334
- className: _dsh_plugin_shop_css_c4028823_default.logLine,
8438
+ className: _dsh_plugin_shop_css_201c343a_default.logLine,
8439
+ "data-shop-log-line": true,
8335
8440
  children: line
8336
8441
  }, index))
8337
8442
  })]
8338
8443
  });
8339
8444
  if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8340
- className: _dsh_plugin_shop_css_c4028823_default.installedActions,
8445
+ className: _dsh_plugin_shop_css_201c343a_default.installedActions,
8341
8446
  children: [
8447
+ view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8448
+ className: _dsh_plugin_shop_css_201c343a_default.log,
8449
+ children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8450
+ className: _dsh_plugin_shop_css_201c343a_default.logLine,
8451
+ "data-shop-log-line": true,
8452
+ children: line
8453
+ }, index))
8454
+ }),
8342
8455
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8343
- className: _dsh_plugin_shop_css_c4028823_default.notice,
8456
+ className: _dsh_plugin_shop_css_201c343a_default.notice,
8344
8457
  "data-shop-uninstall-done": true,
8345
8458
  children: t(uninstallActivationNoticeKey(view.activation))
8346
8459
  }),
@@ -8354,7 +8467,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8354
8467
  }),
8355
8468
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8356
8469
  type: "button",
8357
- className: _dsh_plugin_shop_css_c4028823_default.cancelButton,
8470
+ className: _dsh_plugin_shop_css_201c343a_default.cancelButton,
8358
8471
  "data-shop-uninstall-dismiss": true,
8359
8472
  onClick: reset,
8360
8473
  children: t("dismiss")
@@ -8362,26 +8475,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8362
8475
  ]
8363
8476
  });
8364
8477
  if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8365
- className: _dsh_plugin_shop_css_c4028823_default.installPanel,
8478
+ className: _dsh_plugin_shop_css_201c343a_default.installPanel,
8366
8479
  children: [
8367
8480
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8368
- className: _dsh_plugin_shop_css_c4028823_default.failedHeading,
8481
+ className: _dsh_plugin_shop_css_201c343a_default.failedHeading,
8369
8482
  children: t("uninstallFailed")
8370
8483
  }),
8371
8484
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8372
- className: _dsh_plugin_shop_css_c4028823_default.log,
8485
+ className: _dsh_plugin_shop_css_201c343a_default.log,
8373
8486
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8374
- className: _dsh_plugin_shop_css_c4028823_default.logLine,
8487
+ className: _dsh_plugin_shop_css_201c343a_default.logLine,
8488
+ "data-shop-log-line": true,
8375
8489
  children: line
8376
8490
  }, index))
8377
8491
  }),
8378
8492
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8379
- className: _dsh_plugin_shop_css_c4028823_default.failedDetail,
8493
+ className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
8380
8494
  children: view.detail === "" ? t("uninstallTransportFailed") : view.detail
8381
8495
  }),
8382
8496
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8383
8497
  type: "button",
8384
- className: _dsh_plugin_shop_css_c4028823_default.uninstallButton,
8498
+ className: _dsh_plugin_shop_css_201c343a_default.uninstallButton,
8385
8499
  "data-shop-uninstall-retry": true,
8386
8500
  onClick: () => void start({ name }),
8387
8501
  children: t("retry")
@@ -8391,7 +8505,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8391
8505
  if (view.kind !== "idle") return null;
8392
8506
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8393
8507
  type: "button",
8394
- className: _dsh_plugin_shop_css_c4028823_default.uninstallButton,
8508
+ className: _dsh_plugin_shop_css_201c343a_default.uninstallButton,
8395
8509
  "data-shop-uninstall": true,
8396
8510
  onClick: () => void start({ name }),
8397
8511
  children: t("uninstall")
@@ -8406,12 +8520,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8406
8520
  /** The §8 restart flow (amendment 2026-08-27): after an install, update, or
8407
8521
  * uninstall reports done, this panel offers a restart of dsh. The
8408
8522
  * confirmation gate states the cost — the page disconnects and in-flight
8409
- * conversations/tasks are interrupted — and on confirm the restart RPC
8410
- * commits the two-phase handoff: the host exits, a helper re-runs dsh, and
8411
- * the page's restart monitor (restart-monitor.ts) polls the origin after a
8412
- * grace period, refreshing the page once the NEW server keeps answering.
8413
- * A refused restart renders the host's published detail; a server that
8414
- * never comes back names the manual command. */
8523
+ * conversations/tasks are interrupted — and on confirm the page asks the host
8524
+ * (restart-monitor.ts), which commits the two-phase handoff: the host exits,
8525
+ * a helper re-runs dsh, and the page's monitor polls the origin after a grace
8526
+ * period, refreshing the page once the NEW server keeps answering. A refused
8527
+ * restart renders the host's published detail above the offer, which stays;
8528
+ * a server that never comes back names the manual command. */
8415
8529
  function RestartPanel({ t, restart, reload, gate }) {
8416
8530
  const [ownGateOpen, setOwnGateOpen] = (0, react.useState)(false);
8417
8531
  const gateOpen = gate === void 0 ? ownGateOpen : gate.open;
@@ -8423,71 +8537,65 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8423
8537
  const monitor = useRestartMonitor();
8424
8538
  const onConfirm = async () => {
8425
8539
  setGateOpen(false);
8426
- try {
8427
- const result = await restart();
8428
- if (!result.ok) {
8429
- setRefusal(result.detail);
8430
- return;
8431
- }
8432
- startRestartMonitor({
8433
- reload,
8434
- ...typeof result.logFile === "string" ? { logFile: result.logFile } : {}
8435
- });
8436
- } catch {
8437
- setRefusal(t("restartTransportFailed"));
8438
- }
8540
+ setRefusal(null);
8541
+ const outcome = await requestRestart({
8542
+ reload,
8543
+ restart
8544
+ });
8545
+ if (outcome.kind === "refused") setRefusal(outcome.detail);
8546
+ else if (outcome.kind === "unreachable") setRefusal(t("restartTransportFailed"));
8439
8547
  };
8440
- if (monitor?.kind === "restarting") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8441
- className: _dsh_plugin_shop_css_c4028823_default.notice,
8548
+ if (monitor?.kind === "requesting" || monitor?.kind === "restarting") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8549
+ className: _dsh_plugin_shop_css_201c343a_default.notice,
8442
8550
  "data-shop-restarting": true,
8443
8551
  children: t("restarting")
8444
8552
  });
8445
8553
  if (monitor?.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8446
- className: _dsh_plugin_shop_css_c4028823_default.failedDetail,
8554
+ className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
8447
8555
  "data-shop-restart-error": true,
8448
8556
  children: monitor.logFile === void 0 ? t("restartFailedNotice") : t("restartFailedLogNotice", { log: monitor.logFile })
8449
8557
  });
8450
- if (refusal !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8451
- className: _dsh_plugin_shop_css_c4028823_default.failedDetail,
8452
- "data-shop-restart-error": true,
8453
- children: refusal
8454
- });
8455
8558
  if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8456
- className: _dsh_plugin_shop_css_c4028823_default.gate,
8559
+ className: _dsh_plugin_shop_css_201c343a_default.gate,
8457
8560
  children: [
8458
8561
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8459
- className: _dsh_plugin_shop_css_c4028823_default.gateTitle,
8562
+ className: _dsh_plugin_shop_css_201c343a_default.gateTitle,
8460
8563
  children: t("restartTitle")
8461
8564
  }),
8462
8565
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8463
- className: _dsh_plugin_shop_css_c4028823_default.gateBody,
8566
+ className: _dsh_plugin_shop_css_201c343a_default.gateBody,
8464
8567
  children: t("restartBody")
8465
8568
  }),
8466
8569
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8467
- className: _dsh_plugin_shop_css_c4028823_default.gateActions,
8570
+ className: _dsh_plugin_shop_css_201c343a_default.gateActions,
8468
8571
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8469
8572
  type: "button",
8470
- className: _dsh_plugin_shop_css_c4028823_default.confirmButton,
8573
+ className: _dsh_plugin_shop_css_201c343a_default.confirmButton,
8471
8574
  "data-shop-restart-confirm": true,
8472
8575
  onClick: () => void onConfirm(),
8473
8576
  children: t("restartConfirm")
8474
8577
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8475
8578
  type: "button",
8476
- className: _dsh_plugin_shop_css_c4028823_default.cancelButton,
8579
+ className: _dsh_plugin_shop_css_201c343a_default.cancelButton,
8477
8580
  onClick: () => setGateOpen(false),
8478
8581
  children: t("cancel")
8479
8582
  })]
8480
8583
  })
8481
8584
  ]
8482
8585
  });
8483
- if (gate !== void 0) return null;
8484
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8586
+ const refused = refusal === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8587
+ className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
8588
+ "data-shop-restart-error": true,
8589
+ children: refusal
8590
+ });
8591
+ if (gate !== void 0) return refused;
8592
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refused, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8485
8593
  type: "button",
8486
- className: _dsh_plugin_shop_css_c4028823_default.restartButton,
8594
+ className: _dsh_plugin_shop_css_201c343a_default.restartButton,
8487
8595
  "data-shop-restart": true,
8488
8596
  onClick: () => setGateOpen(true),
8489
8597
  children: t("restart")
8490
- });
8598
+ })] });
8491
8599
  }
8492
8600
  /**
8493
8601
  * The §4/§8 offer under a settled install, update or uninstall: a reload
@@ -8514,7 +8622,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8514
8622
  restart,
8515
8623
  reload
8516
8624
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8517
- className: _dsh_plugin_shop_css_c4028823_default.notice,
8625
+ className: _dsh_plugin_shop_css_201c343a_default.notice,
8518
8626
  "data-shop-restart-disabled": true,
8519
8627
  children: t(restartBlockedNoticeKey(restartBlocked))
8520
8628
  });
@@ -8525,14 +8633,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8525
8633
  * would help without knowing that now is a good time. */
8526
8634
  function ReloadPanel({ t, reload, where }) {
8527
8635
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8528
- className: _dsh_plugin_shop_css_c4028823_default.reloadPanel,
8636
+ className: _dsh_plugin_shop_css_201c343a_default.reloadPanel,
8529
8637
  "data-shop-reload": where,
8530
8638
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8531
- className: _dsh_plugin_shop_css_c4028823_default.notice,
8639
+ className: _dsh_plugin_shop_css_201c343a_default.notice,
8532
8640
  children: t("reloadNote")
8533
8641
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8534
8642
  type: "button",
8535
- className: _dsh_plugin_shop_css_c4028823_default.reloadButton,
8643
+ className: _dsh_plugin_shop_css_201c343a_default.reloadButton,
8536
8644
  onClick: reload,
8537
8645
  children: t("reload")
8538
8646
  })]
@@ -8545,10 +8653,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8545
8653
  * transport throw renders the localized failure line — its private detail
8546
8654
  * (which can name hosts and ports) never reaches the UI.
8547
8655
  */
8548
- function EnabledSwitch({ row, t, setEnabled, reload }) {
8656
+ function EnabledSwitch({ row, t, setEnabled, restart, restartBlocked, reload }) {
8549
8657
  const [enabled, setEnabledState] = (0, react.useState)(row.enabled);
8550
8658
  const [toggle, setToggle] = (0, react.useState)({ kind: "idle" });
8551
- const [needsReload, setNeedsReload] = (0, react.useState)(false);
8659
+ const [owed, setOwed] = (0, react.useState)("nothing");
8552
8660
  const onToggle = async () => {
8553
8661
  if (toggle.kind === "saving") return;
8554
8662
  const next = !enabled;
@@ -8561,7 +8669,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8561
8669
  if (result.ok) {
8562
8670
  setEnabledState(next);
8563
8671
  setToggle({ kind: "saved" });
8564
- if (result.activation !== "live") setNeedsReload(true);
8672
+ if (result.activation === "restart") setOwed("restart");
8673
+ else if (result.activation !== "live") setOwed((previous) => previous === "restart" ? previous : "reload");
8565
8674
  } else setToggle({
8566
8675
  kind: "error",
8567
8676
  detail: result.detail || t("toggleFailed")
@@ -8574,7 +8683,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8574
8683
  }
8575
8684
  };
8576
8685
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8577
- className: _dsh_plugin_shop_css_c4028823_default.switchWrap,
8686
+ className: _dsh_plugin_shop_css_201c343a_default.switchWrap,
8578
8687
  "data-shop-enabled-switch": row.name,
8579
8688
  children: [
8580
8689
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -8583,23 +8692,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8583
8692
  "aria-checked": enabled,
8584
8693
  "aria-label": t("enabledSwitch"),
8585
8694
  "data-shop-toggle": true,
8586
- className: `${_dsh_plugin_shop_css_c4028823_default.switch} ${enabled ? _dsh_plugin_shop_css_c4028823_default.switchOn : ""}`,
8695
+ className: `${_dsh_plugin_shop_css_201c343a_default.switch} ${enabled ? _dsh_plugin_shop_css_201c343a_default.switchOn : ""}`,
8587
8696
  onClick: () => void onToggle(),
8588
8697
  disabled: toggle.kind === "saving",
8589
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_c4028823_default.switchKnob })
8698
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.switchKnob })
8590
8699
  }),
8591
- toggle.kind === "saved" && !needsReload && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8592
- className: _dsh_plugin_shop_css_c4028823_default.notice,
8700
+ toggle.kind === "saved" && owed === "nothing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8701
+ className: _dsh_plugin_shop_css_201c343a_default.notice,
8593
8702
  "data-shop-hot-apply": true,
8594
8703
  children: t("hotApplyNote")
8595
8704
  }),
8596
- needsReload && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReloadPanel, {
8705
+ owed === "restart" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8706
+ className: _dsh_plugin_shop_css_201c343a_default.notice,
8707
+ "data-shop-toggle-restart": true,
8708
+ children: t("toggleRestartNote")
8709
+ }),
8710
+ owed !== "nothing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationOffer, {
8711
+ activation: owed,
8597
8712
  t,
8713
+ restart,
8714
+ restartBlocked,
8598
8715
  reload,
8599
8716
  where: "toggle"
8600
8717
  }),
8601
8718
  toggle.kind === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8602
- className: _dsh_plugin_shop_css_c4028823_default.failedDetail,
8719
+ className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
8603
8720
  "data-shop-toggle-error": true,
8604
8721
  children: toggle.detail
8605
8722
  })
@@ -8611,23 +8728,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8611
8728
  * install flow for `name@latest`, reusing `InstallPanel`). */
8612
8729
  function OutdatedRow({ row, tier, missing, harness, t, setEnabled, flowFor, restart, restartBlocked, reload }) {
8613
8730
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8614
- className: _dsh_plugin_shop_css_c4028823_default.outdatedRow,
8731
+ className: _dsh_plugin_shop_css_201c343a_default.outdatedRow,
8615
8732
  "data-shop-outdated-entry": row.name,
8616
8733
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8617
- className: _dsh_plugin_shop_css_c4028823_default.outdatedInfo,
8734
+ className: _dsh_plugin_shop_css_201c343a_default.outdatedInfo,
8618
8735
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8619
- className: _dsh_plugin_shop_css_c4028823_default.name,
8736
+ className: _dsh_plugin_shop_css_201c343a_default.name,
8620
8737
  children: row.name
8621
8738
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8622
- className: _dsh_plugin_shop_css_c4028823_default.outdatedVersions,
8739
+ className: _dsh_plugin_shop_css_201c343a_default.outdatedVersions,
8623
8740
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("installedVersion", { version: row.source === "github" ? row.installed.slice(0, 7) : row.installed }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("latestVersion", { version: row.latest }) })]
8624
8741
  })]
8625
8742
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8626
- className: _dsh_plugin_shop_css_c4028823_default.outdatedActions,
8743
+ className: _dsh_plugin_shop_css_201c343a_default.outdatedActions,
8627
8744
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
8628
8745
  row,
8629
8746
  t,
8630
8747
  setEnabled,
8748
+ restart,
8749
+ restartBlocked,
8631
8750
  reload
8632
8751
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
8633
8752
  target: {
@@ -8669,22 +8788,22 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8669
8788
  function OutdatedSection({ state, entriesByKey, missingByKey, harnessVerdicts, t, setEnabled, flowFor, restart, restartBlocked, reload }) {
8670
8789
  if (state.kind === "loading") return null;
8671
8790
  if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8672
- className: _dsh_plugin_shop_css_c4028823_default.stateLine,
8791
+ className: _dsh_plugin_shop_css_201c343a_default.stateLine,
8673
8792
  "data-shop-outdated-error": true,
8674
8793
  children: t("installedError")
8675
8794
  });
8676
8795
  const outdated = state.entries.filter((entry) => entry.outdated);
8677
8796
  if (outdated.length === 0) return null;
8678
8797
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
8679
- className: _dsh_plugin_shop_css_c4028823_default.outdatedSection,
8798
+ className: _dsh_plugin_shop_css_201c343a_default.outdatedSection,
8680
8799
  "data-shop-outdated": true,
8681
8800
  "aria-labelledby": UPDATABLE_HEADING_ID,
8682
8801
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
8683
- className: _dsh_plugin_shop_css_c4028823_default.updatableHeading,
8802
+ className: _dsh_plugin_shop_css_201c343a_default.updatableHeading,
8684
8803
  id: UPDATABLE_HEADING_ID,
8685
8804
  children: t("updatableSection")
8686
8805
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8687
- className: _dsh_plugin_shop_css_c4028823_default.outdatedList,
8806
+ className: _dsh_plugin_shop_css_201c343a_default.outdatedList,
8688
8807
  children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
8689
8808
  row,
8690
8809
  tier: entriesByKey.get(identityKey(row))?.tier ?? "community",
@@ -8735,16 +8854,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8735
8854
  role: "alert",
8736
8855
  children: [
8737
8856
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8738
- className: _dsh_plugin_shop_css_c4028823_default.stateLine,
8857
+ className: _dsh_plugin_shop_css_201c343a_default.stateLine,
8739
8858
  children: say("tabCrashed", en.tabCrashed)
8740
8859
  }),
8741
8860
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8742
- className: _dsh_plugin_shop_css_c4028823_default.failedDetail,
8861
+ className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
8743
8862
  children: error.message
8744
8863
  }),
8745
8864
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8746
8865
  type: "button",
8747
- className: _dsh_plugin_shop_css_c4028823_default.actionButton,
8866
+ className: _dsh_plugin_shop_css_201c343a_default.actionButton,
8748
8867
  onClick: () => this.setState({ error: null }),
8749
8868
  children: say("retry", en.retry)
8750
8869
  })
@@ -9001,19 +9120,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9001
9120
  "data-shop-tab": true,
9002
9121
  "aria-busy": "true",
9003
9122
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9004
- className: _dsh_plugin_shop_css_c4028823_default.srOnly,
9123
+ className: _dsh_plugin_shop_css_201c343a_default.srOnly,
9005
9124
  children: t("loading")
9006
9125
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9007
- className: _dsh_plugin_shop_css_c4028823_default.skeletonGrid,
9126
+ className: _dsh_plugin_shop_css_201c343a_default.skeletonGrid,
9008
9127
  "data-shop-skeleton": true,
9009
9128
  "aria-hidden": "true",
9010
9129
  children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9011
- className: _dsh_plugin_shop_css_c4028823_default.skeletonCard,
9130
+ className: _dsh_plugin_shop_css_201c343a_default.skeletonCard,
9012
9131
  children: [
9013
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_c4028823_default.skeletonName }),
9014
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_c4028823_default.skeletonSummary }),
9015
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_c4028823_default.skeletonSummaryShort }),
9016
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_c4028823_default.skeletonActions })
9132
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.skeletonName }),
9133
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.skeletonSummary }),
9134
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.skeletonSummaryShort }),
9135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.skeletonActions })
9017
9136
  ]
9018
9137
  }, i))
9019
9138
  })]
@@ -9023,11 +9142,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9023
9142
  translate: "no",
9024
9143
  "data-shop-tab": true,
9025
9144
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9026
- className: _dsh_plugin_shop_css_c4028823_default.stateLine,
9145
+ className: _dsh_plugin_shop_css_201c343a_default.stateLine,
9027
9146
  children: t("error")
9028
9147
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9029
9148
  type: "button",
9030
- className: _dsh_plugin_shop_css_c4028823_default.actionButton,
9149
+ className: _dsh_plugin_shop_css_201c343a_default.actionButton,
9031
9150
  onClick: () => {
9032
9151
  setRequest({ kind: "retry" });
9033
9152
  setVersionReload((current) => current + 1);
@@ -9042,10 +9161,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9042
9161
  "data-shop-tab": true,
9043
9162
  children: [
9044
9163
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9045
- className: _dsh_plugin_shop_css_c4028823_default.toolbar,
9164
+ className: _dsh_plugin_shop_css_201c343a_default.toolbar,
9046
9165
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
9047
9166
  type: "search",
9048
- className: _dsh_plugin_shop_css_c4028823_default.searchInput,
9167
+ className: _dsh_plugin_shop_css_201c343a_default.searchInput,
9049
9168
  "aria-label": t("search"),
9050
9169
  placeholder: t("search"),
9051
9170
  value: query,
@@ -9054,27 +9173,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9054
9173
  setVisibleCount(48);
9055
9174
  }
9056
9175
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9057
- className: _dsh_plugin_shop_css_c4028823_default.toolbarRight,
9176
+ className: _dsh_plugin_shop_css_201c343a_default.toolbarRight,
9058
9177
  children: [
9059
9178
  result.stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9060
- className: _dsh_plugin_shop_css_c4028823_default.staleBadge,
9179
+ className: _dsh_plugin_shop_css_201c343a_default.staleBadge,
9061
9180
  children: t("staleLabel", { date: result.builtAt.slice(0, 10) })
9062
9181
  }),
9063
9182
  selfVersion !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9064
- className: _dsh_plugin_shop_css_c4028823_default.versionBlock,
9183
+ className: _dsh_plugin_shop_css_201c343a_default.versionBlock,
9065
9184
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9066
- className: _dsh_plugin_shop_css_c4028823_default.versionText,
9185
+ className: _dsh_plugin_shop_css_201c343a_default.versionText,
9067
9186
  "data-shop-version": true,
9068
9187
  children: ["v", selfVersion.installed]
9069
9188
  }), restartMonitor !== null ? null : selfPending && restartBlocked === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9070
9189
  type: "button",
9071
- className: _dsh_plugin_shop_css_c4028823_default.restartSelfButton,
9190
+ className: _dsh_plugin_shop_css_201c343a_default.restartSelfButton,
9072
9191
  "data-shop-restart": true,
9073
9192
  onClick: () => setSelfRestartGate(true),
9074
9193
  children: t("restart")
9075
9194
  }) : !selfPending && selfVersion.outdated && selfVersion.latest !== null && selfUpdate.view.kind === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9076
9195
  type: "button",
9077
- className: _dsh_plugin_shop_css_c4028823_default.updateSelfButton,
9196
+ className: _dsh_plugin_shop_css_201c343a_default.updateSelfButton,
9078
9197
  "data-shop-update-self": true,
9079
9198
  onClick: () => {
9080
9199
  if (selfVersion.latest !== null) selfUpdate.start({ version: selfVersion.latest });
@@ -9082,7 +9201,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9082
9201
  children: t("update")
9083
9202
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9084
9203
  type: "button",
9085
- className: _dsh_plugin_shop_css_c4028823_default.checkUpdateButton,
9204
+ className: _dsh_plugin_shop_css_201c343a_default.checkUpdateButton,
9086
9205
  "data-shop-check-update": true,
9087
9206
  title: t("checkUpdateTitle"),
9088
9207
  disabled: checkState !== "idle",
@@ -9091,7 +9210,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9091
9210
  })]
9092
9211
  }),
9093
9212
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
9094
- className: _dsh_plugin_shop_css_c4028823_default.githubLink,
9213
+ className: _dsh_plugin_shop_css_201c343a_default.githubLink,
9095
9214
  href: SHOP_REPO_URL,
9096
9215
  target: "_blank",
9097
9216
  rel: "noopener noreferrer",
@@ -9110,27 +9229,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9110
9229
  })]
9111
9230
  }),
9112
9231
  selfUpdate.view.kind === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9113
- className: _dsh_plugin_shop_css_c4028823_default.selfUpdatePanel,
9232
+ className: _dsh_plugin_shop_css_201c343a_default.selfUpdatePanel,
9114
9233
  "data-shop-self-updating": true,
9115
9234
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9116
- className: _dsh_plugin_shop_css_c4028823_default.installing,
9235
+ className: _dsh_plugin_shop_css_201c343a_default.installing,
9117
9236
  children: t(installPhaseKey(selfUpdate.view.phase))
9118
9237
  }), selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9119
- className: _dsh_plugin_shop_css_c4028823_default.log,
9238
+ className: _dsh_plugin_shop_css_201c343a_default.log,
9120
9239
  children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9121
- className: _dsh_plugin_shop_css_c4028823_default.logLine,
9240
+ className: _dsh_plugin_shop_css_201c343a_default.logLine,
9241
+ "data-shop-log-line": true,
9122
9242
  children: line
9123
9243
  }, index))
9124
9244
  })]
9125
9245
  }),
9126
9246
  selfPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9127
- className: _dsh_plugin_shop_css_c4028823_default.selfUpdatePanel,
9247
+ className: _dsh_plugin_shop_css_201c343a_default.selfUpdatePanel,
9128
9248
  "data-shop-self-update-done": true,
9129
9249
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9130
- className: _dsh_plugin_shop_css_c4028823_default.installedActions,
9250
+ className: _dsh_plugin_shop_css_201c343a_default.installedActions,
9131
9251
  children: [
9132
9252
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9133
- className: _dsh_plugin_shop_css_c4028823_default.notice,
9253
+ className: _dsh_plugin_shop_css_201c343a_default.notice,
9134
9254
  children: t("installedRestartNotice")
9135
9255
  }),
9136
9256
  restartBlocked === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
@@ -9143,7 +9263,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9143
9263
  }
9144
9264
  }),
9145
9265
  restartBlocked !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9146
- className: _dsh_plugin_shop_css_c4028823_default.notice,
9266
+ className: _dsh_plugin_shop_css_201c343a_default.notice,
9147
9267
  "data-shop-restart-disabled": true,
9148
9268
  children: t(restartBlockedNoticeKey(restartBlocked))
9149
9269
  })
@@ -9151,34 +9271,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9151
9271
  })
9152
9272
  }),
9153
9273
  selfUpdate.view.kind === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9154
- className: _dsh_plugin_shop_css_c4028823_default.selfUpdatePanel,
9274
+ className: _dsh_plugin_shop_css_201c343a_default.selfUpdatePanel,
9155
9275
  "data-shop-self-update-failed": true,
9156
9276
  children: [
9157
9277
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9158
- className: _dsh_plugin_shop_css_c4028823_default.failedHeading,
9278
+ className: _dsh_plugin_shop_css_201c343a_default.failedHeading,
9159
9279
  children: t("updateFailed")
9160
9280
  }),
9161
9281
  selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9162
- className: _dsh_plugin_shop_css_c4028823_default.log,
9282
+ className: _dsh_plugin_shop_css_201c343a_default.log,
9163
9283
  children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9164
- className: _dsh_plugin_shop_css_c4028823_default.logLine,
9284
+ className: _dsh_plugin_shop_css_201c343a_default.logLine,
9285
+ "data-shop-log-line": true,
9165
9286
  children: line
9166
9287
  }, index))
9167
9288
  }),
9168
9289
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9169
- className: _dsh_plugin_shop_css_c4028823_default.failedDetail,
9290
+ className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
9170
9291
  children: selfUpdate.view.detail === "" ? t("updateTransportFailed") : selfUpdate.view.detail
9171
9292
  })
9172
9293
  ]
9173
9294
  }),
9174
9295
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9175
- className: _dsh_plugin_shop_css_c4028823_default.categoryBar,
9296
+ className: _dsh_plugin_shop_css_201c343a_default.categoryBar,
9176
9297
  role: "group",
9177
9298
  "aria-label": t("catalog"),
9178
9299
  children: [
9179
9300
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9180
9301
  type: "button",
9181
- className: category === null ? `${_dsh_plugin_shop_css_c4028823_default.categoryButton} ${_dsh_plugin_shop_css_c4028823_default.categoryButtonOn}` : _dsh_plugin_shop_css_c4028823_default.categoryButton,
9302
+ className: category === null ? `${_dsh_plugin_shop_css_201c343a_default.categoryButton} ${_dsh_plugin_shop_css_201c343a_default.categoryButtonOn}` : _dsh_plugin_shop_css_201c343a_default.categoryButton,
9182
9303
  "aria-pressed": category === null,
9183
9304
  onClick: () => {
9184
9305
  setCategory(null);
@@ -9193,7 +9314,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9193
9314
  }),
9194
9315
  CATEGORY_ORDER.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9195
9316
  type: "button",
9196
- className: category === key ? `${_dsh_plugin_shop_css_c4028823_default.categoryButton} ${_dsh_plugin_shop_css_c4028823_default.categoryButtonOn}` : _dsh_plugin_shop_css_c4028823_default.categoryButton,
9317
+ className: category === key ? `${_dsh_plugin_shop_css_201c343a_default.categoryButton} ${_dsh_plugin_shop_css_201c343a_default.categoryButtonOn}` : _dsh_plugin_shop_css_201c343a_default.categoryButton,
9197
9318
  "aria-pressed": category === key,
9198
9319
  "data-category": key,
9199
9320
  "data-shop-category-tab": key,
@@ -9209,7 +9330,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9209
9330
  }, key)),
9210
9331
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9211
9332
  type: "button",
9212
- className: category === "installed" ? `${_dsh_plugin_shop_css_c4028823_default.categoryButton} ${_dsh_plugin_shop_css_c4028823_default.categoryButtonOn}` : _dsh_plugin_shop_css_c4028823_default.categoryButton,
9333
+ className: category === "installed" ? `${_dsh_plugin_shop_css_201c343a_default.categoryButton} ${_dsh_plugin_shop_css_201c343a_default.categoryButtonOn}` : _dsh_plugin_shop_css_201c343a_default.categoryButton,
9213
9334
  "aria-pressed": category === "installed",
9214
9335
  "data-shop-category-installed": true,
9215
9336
  onClick: () => {
@@ -9226,7 +9347,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9226
9347
  type: "button",
9227
9348
  role: "switch",
9228
9349
  "aria-checked": hideIncompatible,
9229
- className: hideIncompatible ? `${_dsh_plugin_shop_css_c4028823_default.incompatibleFilter} ${_dsh_plugin_shop_css_c4028823_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_c4028823_default.incompatibleFilter,
9350
+ className: hideIncompatible ? `${_dsh_plugin_shop_css_201c343a_default.incompatibleFilter} ${_dsh_plugin_shop_css_201c343a_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_201c343a_default.incompatibleFilter,
9230
9351
  title: t(hideIncompatible ? "showIncompatibleTitle" : "incompatibleFilterTitle"),
9231
9352
  "data-shop-hide-incompatible": true,
9232
9353
  onClick: () => {
@@ -9234,18 +9355,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9234
9355
  setVisibleCount(48);
9235
9356
  },
9236
9357
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9237
- className: hideIncompatible ? `${_dsh_plugin_shop_css_c4028823_default.switch} ${_dsh_plugin_shop_css_c4028823_default.switchOn}` : _dsh_plugin_shop_css_c4028823_default.switch,
9358
+ className: hideIncompatible ? `${_dsh_plugin_shop_css_201c343a_default.switch} ${_dsh_plugin_shop_css_201c343a_default.switchOn}` : _dsh_plugin_shop_css_201c343a_default.switch,
9238
9359
  "aria-hidden": "true",
9239
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_c4028823_default.switchKnob })
9360
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.switchKnob })
9240
9361
  }), t("hideIncompatible", { count: incompatibleCount })]
9241
9362
  })
9242
9363
  ]
9243
9364
  }),
9244
9365
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9245
- className: _dsh_plugin_shop_css_c4028823_default.catalogStatsRow,
9366
+ className: _dsh_plugin_shop_css_201c343a_default.catalogStatsRow,
9246
9367
  children: [
9247
9368
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9248
- className: _dsh_plugin_shop_css_c4028823_default.catalogStats,
9369
+ className: _dsh_plugin_shop_css_201c343a_default.catalogStats,
9249
9370
  "data-shop-catalog-stats": true,
9250
9371
  children: t("catalogStats", {
9251
9372
  count: String(browsable.length),
@@ -9254,7 +9375,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9254
9375
  }),
9255
9376
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9256
9377
  type: "button",
9257
- className: _dsh_plugin_shop_css_c4028823_default.catalogRefreshButton,
9378
+ className: _dsh_plugin_shop_css_201c343a_default.catalogRefreshButton,
9258
9379
  "data-shop-catalog-refresh": true,
9259
9380
  disabled: reloading,
9260
9381
  onClick: () => {
@@ -9266,21 +9387,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9266
9387
  children: reloading ? t("refreshing") : t("refresh")
9267
9388
  }),
9268
9389
  reloadFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9269
- className: _dsh_plugin_shop_css_c4028823_default.catalogRefreshFailed,
9390
+ className: _dsh_plugin_shop_css_201c343a_default.catalogRefreshFailed,
9270
9391
  "data-shop-catalog-refresh-failed": true,
9271
9392
  children: t("refreshFailed")
9272
9393
  })
9273
9394
  ]
9274
9395
  }),
9275
9396
  result.plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9276
- className: _dsh_plugin_shop_css_c4028823_default.emptyLine,
9397
+ className: _dsh_plugin_shop_css_201c343a_default.emptyLine,
9277
9398
  children: t("empty")
9278
9399
  }) : filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9279
- className: _dsh_plugin_shop_css_c4028823_default.emptyLine,
9400
+ className: _dsh_plugin_shop_css_201c343a_default.emptyLine,
9280
9401
  "data-shop-empty": true,
9281
9402
  children: matched.length > 0 ? t("emptyIncompatibleFiltered") : t("emptySearch")
9282
9403
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
9283
- className: _dsh_plugin_shop_css_c4028823_default.cards,
9404
+ className: _dsh_plugin_shop_css_201c343a_default.cards,
9284
9405
  children: [visible.map((entry) => {
9285
9406
  const key = entryKey(entry);
9286
9407
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
@@ -9301,12 +9422,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9301
9422
  }) }, key);
9302
9423
  }), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
9303
9424
  ref: sentinelRef,
9304
- className: _dsh_plugin_shop_css_c4028823_default.cardsSentry,
9425
+ className: _dsh_plugin_shop_css_201c343a_default.cardsSentry,
9305
9426
  "data-shop-sentry": true,
9306
9427
  "aria-hidden": "true"
9307
9428
  })]
9308
9429
  }), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9309
- className: _dsh_plugin_shop_css_c4028823_default.showingLine,
9430
+ className: _dsh_plugin_shop_css_201c343a_default.showingLine,
9310
9431
  "aria-live": "polite",
9311
9432
  children: t("showing", {
9312
9433
  shown: String(visibleCount),
@@ -9377,7 +9498,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9377
9498
  en
9378
9499
  }), "dsh-plugin-shop: dictionaries");
9379
9500
  const dispose = await ctx.remote.$mount(TYPERT_REMOTE);
9380
- ctx.effect(() => dispose, "dsh-plugin-shop: shop remote mount");
9501
+ let withdrawRestart = () => {};
9502
+ ctx.effect(() => () => {
9503
+ withdrawRestart();
9504
+ return dispose();
9505
+ }, "dsh-plugin-shop: shop remote mount");
9381
9506
  const ns = ctx.get("remote.shop");
9382
9507
  if (ns === void 0) throw new Error("dsh-plugin-shop: the shop remote did not register (service \"remote.shop\" missing after $mount)");
9383
9508
  const unwrap = (result) => {
@@ -9427,6 +9552,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9427
9552
  ...result,
9428
9553
  incompatible: await refineAgainstModuleTable(result.incompatible ?? {}, ctx.get("modules"), pageRemoved)
9429
9554
  });
9555
+ const restart = async () => unwrap(await ns.restart());
9556
+ withdrawRestart = registerRestartCarrier(restart);
9430
9557
  const injected = () => ({
9431
9558
  catalog: async (args) => handOver(await hostCatalog(args)),
9432
9559
  install: async (args) => {
@@ -9444,7 +9571,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9444
9571
  noteUninstalled: (name) => {
9445
9572
  pageRemoved.add(name);
9446
9573
  },
9447
- restart: async () => unwrap(await ns.restart()),
9574
+ restart,
9448
9575
  version: async () => unwrap(await ns.version()),
9449
9576
  updateStart: async (args) => {
9450
9577
  warmCatalog = null;