dsh-plugin-shop 0.8.7-beta.1 → 0.8.7-beta.2

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
@@ -6483,10 +6483,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6483
6483
  return blockerBadgeKey(blockers) === "incompatibleBadge";
6484
6484
  }
6485
6485
  /**
6486
- * Whether this install is refused before it starts, so its button is
6487
- * disabled rather than opening the acknowledgement gate for an install that
6488
- * cannot proceed: by the host, for a taken name, or by the running dsh, for
6489
- * its harness peers.
6486
+ * Whether this install is refused before it starts, so the card shows the
6487
+ * refused label where its button would be, rather than a button for an
6488
+ * install that cannot proceed: refused by the host, for a taken name, or by
6489
+ * the running dsh, for its harness peers.
6490
6490
  *
6491
6491
  * The second is the one harness reason that blocks (design
6492
6492
  * 2026-09-26-dsh-017-readiness, B1, amending "warn, never block"): dsh 0.1.7
@@ -6494,9 +6494,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6494
6494
  * entry after it, rolling the profile back, and the shop cannot install past
6495
6495
  * it. The verdict is dsh's own rule on the profile's own exemptions, whichever
6496
6496
  * channel the entry comes from (design
6497
- * 2026-09-28-bundle-components-and-github-peers, section 7.3), so a disabled
6498
- * button is dsh's answer given early, never the shop's opinion — and
6499
- * recording the exemption the card shows is what enables it again.
6497
+ * 2026-09-28-bundle-components-and-github-peers, section 7.3), so the label
6498
+ * is dsh's answer given early, never the shop's opinion — and recording the
6499
+ * exemption the card shows is what brings the button back.
6500
6500
  */
6501
6501
  function refusesInstall(blockers) {
6502
6502
  return blockers.some((blocker) => blocker.kind === "name-taken" || blocker.kind === "harness-peers");
@@ -6532,11 +6532,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6532
6532
  if (spec === void 0) return void 0;
6533
6533
  return specVerdict(entry, spec) === "same" ? void 0 : holderLabel(spec, entry.name);
6534
6534
  }
6535
- /** Spec §9.3 verbatim — the community-tier acknowledgement. The zh dictionary
6536
- * states the same facts in its own register; never soften this wording. */
6537
- const ACKNOWLEDGEMENT_EN = "Once installed, this plugin holds the same privileges as a built-in one: reading and writing your files, running shell commands, and reading and modifying the requests sent to the model. It has not been reviewed.";
6538
- /** Spec §9.3 in the zh register — the same facts, the same force. */
6539
- const ACKNOWLEDGEMENT_ZH = "安装后,此插件将拥有与内置插件相同的权限:读写你的文件、执行 shell 命令,以及读取和修改发送给模型的请求。它未经审核。";
6535
+ /** The disclosure every install now stands under (spec §9.3, amended
6536
+ * 2026-10-14). It used to be the acknowledgement dialog's body: the gate asked
6537
+ * a reader to accept THESE privileges before each community install. The gate
6538
+ * is gone, and the same facts stand instead as one quiet line on the page, so
6539
+ * they cannot be missed and cannot be dismissed unread.
6540
+ *
6541
+ * Stated in the general case — "a plugin", not "this plugin" — because it now
6542
+ * describes the tier system rather than one entry, and because the community
6543
+ * tier is never badged on a card: naming a badge that is not rendered would
6544
+ * send a reader looking for it. */
6545
+ const DISCLOSURE_EN = "Once installed, a plugin holds the same privileges as a built-in one: reading and writing your files, running shell commands, and reading and modifying the requests sent to the model. Plugins not marked Verified have not been reviewed.";
6546
+ /** The same facts in the zh register, with the same force. */
6547
+ const DISCLOSURE_ZH = "安装后,插件拥有与内置插件相同的权限:读写你的文件、执行 shell 命令,以及读取和修改发送给模型的请求。未标「已审核」的插件未经审核。";
6540
6548
  /** Install rejection code → locale key, for the rejected-state code label
6541
6549
  * (§7.2): every rejection the host can return has a human label here. */
6542
6550
  function rejectionCodeKey(code) {
@@ -7064,11 +7072,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7064
7072
  archivedBadge: "已归档",
7065
7073
  capabilitiesNote: "作者自述,无强制约束",
7066
7074
  install: "安装",
7067
- confirm: "确认",
7068
7075
  cancel: "取消",
7069
7076
  dismiss: "知道了",
7070
- acknowledgementTitle: "需要确认",
7071
- acknowledgementBody: ACKNOWLEDGEMENT_ZH,
7077
+ /** What every install now stands under, on the page rather than in a dialog
7078
+ * (spec §9.2/§9.3, amended 2026-10-14). */
7079
+ disclosure: DISCLOSURE_ZH,
7080
+ /** The fold toggle under a card's collapsed red block. `aria-expanded` plus:
7081
+ * open shows the whole block; closed shows two lines of it. This toggle is
7082
+ * independent of the card's own header-expand. */
7083
+ moreRedLines: "展开",
7084
+ lessRedLines: "收起",
7072
7085
  installing: "正在安装…",
7073
7086
  downloading: "正在下载…",
7074
7087
  installedRestartNotice: "已安装;重启 dsh 后生效",
@@ -7080,8 +7093,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7080
7093
  * "unsupported" leaves the reader guessing which side is off. */
7081
7094
  harnessRangeDetail: "作者声明支持的 dsh 版本:{range};本机运行的是 {running}。",
7082
7095
  harnessProfileDetail: "作者声明支持的 profile:{declared};此 dsh 以 {running} profile 启动。",
7083
- /** dsh's OWN refusal (0.1.7 on), not the author's claim: the install button
7084
- * is disabled beside it (design 2026-09-26-dsh-017-readiness, B1). `{peers}`
7096
+ /** dsh's OWN refusal (0.1.7 on), not the author's claim: the card shows the
7097
+ * refused label in the install button's place (design
7098
+ * 2026-09-26-dsh-017-readiness, B1, amended 2026-10-14). `{peers}`
7085
7099
  * is each refused peer with the range the package declared. The remedy
7086
7100
  * leads into the exemption command, rendered on its own line after it, and
7087
7101
  * names the Refresh button by its label. */
@@ -7245,11 +7259,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7245
7259
  tierCommunity: "Community",
7246
7260
  capabilitiesNote: "self-declared by the author, not enforced",
7247
7261
  install: "Install",
7248
- confirm: "Confirm",
7249
7262
  cancel: "Cancel",
7250
7263
  dismiss: "Dismiss",
7251
- acknowledgementTitle: "Acknowledgement required",
7252
- acknowledgementBody: ACKNOWLEDGEMENT_EN,
7264
+ disclosure: DISCLOSURE_EN,
7265
+ moreRedLines: "Show more",
7266
+ lessRedLines: "Show less",
7253
7267
  installing: "Installing…",
7254
7268
  downloading: "Downloading…",
7255
7269
  installedRestartNotice: "installed; restart dsh to activate",
@@ -7861,6 +7875,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7861
7875
  */
7862
7876
  function useKeyedFlows(begin, installStatus, onSettled) {
7863
7877
  const [views, setViews] = (0, react.useState)(() => /* @__PURE__ */ new Map());
7878
+ const [starting, setStarting] = (0, react.useState)(() => /* @__PURE__ */ new Set());
7879
+ const inFlight = (0, react.useRef)(/* @__PURE__ */ new Set());
7864
7880
  const settled = (0, react.useRef)(onSettled);
7865
7881
  settled.current = onSettled;
7866
7882
  const put = (0, react.useCallback)((key, view) => {
@@ -7881,8 +7897,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7881
7897
  });
7882
7898
  }, []);
7883
7899
  const start = (0, react.useCallback)(async (key, args) => {
7884
- put(key, { kind: "idle" });
7885
- put(key, await begin(args));
7900
+ if (inFlight.current.has(key)) return;
7901
+ inFlight.current.add(key);
7902
+ setStarting((current) => new Set(current).add(key));
7903
+ try {
7904
+ put(key, { kind: "idle" });
7905
+ put(key, await begin(args));
7906
+ } finally {
7907
+ inFlight.current.delete(key);
7908
+ setStarting((current) => {
7909
+ if (!current.has(key)) return current;
7910
+ const next = new Set(current);
7911
+ next.delete(key);
7912
+ return next;
7913
+ });
7914
+ }
7886
7915
  }, [begin, put]);
7887
7916
  const reset = (0, react.useCallback)((key) => {
7888
7917
  setViews((current) => {
@@ -7946,10 +7975,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7946
7975
  };
7947
7976
  }
7948
7977
  const view = views.get(key) ?? IDLE_VIEW;
7978
+ const isStarting = starting.has(key);
7949
7979
  const cached = flowCache.current.get(key);
7950
- if (cached !== void 0 && cached.view === view) return cached;
7980
+ if (cached !== void 0 && cached.view === view && cached.starting === isStarting) return cached;
7951
7981
  const flow = {
7952
7982
  view,
7983
+ starting: isStarting,
7953
7984
  start: (args) => start(key, args),
7954
7985
  reset: () => reset(key)
7955
7986
  };
@@ -7957,6 +7988,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7957
7988
  return flow;
7958
7989
  }, [
7959
7990
  views,
7991
+ starting,
7960
7992
  start,
7961
7993
  reset
7962
7994
  ]),
@@ -9215,132 +9247,232 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9215
9247
  };
9216
9248
  }
9217
9249
  //#endregion
9218
- //#region \0dsh-plugin-shop-css:d74581c2
9219
- 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.s73a3ff_client.s45aee2_config.s8a51a7_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.s73a3ff_client.s117e2f_spec.s8a51a7_ts` pins this. */\n\n.sa2d7b5_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 `.s7c10d7_searchInput` sits at\n * exactly (0, 0) of this box — it is the first child of `.sbee2c3_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.sa89499_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.s90f53b_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.sd5b66e_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.sfb41cd_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.sfb41cd_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.sc9bc63_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.s75708b_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.s772f28_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.s6ce39d_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.sfb41cd_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 .sfb41cd_skeletonCard span { animation: none; opacity: 0.75; }\n .sfb41cd_skeletonCard::after { content: none; }\n}\n\n.s1547e4_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.s1547e4_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.s1547e4_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sbee2c3_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.sfb98ad_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.s15df74_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s9d78e5_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.scb0503_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.scb0503_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.scb0503_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.seab5ae_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.seab5ae_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.seab5ae_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.seab5ae_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.s6932e1_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.s6932e1_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.s6932e1_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.sb1e444_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.s7c10d7_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.s7c10d7_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* Subdued line under the search box when a dictionary intent fired (design\n * 2026-10-08-search-query-expansion §4). One fact, no badge. */\n.s1dee54_expansionNote {\n margin: 4px 0 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s5823b0_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.s52084f_categoryBar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.sd07f01_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.sd07f01_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 `.sd07f01_categoryButton.s7d231e_categoryButtonOn` — TWO classes — so it\n * matches `.sd07f01_categoryButton:hover`'s specificity and, coming after it, wins.\n * As a bare `.s7d231e_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.s73a3ff_client.s117e2f_spec.s8a51a7_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.sd07f01_categoryButton.s7d231e_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 .sd07f01_categoryButton.s7d231e_categoryButtonOn {\n outline: 2px solid ButtonText;\n outline-offset: -4px;\n }\n}\n\n.sd07f01_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 `.sd07f01_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.s7abba7_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: `.s7abba7_incompatibleFilter:hover .sa4bed2_switch` outranks `.s97d8ec_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.s7abba7_incompatibleFilter:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n.s7abba7_incompatibleFilter:hover .sa4bed2_switch:not(.s97d8ec_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 `.sd07f01_categoryButton.s7d231e_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.s7abba7_incompatibleFilter.sa24b4e_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.s7abba7_incompatibleFilter .sa4bed2_switch:not(.s97d8ec_switchOn) .s24e1f5_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.s7abba7_incompatibleFilter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 999px;\n}\n\n.s3ffb2a_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.sfd3ed8_catalogStatsRow {\n display: flex;\n align-items: center;\n /* The row holds the catalog line, the refresh button and the shelf order's\n * three pills (design 2026-10-12-shelf-orders §4). Without wrapping, the\n * last of them overflows the tab at a narrow width. */\n flex-wrap: wrap;\n gap: 10px;\n margin: 6px 0 0;\n}\n\n/* The shelf's order control, at the right end of the stats row. `margin-left:\n * auto` is what puts it there while the line and the refresh button stay\n * left — and it is safe on a wrapping row, unlike a width. */\n.sc5a073_sortGroup {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-left: auto;\n}\n\n.s275eb9_sortLabel {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.s3c7a5d_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 `.s1547e4_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.s6d141d_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.s6d141d_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.s6d141d_catalogRefreshButton:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.s6d141d_catalogRefreshButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s8b8ed4_catalogRefreshFailed {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s918b21_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.sf66d65_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 .sf66d65_cards { animation: none; }\n}\n\n.sa6c0bf_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.s21cef7_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s9babb8_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa6c0bf_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 .sa6c0bf_card { transition: none; }\n .sa6c0bf_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.sce2063_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. `.sd07f01_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.sa6c0bf_card[data-category='tool'], .sd07f01_categoryButton[data-category='tool'] { --category-hue: #4C8DFF; }\n.sa6c0bf_card[data-category='provider'], .sd07f01_categoryButton[data-category='provider'] { --category-hue: #A78BFA; }\n.sa6c0bf_card[data-category='ui'], .sd07f01_categoryButton[data-category='ui'] { --category-hue: #2DD4BF; }\n.sa6c0bf_card[data-category='workflow'], .sd07f01_categoryButton[data-category='workflow'] { --category-hue: #F59E0B; }\n.sa6c0bf_card[data-category='integration'], .sd07f01_categoryButton[data-category='integration'] { --category-hue: #34D399; }\n.sa6c0bf_card[data-category='theme'], .sd07f01_categoryButton[data-category='theme'] { --category-hue: #F472B6; }\n.sa6c0bf_card[data-category='other'], .sd07f01_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.sdc7cd8_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.s6ae6ba_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.s6ae6ba_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.saf3e98_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.s93d243_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.s763b75_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.s763b75_cardActions > .sff1df6_gate,\n.s763b75_cardActions > .s6d7bee_installPanel,\n.s763b75_cardActions > .s10ddb2_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.s9d1d9e_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 /* A release tag prints as tagged, never cut to seven characters (market\n * borrowings §11), and a tag can run to any length. The cap keeps every\n * tag of up to fourteen characters whole, prerelease suffix included\n * (`v0.1.0-alpha.0`): fourteen glyphs plus the letter-spacing above come to\n * about 14.5ch, so the cap is 15ch, not 14. A longer tag ends in an ellipsis,\n * and the tooltip carries the whole of it. */\n max-width: 15ch;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* The octocat marking a source the reader can read on GitHub, beside the\n * card's version. */\n.sad456f_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s7f352e_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s7f352e_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.s7f352e_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.s7f352e_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/* The §C10 shadowed badge: an installed github entry the catalog refuses\n * shadowed-by-npm. Subdued/secondary rather than a warn tint — nothing is\n * wrong with the install itself; the shelf just cannot show it. */\n.s7f352e_tierBadge[data-tier='shadowed'] {\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/* The §C5 archived badge: a github repository the owner has frozen.\n * Subdued register, same as shadowed — nothing is wrong with the install;\n * the pinned commit still serves, so the mark is informational only. */\n.s7f352e_tierBadge[data-tier='archived'] {\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.s649a66_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.se83753_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sc7129d_chevron, .s3e170e_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s3e170e_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .sc7129d_chevron, .s3e170e_chevronOpen { transition: none; }\n}\n\n.s249e15_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sdd4d7a_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.s1a6809_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.sc924dd_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sab7503_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.s4625f5_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.s632e71_remedy {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s360025_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.s12ec11_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.sa92311_refusal {\n display: flex;\n flex-direction: column;\n gap: 6px;\n flex-basis: 100%;\n}\n\n.s99a4f5_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.scf0a21_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sc760c8_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.sc760c8_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.s48edfc_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.sce5502_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.scc4b2b_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.scc4b2b_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.scc4b2b_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.scc4b2b_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.scc4b2b_detailRow a:hover {\n text-decoration: underline;\n}\n\n.scc4b2b_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 * `.s763b75_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.s8fb80a_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: `.s8fb80a_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 *\n * `.s72b045_shelfDate` shares this rule because it is the same kind of fact — a\n * fixed-width figure a reader compares down the shelf (design\n * 2026-10-12-shelf-orders §4) — and two copies of these declarations would\n * drift. */\n.s24611f_size,\n.s72b045_shelfDate {\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 `.s8fb80a_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 `.s8fb80a_cardMeta`. There is no middot:\n * `.s8fb80a_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.seefbaa_author {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sa79085_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.s6d7bee_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.sdddc5f_installButton, .safd4a8_confirmButton, .sfcf768_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.sdddc5f_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.sdddc5f_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.safd4a8_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.safd4a8_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sfcf768_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.sfcf768_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.sdddc5f_installButton:focus-visible, .safd4a8_confirmButton:focus-visible, .sfcf768_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.s563674_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.s10ddb2_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.s767a97_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.s767a97_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s767a97_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.sa6b105_restartSelfButton {\n /* The version row's third state. Geometry matches .seab5ae_checkUpdateButton and\n * .scb0503_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.sa6b105_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.s426aa0_restartButton,\n.sa283f9_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.s426aa0_restartButton:hover,\n.sa283f9_reloadButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s426aa0_restartButton:focus-visible,\n.sa283f9_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 * `.s10ddb2_installedActions` the way the restart gate is — a column of its own\n * rather than inline with the row. */\n.sf20a99_reloadPanel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n\n.sff1df6_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.see0e3d_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.sd04ef9_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s7fd5d3_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.sf6b0aa_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.sa32b72_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf9b6a4_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.s9e4d00_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s7cd7db_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.sfb1bae_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.s9591b1_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.saa0605_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.sbd5c7c_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.s7c248d_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.se144d3_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s6fbe58_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.s8af496_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s8af496_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.sf82d51_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 .s93d243_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/* The §C10 shadowed section. Reuses the outdated list's visual language; only\n * the section spacing and the per-row explanation paragraph are its own. */\n.s31b89a_shadowedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.scf838f_shadowedDetail {\n margin: 4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s109c95_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: `.s7abba7_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 `.sa4bed2_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.sa4bed2_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.s97d8ec_switchOn {\n border-color: var(--track-hue);\n background: color-mix(in srgb, var(--track-hue) 55%, transparent);\n}\n\n.s24e1f5_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.s97d8ec_switchOn .s24e1f5_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.sa4bed2_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 .sa4bed2_switch, .s24e1f5_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.s5c21f4_switchWrap {\n display: inline-flex;\n align-items: center;\n /* Same reason as `.s109c95_outdatedActions`: `.sf20a99_reloadPanel`'s own comment calls\n * itself \"a column of its own rather than inline with the row\", which held\n * for `.s10ddb2_installedActions` and not here until this line. */\n flex-wrap: wrap;\n gap: 8px;\n}\n";
9220
- if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"d74581c2\"]")) {
9250
+ //#region src/client/useFoldableRedBlock.ts
9251
+ /**
9252
+ * The fold under a card's whole red block: two lines by default, a small pill
9253
+ * that expands it when there is more, and the whole block again on a second
9254
+ * press. Three rules keep the fold honest:
9255
+ *
9256
+ * - It is measured, not declared. The toggle is offered only where the block
9257
+ * itself shows it holds more than the clamp lets through —
9258
+ * `scrollHeight > clientHeight`, read while the block is collapsed. A count
9259
+ * of lines or characters would lie on exactly the narrow window the fold
9260
+ * matters most for.
9261
+ * - It is independent of the card's own expand. The header's chevron opens
9262
+ * the author description; the pill opens the red block.
9263
+ * - It is visual only. The label's accessible name and tooltip carry the
9264
+ * WHOLE block, folded or open, so a screen reader meets the same facts
9265
+ * either way.
9266
+ *
9267
+ * The measure runs wherever its answer can change: at mount, on every
9268
+ * collapse, when the block's box resizes (a ResizeObserver: the panel
9269
+ * narrowing, a font arriving), and when its text changes (a MutationObserver:
9270
+ * a verdict that lands after the first paint, or one a refresh withdraws).
9271
+ * Both observers are needed. A block already clamped at two lines keeps its
9272
+ * height when its content grows, so a ResizeObserver alone never hears the
9273
+ * line that pushes it over; and a block that first mounts with no box (a
9274
+ * hidden ancestor) changes no text when it is shown.
9275
+ *
9276
+ * While the block is open nothing is measured: an open block is as tall as
9277
+ * its content and fits by construction. Reading that as "nothing to fold" is
9278
+ * what once let the press that opened the fold unmount its own toggle.
9279
+ */
9280
+ function useFoldableRedBlock() {
9281
+ const rootRef = (0, react.useRef)(null);
9282
+ const [expandable, setExpandable] = (0, react.useState)(false);
9283
+ const [expanded, setExpanded] = (0, react.useState)(false);
9284
+ const expandedRef = (0, react.useRef)(false);
9285
+ const measure = (0, react.useCallback)(() => {
9286
+ const el = rootRef.current;
9287
+ if (el === null || expandedRef.current) return;
9288
+ setExpandable(el.scrollHeight > el.clientHeight);
9289
+ }, []);
9290
+ (0, react.useEffect)(() => {
9291
+ if (!expanded) measure();
9292
+ }, [expanded, measure]);
9293
+ (0, react.useEffect)(() => {
9294
+ const el = rootRef.current;
9295
+ if (el === null) return;
9296
+ const observers = [];
9297
+ if (typeof ResizeObserver !== "undefined") {
9298
+ const resize = new ResizeObserver(measure);
9299
+ resize.observe(el);
9300
+ observers.push(resize);
9301
+ }
9302
+ if (typeof MutationObserver !== "undefined") {
9303
+ const mutation = new MutationObserver(measure);
9304
+ mutation.observe(el, {
9305
+ childList: true,
9306
+ subtree: true,
9307
+ characterData: true
9308
+ });
9309
+ observers.push(mutation);
9310
+ }
9311
+ return () => {
9312
+ for (const observer of observers) observer.disconnect();
9313
+ };
9314
+ }, [measure]);
9315
+ return {
9316
+ rootRef,
9317
+ expandable,
9318
+ expanded,
9319
+ toggle: () => {
9320
+ expandedRef.current = !expandedRef.current;
9321
+ setExpanded(expandedRef.current);
9322
+ }
9323
+ };
9324
+ }
9325
+ //#endregion
9326
+ //#region \0dsh-plugin-shop-css:5f753521
9327
+ 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.sa85938_client.s94d4e5_config.s91da2e_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.sa85938_client.sbb5706_spec.s91da2e_ts` pins this. */\n\n.s6a01b8_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n /* The two reds every red text in this tab is painted with. dsh's error red\n * is #ec1313 in the light theme, which measures 4.498:1 as text on white —\n * a miss of two thousandths — and 3.57:1 on the badge's own 14% red fill\n * (4.00:1 in dark). Mixing it toward label-primary fixes both, because\n * label-primary is the theme's foreground pole: it darkens the red on light\n * surfaces and lightens it on dark ones. Measured in the live app: 90% of\n * the red is 5.30:1 on white and 5.29:1 on the dark card, and 80% is\n * 4.98:1 on the badge's tint, 4.92:1 in dark. Computed: 90% is 4.69:1 on\n * the dark panel itself (#2c2c2e), where the self-update's failure heading\n * sits, and 80% is 5.23:1 on the uninstall button's 10% hover fill.\n * Declared here, on the tab root, because a custom property inherits: one\n * declaration reaches every red element, and a new one cannot be\n * forgotten. */\n --shop-error: color-mix(in srgb, var(--dsw-alias-state-error-primary) 90%, var(--dsw-alias-label-primary));\n --shop-error-on-tint: color-mix(in srgb, var(--dsw-alias-state-error-primary) 80%, var(--dsw-alias-label-primary));\n /* The amber and the green, mixed the same way for the same reason. dsh's\n * warn #f59e0b and success #22c55e are one value in both themes and pass\n * in the dark one, but as text on white they measure 2.15:1 and 2.28:1.\n * Each derived colour keeps the largest multiple of ten of its hue that\n * clears every surface it paints, as the reds do. Computed from the live\n * tokens over every bg layer in both themes: amber at 60% is 4.95:1 on a\n * plain surface but 4.44:1 on its own 14% tint, so text on an amber tint\n * takes 50% (5.66:1); green at 60% is 5.18:1 plain and 4.60:1 on its 14%\n * tint, so one green serves both. */\n --shop-warn: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 60%, var(--dsw-alias-label-primary));\n --shop-warn-on-tint: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 50%, var(--dsw-alias-label-primary));\n --shop-success: color-mix(in srgb, var(--dsw-alias-state-success-primary) 60%, var(--dsw-alias-label-primary));\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 `.sa16cbe_searchInput` sits at\n * exactly (0, 0) of this box — it is the first child of `.sbc1497_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.s352f7a_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.s60f2f5_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.s34d5f5_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s11e315_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.s11e315_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.s227d7c_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.s83025b_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.sdb7845_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.sa61117_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.s11e315_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 .s11e315_skeletonCard span { animation: none; opacity: 0.75; }\n .s11e315_skeletonCard::after { content: none; }\n}\n\n.s757132_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.s757132_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.s757132_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sbc1497_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s22d15d_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.s76d3c9_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s390e34_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.s79450a_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.s79450a_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.s79450a_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.sb48cbc_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.sb48cbc_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.sb48cbc_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.sb48cbc_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.s946346_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.s946346_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.s946346_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.s861599_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.sa16cbe_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.sa16cbe_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* Subdued line under the search box when a dictionary intent fired (design\n * 2026-10-08-search-query-expansion §4). One fact, no badge. */\n.seb8329_expansionNote {\n margin: 4px 0 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s31a2f0_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(--shop-warn-on-tint);\n font-size: 12px;\n}\n\n.sab4844_categoryBar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s512425_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.s512425_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 `.s512425_categoryButton.s598384_categoryButtonOn` — TWO classes — so it\n * matches `.s512425_categoryButton:hover`'s specificity and, coming after it, wins.\n * As a bare `.s598384_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: a pressed tab must occupy exactly the\n * same box as an unpressed one, so the bar does not reflow — and with ~9 pills\n * on a wrapping row, one that grows can push the rest onto another line and\n * move the tab out from under the pointer that just clicked it. That is what\n * `font-weight: 600` did (bold metrics are wider than regular), and it is why\n * nothing may be added here that resolves to a length: no padding, no\n * border-width, no font-size, no letter-spacing. `css-tokens.sa85938_client.sbb5706_spec.s91da2e_ts`\n * fails the build on one.\n *\n * Nor a second line. An inset ring once doubled this border, and every control\n * in the stylesheet now draws exactly one 1px line, which is also the thinnest\n * the host paints (Chromium draws 0.5px as 1px). What separates pressed from\n * unpressed is lightness instead of thickness: the border is the hue mixed 30%\n * toward label-primary, the theme's foreground pole, so it clears 3:1 against\n * the bar in both themes for every hue — the raw hue measured 1.86:1 on white\n * for `ui` — and the fill under it is 22% of the hue. The `other` tab is the\n * case this is for: its hue IS the neutral gray this file uses for \"no\n * category\", so on the light theme the hue alone barely separates the two. */\n.s512425_categoryButton.s598384_categoryButtonOn {\n border-color: color-mix(in srgb, var(--category-hue) 70%, var(--dsw-alias-label-primary));\n background: color-mix(in srgb, var(--category-hue) 22%, transparent);\n color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-label-primary) 55%);\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, so this mode alone gets an outline. */\n@media (forced-colors: active) {\n .s512425_categoryButton.s598384_categoryButtonOn {\n outline: 2px solid ButtonText;\n outline-offset: -4px;\n }\n}\n\n.s512425_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 `.s512425_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.s7ce437_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: `.s7ce437_incompatibleFilter:hover .s3a8382_switch` outranks `.s1e392e_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.s7ce437_incompatibleFilter:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n.s7ce437_incompatibleFilter:hover .s3a8382_switch:not(.s1e392e_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 `.s512425_categoryButton.s598384_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.s7ce437_incompatibleFilter.sd9fa8d_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.s7ce437_incompatibleFilter .s3a8382_switch:not(.s1e392e_switchOn) .s419ba1_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.s7ce437_incompatibleFilter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 999px;\n}\n\n.s320079_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.s923c82_catalogStatsRow {\n display: flex;\n align-items: center;\n /* The row holds the catalog line, the refresh button and the shelf order's\n * three pills (design 2026-10-12-shelf-orders §4). Without wrapping, the\n * last of them overflows the tab at a narrow width. */\n flex-wrap: wrap;\n gap: 10px;\n margin: 6px 0 0;\n}\n\n/* The shelf's order control, at the right end of the stats row. `margin-left:\n * auto` is what puts it there while the line and the refresh button stay\n * left — and it is safe on a wrapping row, unlike a width. */\n.s3edd9f_sortGroup {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-left: auto;\n}\n\n.s73feb1_sortLabel {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sad5aba_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/* The install disclosure: one grey line under the stats row, always on the\n * page. It carries what the §9.3 dialog used to say before every community\n * install, which is why it must not be dismissible or collapsed — a reader\n * who never installs anything still learns what installing means here.\n * Secondary label, the same register as the stats line it sits under: the\n * disclosure is a fact about the shelf, not a warning about one entry. It\n * wraps to a few lines at a narrow width, which the panel already allows. */\n.s58eb5b_disclosure {\n margin: 6px 0 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* Sits beside the 12px stats caption, so it is quieter than `.s757132_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.s4cca13_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.s4cca13_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.s4cca13_catalogRefreshButton:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.s4cca13_catalogRefreshButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sc24c97_catalogRefreshFailed {\n font-size: 11px;\n color: var(--shop-warn);\n}\n\n.s1aa6bf_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.sa036b0_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 .sa036b0_cards { animation: none; }\n}\n\n.s88b200_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.s853040_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s239c39_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s88b200_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 .s88b200_card { transition: none; }\n .s88b200_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.sc1c25e_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. `.s512425_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.s88b200_card[data-category='tool'], .s512425_categoryButton[data-category='tool'] { --category-hue: #4C8DFF; }\n.s88b200_card[data-category='provider'], .s512425_categoryButton[data-category='provider'] { --category-hue: #A78BFA; }\n.s88b200_card[data-category='ui'], .s512425_categoryButton[data-category='ui'] { --category-hue: #2DD4BF; }\n.s88b200_card[data-category='workflow'], .s512425_categoryButton[data-category='workflow'] { --category-hue: #F59E0B; }\n.s88b200_card[data-category='integration'], .s512425_categoryButton[data-category='integration'] { --category-hue: #34D399; }\n.s88b200_card[data-category='theme'], .s512425_categoryButton[data-category='theme'] { --category-hue: #F472B6; }\n.s88b200_card[data-category='other'], .s512425_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.s433c76_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.s6bc968_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.s6bc968_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.s704f0d_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.sba9311_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.s9b33f4_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.s9b33f4_cardActions > .sc99cff_gate,\n.s9b33f4_cardActions > .sbd0905_installPanel,\n.s9b33f4_cardActions > .sd562d9_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.s83e5d6_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 /* A release tag prints as tagged, never cut to seven characters (market\n * borrowings §11), and a tag can run to any length. The cap keeps every\n * tag of up to fourteen characters whole, prerelease suffix included\n * (`v0.1.0-alpha.0`): fourteen glyphs plus the letter-spacing above come to\n * about 14.5ch, so the cap is 15ch, not 14. A longer tag ends in an ellipsis,\n * and the tooltip carries the whole of it. */\n max-width: 15ch;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* The octocat marking a source the reader can read on GitHub, beside the\n * card's version. */\n.s93d06c_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s1fe784_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s1fe784_tierBadge[data-tier='verified'] {\n color: var(--shop-success);\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.s1fe784_tierBadge[data-tier='verified-stale'] {\n color: var(--shop-warn-on-tint);\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.s1fe784_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/* The §C10 shadowed badge: an installed github entry the catalog refuses\n * shadowed-by-npm. Subdued/secondary rather than a warn tint — nothing is\n * wrong with the install itself; the shelf just cannot show it. */\n.s1fe784_tierBadge[data-tier='shadowed'] {\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/* The §C5 archived badge: a github repository the owner has frozen.\n * Subdued register, same as shadowed — nothing is wrong with the install;\n * the pinned commit still serves, so the mark is informational only. */\n.s1fe784_tierBadge[data-tier='archived'] {\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.s2f456e_incompatibleBadge {\n flex-shrink: 0;\n padding: 3px 9px;\n border-radius: 999px;\n font-size: 12px;\n white-space: nowrap;\n /* On the tint, not on the plain surface: this text reads on its own 14% red\n * fill, where the 90% red would measure 4.21:1 (light) and 4.43:1 (dark). */\n color: var(--shop-error-on-tint);\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.sc6c968_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s287fda_chevron, .s20b60f_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s20b60f_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s287fda_chevron, .s20b60f_chevronOpen { transition: none; }\n}\n\n.s87fcee_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sf5d0d8_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.sa0923f_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.s20eea8_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sb53b34_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.sc2cc78_incompatibleDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre-line;\n color: var(--shop-error);\n}\n\n/* The way out of dsh's own refusal: the lead-in, then the exemption command\n * on its own line. */\n/* Block layout rather than flex: Firefox 153 will not line-clamp a container\n * that holds a display:flex child (Chromium 151 does), and the fold's\n * measured goal — \"the whole red block, remedy included, as one unit\" —\n * needs the container to clamp in BOTH engines the shop targets. The 4px\n * rhythm between the lead-in and the command is kept by the margins of the\n * children. */\n.sc7602c_remedy {\n display: block;\n}\n\n.sc7602c_remedy > * + * { margin-top: 4px; }\n\n/* The fold under a card's whole red block: two lines by default, a small\n * Refresh-pill-shaped toggle under it. Two containers, one rhythm: the shelf\n * uses `.s895dae_foldBlock` collapsed and `.s6b136d_foldBlockExpanded` open, and they share\n * one `display: block` (Firefox clamps no flex child inside a -webkit-box).\n * The clamp is on the BLOCK rather than any one line, so a 43-line remedy\n * counts as a unit instead of squashing the harness-peers line to a sliver\n * and hiding the command the reader actually came for. Both containers\n * still render the full block to a screen reader — `white-space: normal`\n * instead of pre-line keeps the copy's own `\\n` visible without forcing the\n * browser to lay the block's pieces like a text node. */\n.s895dae_foldBlock {\n display: block;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n display: -webkit-box;\n overflow: hidden;\n}\n\n.s6b136d_foldBlockExpanded { display: block; }\n\n/* The toggle. Geometry and quiet voice are the Refresh pill's\n * (`catalogStatsRow` right beside it on the same screen): one 1px border,\n * 11px text, secondary label, no fill. It is the only one of its kind here,\n * so a class of its own means one function of its own. */\n.s8c3792_foldToggle {\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.s8c3792_foldToggle:hover { border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2)); }\n.s8c3792_foldToggle:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }\n\n.s935bd9_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.se75f45_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 * refused label, the command that clears dsh's refusal included. Block,\n * rather than flex: Firefox will not line-clamp a -webkit-box that holds a\n * display:flex child, and the fold this element can sit inside of carries\n * that clamp. The 6px rhythm between the block's lines is carried by the\n * children's margins. */\n.se69fd7_refusal {\n display: block;\n flex-basis: 100%;\n}\n\n.se69fd7_refusal > * + * { margin-top: 6px; }\n\n.sb2edea_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.sa5c8b2_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sb95e1e_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.sb95e1e_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.s1f4436_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.sf6f153_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sbfe178_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sbfe178_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sbfe178_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.sbfe178_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sbfe178_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sbfe178_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 * `.s9b33f4_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.s92d907_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: `.s92d907_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 *\n * `.s6119f4_shelfDate` shares this rule because it is the same kind of fact — a\n * fixed-width figure a reader compares down the shelf (design\n * 2026-10-12-shelf-orders §4) — and two copies of these declarations would\n * drift. */\n.s5889e2_size,\n.s6119f4_shelfDate {\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 `.s92d907_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 `.s92d907_cardMeta`. There is no middot:\n * `.s92d907_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.s5efcbe_author {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sdd3957_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(--shop-warn-on-tint);\n}\n\n/* Install flow. */\n.sbd0905_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n/* ONE geometry for everything that occupies the install button's place: the\n * button itself, the confirm/cancel pair beside the restart gate, the installed\n * label, and the refused label. Stated once because a refused label HAS to look\n * like the button it replaces apart from its colour and its text, and four\n * copies of the same five declarations is how they stop looking alike.\n * `cursor: pointer` is NOT here: only the controls that can be pressed take it,\n * while a label renders with the default cursor. */\n.se20cee_installButton,\n.sdf88f9_confirmButton,\n.s330a03_cancelButton,\n.sad029f_installedLabel,\n.s7b7f2d_refusedLabel {\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}\n\n.se20cee_installButton, .sdf88f9_confirmButton, .s330a03_cancelButton {\n cursor: pointer;\n}\n\n.se20cee_installButton {\n border-color: var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n/* Between the click and the host's answer. The flow ignores a repeat start for\n * the same identity, so this is the visible half of the same fact: a control\n * that says \"not now\" rather than one that looks ready to send a second\n * install. Only the button can be in this state — a label is never pressed. */\n.se20cee_installButton:disabled {\n cursor: wait;\n}\n\n.se20cee_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sdf88f9_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.sdf88f9_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s330a03_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.s330a03_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.se20cee_installButton:focus-visible, .sdf88f9_confirmButton:focus-visible, .s330a03_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.sad029f_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/* The refused Install/Update button's replacement: the same box as the button\n * (the geometry rule above), filled solid red, not pressable. The fill is the\n * derived red rather than the raw token because text sits on it — bg-base on\n * `--shop-error` measures 5.31:1 light and 6.14:1 dark — and the raw error red\n * under bg-base text measures 4.498:1, the miss this whole family of fixes\n * exists for. Nothing here changes on hover: it is a statement, not a control,\n * so it keeps the default cursor and its colours. */\n.s7b7f2d_refusedLabel {\n border-color: var(--shop-error);\n background: var(--shop-error);\n color: var(--dsw-alias-bg-base);\n white-space: nowrap;\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.sd562d9_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.s1ef16e_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 /* The on-tint red: hovering this button paints a 10% red fill under the\n * word, where the plain red measures 4.50:1 (light) — the same two\n * thousandths short of the floor the raw token shipped at. */\n color: var(--shop-error-on-tint);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s1ef16e_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s1ef16e_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.s912ec7_restartSelfButton {\n /* The version row's third state. Geometry matches .sb48cbc_checkUpdateButton and\n * .s79450a_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.s912ec7_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.sa5d8c1_restartButton,\n.se5bd2c_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.sa5d8c1_restartButton:hover,\n.se5bd2c_reloadButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.sa5d8c1_restartButton:focus-visible,\n.se5bd2c_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 * `.sd562d9_installedActions` the way the restart gate is — a column of its own\n * rather than inline with the row. */\n.s76690e_reloadPanel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n\n.sc99cff_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.s58fe9b_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--shop-warn-on-tint);\n}\n\n.sd510f5_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s943dbd_gateWarning {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n white-space: pre-line;\n color: var(--shop-error);\n}\n\n.sb2e35c_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s407016_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s56c1a5_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.s3d7aca_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s3e1ea0_notice {\n margin: 0;\n font-size: 12px;\n color: var(--shop-success);\n}\n\n.s71965d_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--shop-error);\n}\n\n.s41bb43_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s3b880d_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--shop-error);\n}\n\n.s465924_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.sfccf6f_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.s403f9e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.sd9cb61_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.s7b2d89_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s7b2d89_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.sc33878_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 .sba9311_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/* The §C10 shadowed section. Reuses the outdated list's visual language; only\n * the section spacing and the per-row explanation paragraph are its own. */\n.sa88998_shadowedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.s98799e_shadowedDetail {\n margin: 4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n}\n\n.saaf7ce_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: `.s7ce437_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 `.s3a8382_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.s3a8382_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.s1e392e_switchOn {\n border-color: var(--track-hue);\n background: color-mix(in srgb, var(--track-hue) 55%, transparent);\n}\n\n.s419ba1_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.s1e392e_switchOn .s419ba1_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s3a8382_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 .s3a8382_switch, .s419ba1_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.saa9c93_switchWrap {\n display: inline-flex;\n align-items: center;\n /* Same reason as `.saaf7ce_outdatedActions`: `.s76690e_reloadPanel`'s own comment calls\n * itself \"a column of its own rather than inline with the row\", which held\n * for `.sd562d9_installedActions` and not here until this line. */\n flex-wrap: wrap;\n gap: 8px;\n}\n";
9328
+ if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"5f753521\"]")) {
9221
9329
  const tag = document.createElement("style");
9222
- tag.dataset.pluginCss = "d74581c2";
9330
+ tag.dataset.pluginCss = "5f753521";
9223
9331
  tag.textContent = css;
9224
9332
  document.head.appendChild(tag);
9225
9333
  }
9226
- var _dsh_plugin_shop_css_d74581c2_default = {
9227
- "client": "s73a3ff_client",
9228
- "config": "s45aee2_config",
9229
- "ts": "s8a51a7_ts",
9230
- "spec": "s117e2f_spec",
9231
- "panel": "sa2d7b5_panel",
9232
- "searchInput": "s7c10d7_searchInput",
9233
- "toolbar": "sbee2c3_toolbar",
9234
- "stateLine": "sa89499_stateLine",
9235
- "srOnly": "s90f53b_srOnly",
9236
- "skeletonGrid": "sd5b66e_skeletonGrid",
9237
- "skeletonCard": "sfb41cd_skeletonCard",
9238
- "skeletonName": "sc9bc63_skeletonName",
9239
- "skeletonSummary": "s75708b_skeletonSummary",
9240
- "skeletonSummaryShort": "s772f28_skeletonSummaryShort",
9241
- "skeletonActions": "s6ce39d_skeletonActions",
9242
- "actionButton": "s1547e4_actionButton",
9243
- "toolbarRight": "sfb98ad_toolbarRight",
9244
- "versionBlock": "s15df74_versionBlock",
9245
- "versionText": "s9d78e5_versionText",
9246
- "updateSelfButton": "scb0503_updateSelfButton",
9247
- "checkUpdateButton": "seab5ae_checkUpdateButton",
9248
- "githubLink": "s6932e1_githubLink",
9249
- "selfUpdatePanel": "sb1e444_selfUpdatePanel",
9250
- "expansionNote": "s1dee54_expansionNote",
9251
- "staleBadge": "s5823b0_staleBadge",
9252
- "categoryBar": "s52084f_categoryBar",
9253
- "categoryButton": "sd07f01_categoryButton",
9254
- "categoryButtonOn": "s7d231e_categoryButtonOn",
9255
- "incompatibleFilter": "s7abba7_incompatibleFilter",
9256
- "switch": "sa4bed2_switch",
9257
- "switchOn": "s97d8ec_switchOn",
9258
- "incompatibleFilterOn": "sa24b4e_incompatibleFilterOn",
9259
- "switchKnob": "s24e1f5_switchKnob",
9260
- "updatableHeading": "s3ffb2a_updatableHeading",
9261
- "catalogStatsRow": "sfd3ed8_catalogStatsRow",
9262
- "sortGroup": "sc5a073_sortGroup",
9263
- "sortLabel": "s275eb9_sortLabel",
9264
- "catalogStats": "s3c7a5d_catalogStats",
9265
- "catalogRefreshButton": "s6d141d_catalogRefreshButton",
9266
- "catalogRefreshFailed": "s8b8ed4_catalogRefreshFailed",
9267
- "emptyLine": "s918b21_emptyLine",
9268
- "cards": "sf66d65_cards",
9269
- "card": "sa6c0bf_card",
9270
- "cardsSentry": "s21cef7_cardsSentry",
9271
- "showingLine": "s9babb8_showingLine",
9272
- "cardSpine": "sce2063_cardSpine",
9273
- "categoryBadge": "sdc7cd8_categoryBadge",
9274
- "entryHeader": "s6ae6ba_entryHeader",
9275
- "name": "saf3e98_name",
9276
- "badges": "s93d243_badges",
9277
- "cardActions": "s763b75_cardActions",
9278
- "gate": "sff1df6_gate",
9279
- "installPanel": "s6d7bee_installPanel",
9280
- "installedActions": "s10ddb2_installedActions",
9281
- "cardVersion": "s9d1d9e_cardVersion",
9282
- "sourceBadge": "sad456f_sourceBadge",
9283
- "tierBadge": "s7f352e_tierBadge",
9284
- "incompatibleBadge": "s649a66_incompatibleBadge",
9285
- "starsBadge": "se83753_starsBadge",
9286
- "chevron": "sc7129d_chevron",
9287
- "chevronOpen": "s3e170e_chevronOpen",
9288
- "body": "s249e15_body",
9289
- "summary": "sdd4d7a_summary",
9290
- "summaryZh": "s1a6809_summaryZh",
9291
- "summaryExpanded": "sc924dd_summaryExpanded",
9292
- "summaryZhExpanded": "sab7503_summaryZhExpanded",
9293
- "incompatibleDetail": "s4625f5_incompatibleDetail",
9294
- "remedy": "s632e71_remedy",
9295
- "remedyLead": "s360025_remedyLead",
9296
- "allowCommand": "s12ec11_allowCommand",
9297
- "refusal": "sa92311_refusal",
9298
- "capabilitiesBlock": "s99a4f5_capabilitiesBlock",
9299
- "capabilitiesNote": "scf0a21_capabilitiesNote",
9300
- "capabilities": "sc760c8_capabilities",
9301
- "detail": "s48edfc_detail",
9302
- "detailRows": "sce5502_detailRows",
9303
- "detailRow": "scc4b2b_detailRow",
9304
- "cardMeta": "s8fb80a_cardMeta",
9305
- "shelfDate": "s72b045_shelfDate",
9306
- "size": "s24611f_size",
9307
- "author": "seefbaa_author",
9308
- "reviewedLine": "sa79085_reviewedLine",
9309
- "installButton": "sdddc5f_installButton",
9310
- "confirmButton": "safd4a8_confirmButton",
9311
- "cancelButton": "sfcf768_cancelButton",
9312
- "installedLabel": "s563674_installedLabel",
9313
- "uninstallButton": "s767a97_uninstallButton",
9314
- "restartSelfButton": "sa6b105_restartSelfButton",
9315
- "restartButton": "s426aa0_restartButton",
9316
- "reloadButton": "sa283f9_reloadButton",
9317
- "reloadPanel": "sf20a99_reloadPanel",
9318
- "gateTitle": "see0e3d_gateTitle",
9319
- "gateBody": "sd04ef9_gateBody",
9320
- "gateWarning": "s7fd5d3_gateWarning",
9321
- "gateActions": "sf6b0aa_gateActions",
9322
- "installing": "sa32b72_installing",
9323
- "log": "sf9b6a4_log",
9324
- "logLine": "s9e4d00_logLine",
9325
- "notice": "s7cd7db_notice",
9326
- "failedHeading": "sfb1bae_failedHeading",
9327
- "failedDetail": "s9591b1_failedDetail",
9328
- "rejectedCode": "saa0605_rejectedCode",
9329
- "rejectedDetail": "sbd5c7c_rejectedDetail",
9330
- "outdatedSection": "s7c248d_outdatedSection",
9331
- "outdatedList": "se144d3_outdatedList",
9332
- "outdatedRow": "s6fbe58_outdatedRow",
9333
- "outdatedInfo": "s8af496_outdatedInfo",
9334
- "outdatedVersions": "sf82d51_outdatedVersions",
9335
- "shadowedSection": "s31b89a_shadowedSection",
9336
- "shadowedDetail": "scf838f_shadowedDetail",
9337
- "outdatedActions": "s109c95_outdatedActions",
9338
- "switchWrap": "s5c21f4_switchWrap"
9334
+ var _dsh_plugin_shop_css_5f753521_default = {
9335
+ "client": "sa85938_client",
9336
+ "config": "s94d4e5_config",
9337
+ "ts": "s91da2e_ts",
9338
+ "spec": "sbb5706_spec",
9339
+ "panel": "s6a01b8_panel",
9340
+ "searchInput": "sa16cbe_searchInput",
9341
+ "toolbar": "sbc1497_toolbar",
9342
+ "stateLine": "s352f7a_stateLine",
9343
+ "srOnly": "s60f2f5_srOnly",
9344
+ "skeletonGrid": "s34d5f5_skeletonGrid",
9345
+ "skeletonCard": "s11e315_skeletonCard",
9346
+ "skeletonName": "s227d7c_skeletonName",
9347
+ "skeletonSummary": "s83025b_skeletonSummary",
9348
+ "skeletonSummaryShort": "sdb7845_skeletonSummaryShort",
9349
+ "skeletonActions": "sa61117_skeletonActions",
9350
+ "actionButton": "s757132_actionButton",
9351
+ "toolbarRight": "s22d15d_toolbarRight",
9352
+ "versionBlock": "s76d3c9_versionBlock",
9353
+ "versionText": "s390e34_versionText",
9354
+ "updateSelfButton": "s79450a_updateSelfButton",
9355
+ "checkUpdateButton": "sb48cbc_checkUpdateButton",
9356
+ "githubLink": "s946346_githubLink",
9357
+ "selfUpdatePanel": "s861599_selfUpdatePanel",
9358
+ "expansionNote": "seb8329_expansionNote",
9359
+ "staleBadge": "s31a2f0_staleBadge",
9360
+ "categoryBar": "sab4844_categoryBar",
9361
+ "categoryButton": "s512425_categoryButton",
9362
+ "categoryButtonOn": "s598384_categoryButtonOn",
9363
+ "incompatibleFilter": "s7ce437_incompatibleFilter",
9364
+ "switch": "s3a8382_switch",
9365
+ "switchOn": "s1e392e_switchOn",
9366
+ "incompatibleFilterOn": "sd9fa8d_incompatibleFilterOn",
9367
+ "switchKnob": "s419ba1_switchKnob",
9368
+ "updatableHeading": "s320079_updatableHeading",
9369
+ "catalogStatsRow": "s923c82_catalogStatsRow",
9370
+ "sortGroup": "s3edd9f_sortGroup",
9371
+ "sortLabel": "s73feb1_sortLabel",
9372
+ "catalogStats": "sad5aba_catalogStats",
9373
+ "disclosure": "s58eb5b_disclosure",
9374
+ "catalogRefreshButton": "s4cca13_catalogRefreshButton",
9375
+ "catalogRefreshFailed": "sc24c97_catalogRefreshFailed",
9376
+ "emptyLine": "s1aa6bf_emptyLine",
9377
+ "cards": "sa036b0_cards",
9378
+ "card": "s88b200_card",
9379
+ "cardsSentry": "s853040_cardsSentry",
9380
+ "showingLine": "s239c39_showingLine",
9381
+ "cardSpine": "sc1c25e_cardSpine",
9382
+ "categoryBadge": "s433c76_categoryBadge",
9383
+ "entryHeader": "s6bc968_entryHeader",
9384
+ "name": "s704f0d_name",
9385
+ "badges": "sba9311_badges",
9386
+ "cardActions": "s9b33f4_cardActions",
9387
+ "gate": "sc99cff_gate",
9388
+ "installPanel": "sbd0905_installPanel",
9389
+ "installedActions": "sd562d9_installedActions",
9390
+ "cardVersion": "s83e5d6_cardVersion",
9391
+ "sourceBadge": "s93d06c_sourceBadge",
9392
+ "tierBadge": "s1fe784_tierBadge",
9393
+ "incompatibleBadge": "s2f456e_incompatibleBadge",
9394
+ "starsBadge": "sc6c968_starsBadge",
9395
+ "chevron": "s287fda_chevron",
9396
+ "chevronOpen": "s20b60f_chevronOpen",
9397
+ "body": "s87fcee_body",
9398
+ "summary": "sf5d0d8_summary",
9399
+ "summaryZh": "sa0923f_summaryZh",
9400
+ "summaryExpanded": "s20eea8_summaryExpanded",
9401
+ "summaryZhExpanded": "sb53b34_summaryZhExpanded",
9402
+ "incompatibleDetail": "sc2cc78_incompatibleDetail",
9403
+ "remedy": "sc7602c_remedy",
9404
+ "foldBlock": "s895dae_foldBlock",
9405
+ "foldBlockExpanded": "s6b136d_foldBlockExpanded",
9406
+ "foldToggle": "s8c3792_foldToggle",
9407
+ "remedyLead": "s935bd9_remedyLead",
9408
+ "allowCommand": "se75f45_allowCommand",
9409
+ "refusal": "se69fd7_refusal",
9410
+ "capabilitiesBlock": "sb2edea_capabilitiesBlock",
9411
+ "capabilitiesNote": "sa5c8b2_capabilitiesNote",
9412
+ "capabilities": "sb95e1e_capabilities",
9413
+ "detail": "s1f4436_detail",
9414
+ "detailRows": "sf6f153_detailRows",
9415
+ "detailRow": "sbfe178_detailRow",
9416
+ "cardMeta": "s92d907_cardMeta",
9417
+ "shelfDate": "s6119f4_shelfDate",
9418
+ "size": "s5889e2_size",
9419
+ "author": "s5efcbe_author",
9420
+ "reviewedLine": "sdd3957_reviewedLine",
9421
+ "installButton": "se20cee_installButton",
9422
+ "confirmButton": "sdf88f9_confirmButton",
9423
+ "cancelButton": "s330a03_cancelButton",
9424
+ "installedLabel": "sad029f_installedLabel",
9425
+ "refusedLabel": "s7b7f2d_refusedLabel",
9426
+ "uninstallButton": "s1ef16e_uninstallButton",
9427
+ "restartSelfButton": "s912ec7_restartSelfButton",
9428
+ "restartButton": "sa5d8c1_restartButton",
9429
+ "reloadButton": "se5bd2c_reloadButton",
9430
+ "reloadPanel": "s76690e_reloadPanel",
9431
+ "gateTitle": "s58fe9b_gateTitle",
9432
+ "gateBody": "sd510f5_gateBody",
9433
+ "gateWarning": "s943dbd_gateWarning",
9434
+ "gateActions": "sb2e35c_gateActions",
9435
+ "installing": "s407016_installing",
9436
+ "log": "s56c1a5_log",
9437
+ "logLine": "s3d7aca_logLine",
9438
+ "notice": "s3e1ea0_notice",
9439
+ "failedHeading": "s71965d_failedHeading",
9440
+ "failedDetail": "s41bb43_failedDetail",
9441
+ "rejectedCode": "s3b880d_rejectedCode",
9442
+ "rejectedDetail": "s465924_rejectedDetail",
9443
+ "outdatedSection": "sfccf6f_outdatedSection",
9444
+ "outdatedList": "s403f9e_outdatedList",
9445
+ "outdatedRow": "sd9cb61_outdatedRow",
9446
+ "outdatedInfo": "s7b2d89_outdatedInfo",
9447
+ "outdatedVersions": "sc33878_outdatedVersions",
9448
+ "shadowedSection": "sa88998_shadowedSection",
9449
+ "shadowedDetail": "s98799e_shadowedDetail",
9450
+ "outdatedActions": "saaf7ce_outdatedActions",
9451
+ "switchWrap": "saa9c93_switchWrap"
9339
9452
  };
9340
9453
  //#endregion
9454
+ //#region src/client/FoldableRedBlock.tsx
9455
+ function FoldableRedBlock({ children, t, attrName }) {
9456
+ const { rootRef, expandable, expanded, toggle } = useFoldableRedBlock();
9457
+ const kindName = attrName.replace(/^data-shop-/, "");
9458
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9459
+ className: expanded ? _dsh_plugin_shop_css_5f753521_default.foldBlockExpanded : _dsh_plugin_shop_css_5f753521_default.foldBlock,
9460
+ [attrName]: true,
9461
+ ref: rootRef,
9462
+ children
9463
+ }), expandable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9464
+ type: "button",
9465
+ className: _dsh_plugin_shop_css_5f753521_default.foldToggle,
9466
+ "data-shop-fold": kindName,
9467
+ "aria-expanded": expanded,
9468
+ onClick: toggle,
9469
+ children: expanded ? t("lessRedLines") : t("moreRedLines")
9470
+ })] });
9471
+ }
9472
+ //#endregion
9341
9473
  //#region src/client/ShopTab.tsx
9342
- /** The shop tab: browse the catalog, detail an entry, install with the
9343
- * acknowledgement gate, poll the install to its terminal state (§7.2).
9474
+ /** The shop tab: browse the catalog, detail an entry, install on one click,
9475
+ * poll the install to its terminal state (§7.2).
9344
9476
  * Everything the tab renders is text — summaries, capabilities, log lines,
9345
9477
  * details — never markup, so hostile npm descriptions cannot inject (spec
9346
9478
  * §11.3.4): no render path here may ever use dangerouslySetInnerHTML. */
@@ -9352,14 +9484,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9352
9484
  * those nodes, and React's next commit throws on the ones it no longer finds
9353
9485
  * (design 2026-09-26-market-borrowings §4). The tab renders no portals, so its
9354
9486
  * root is its whole surface. */
9355
- const PANEL_ROOT = `${_dsh_plugin_shop_css_d74581c2_default.panel} notranslate`;
9487
+ const PANEL_ROOT = `${_dsh_plugin_shop_css_5f753521_default.panel} notranslate`;
9356
9488
  function ChevronIcon({ open }) {
9357
9489
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
9358
9490
  viewBox: "0 0 16 16",
9359
9491
  width: "14",
9360
9492
  height: "14",
9361
9493
  "aria-hidden": "true",
9362
- className: open ? _dsh_plugin_shop_css_d74581c2_default.chevronOpen : _dsh_plugin_shop_css_d74581c2_default.chevron,
9494
+ className: open ? _dsh_plugin_shop_css_5f753521_default.chevronOpen : _dsh_plugin_shop_css_5f753521_default.chevron,
9363
9495
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
9364
9496
  fill: "currentColor",
9365
9497
  d: "M3.5 6 8 10.5 12.5 6l1 1L8 12.5 2.5 7l1-1Z"
@@ -9391,32 +9523,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9391
9523
  subdir: entry.subdir
9392
9524
  };
9393
9525
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9394
- className: _dsh_plugin_shop_css_d74581c2_default.card,
9526
+ className: _dsh_plugin_shop_css_5f753521_default.card,
9395
9527
  "data-shop-entry": entry.name,
9396
9528
  "data-category": category,
9397
9529
  children: [
9398
9530
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9399
- className: _dsh_plugin_shop_css_d74581c2_default.cardSpine,
9531
+ className: _dsh_plugin_shop_css_5f753521_default.cardSpine,
9400
9532
  "aria-hidden": "true"
9401
9533
  }),
9402
9534
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9403
9535
  type: "button",
9404
- className: _dsh_plugin_shop_css_d74581c2_default.entryHeader,
9536
+ className: _dsh_plugin_shop_css_5f753521_default.entryHeader,
9405
9537
  "aria-expanded": open,
9406
9538
  "aria-controls": detailId,
9407
9539
  onClick: () => setOpen((current) => !current),
9408
9540
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9409
- className: _dsh_plugin_shop_css_d74581c2_default.name,
9541
+ className: _dsh_plugin_shop_css_5f753521_default.name,
9410
9542
  children: entry.name
9411
9543
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9412
- className: _dsh_plugin_shop_css_d74581c2_default.badges,
9544
+ className: _dsh_plugin_shop_css_5f753521_default.badges,
9413
9545
  children: [
9414
9546
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9415
- className: _dsh_plugin_shop_css_d74581c2_default.categoryBadge,
9547
+ className: _dsh_plugin_shop_css_5f753521_default.categoryBadge,
9416
9548
  children: t(categoryKey(entry))
9417
9549
  }),
9418
9550
  entry.source === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9419
- className: _dsh_plugin_shop_css_d74581c2_default.sourceBadge,
9551
+ className: _dsh_plugin_shop_css_5f753521_default.sourceBadge,
9420
9552
  "data-shop-source-npm": true,
9421
9553
  role: "img",
9422
9554
  "aria-label": t("npmSource"),
@@ -9433,7 +9565,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9433
9565
  })
9434
9566
  }),
9435
9567
  hasGithubHome(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9436
- className: _dsh_plugin_shop_css_d74581c2_default.sourceBadge,
9568
+ className: _dsh_plugin_shop_css_5f753521_default.sourceBadge,
9437
9569
  "data-shop-source-github": true,
9438
9570
  role: "img",
9439
9571
  "aria-label": t("githubSource"),
@@ -9447,7 +9579,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9447
9579
  })
9448
9580
  }),
9449
9581
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9450
- className: _dsh_plugin_shop_css_d74581c2_default.cardVersion,
9582
+ className: _dsh_plugin_shop_css_5f753521_default.cardVersion,
9451
9583
  "data-card-version": true,
9452
9584
  role: "img",
9453
9585
  "aria-label": versionName,
@@ -9455,17 +9587,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9455
9587
  children: version.text
9456
9588
  }),
9457
9589
  entry.tier !== "community" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9458
- className: _dsh_plugin_shop_css_d74581c2_default.tierBadge,
9590
+ className: _dsh_plugin_shop_css_5f753521_default.tierBadge,
9459
9591
  "data-tier": entry.tier,
9460
9592
  children: t(tierKey(entry.tier))
9461
9593
  }),
9462
9594
  entry.archived === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9463
- className: _dsh_plugin_shop_css_d74581c2_default.tierBadge,
9595
+ className: _dsh_plugin_shop_css_5f753521_default.tierBadge,
9464
9596
  "data-tier": "archived",
9465
9597
  children: t("archivedBadge")
9466
9598
  }),
9467
9599
  stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9468
- className: _dsh_plugin_shop_css_d74581c2_default.starsBadge,
9600
+ className: _dsh_plugin_shop_css_5f753521_default.starsBadge,
9469
9601
  role: "img",
9470
9602
  "aria-label": t("stars", { count: stars }),
9471
9603
  children: ["★ ", formatStars(stars)]
@@ -9475,39 +9607,43 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9475
9607
  })]
9476
9608
  }),
9477
9609
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9478
- className: _dsh_plugin_shop_css_d74581c2_default.body,
9610
+ className: _dsh_plugin_shop_css_5f753521_default.body,
9479
9611
  children: [
9480
9612
  summary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9481
- className: open ? `${_dsh_plugin_shop_css_d74581c2_default.summary} ${_dsh_plugin_shop_css_d74581c2_default.summaryExpanded}` : _dsh_plugin_shop_css_d74581c2_default.summary,
9613
+ className: open ? `${_dsh_plugin_shop_css_5f753521_default.summary} ${_dsh_plugin_shop_css_5f753521_default.summaryExpanded}` : _dsh_plugin_shop_css_5f753521_default.summary,
9482
9614
  children: summary.en
9483
9615
  }),
9484
9616
  summary?.zh !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9485
- className: open ? `${_dsh_plugin_shop_css_d74581c2_default.summaryZh} ${_dsh_plugin_shop_css_d74581c2_default.summaryZhExpanded}` : _dsh_plugin_shop_css_d74581c2_default.summaryZh,
9617
+ className: open ? `${_dsh_plugin_shop_css_5f753521_default.summaryZh} ${_dsh_plugin_shop_css_5f753521_default.summaryZhExpanded}` : _dsh_plugin_shop_css_5f753521_default.summaryZh,
9486
9618
  children: summary.zh
9487
9619
  }),
9488
- blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
9489
- blocker,
9490
- className: _dsh_plugin_shop_css_d74581c2_default.incompatibleDetail,
9491
- surface: "detail"
9492
- }, blocker.kind)),
9620
+ blockers.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FoldableRedBlock, {
9621
+ t,
9622
+ attrName: "data-shop-remedy",
9623
+ children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
9624
+ blocker,
9625
+ className: _dsh_plugin_shop_css_5f753521_default.incompatibleDetail,
9626
+ surface: "detail"
9627
+ }, blocker.kind))
9628
+ }),
9493
9629
  open && entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9494
- className: _dsh_plugin_shop_css_d74581c2_default.capabilitiesBlock,
9630
+ className: _dsh_plugin_shop_css_5f753521_default.capabilitiesBlock,
9495
9631
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9496
- className: _dsh_plugin_shop_css_d74581c2_default.capabilitiesNote,
9632
+ className: _dsh_plugin_shop_css_5f753521_default.capabilitiesNote,
9497
9633
  children: t("capabilitiesNote")
9498
9634
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
9499
- className: _dsh_plugin_shop_css_d74581c2_default.capabilities,
9635
+ className: _dsh_plugin_shop_css_5f753521_default.capabilities,
9500
9636
  children: entry.catalog.capabilities.map((capability) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: capability }, capability))
9501
9637
  })]
9502
9638
  }),
9503
9639
  open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
9504
9640
  id: detailId,
9505
- className: _dsh_plugin_shop_css_d74581c2_default.detail,
9641
+ className: _dsh_plugin_shop_css_5f753521_default.detail,
9506
9642
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
9507
- className: _dsh_plugin_shop_css_d74581c2_default.detailRows,
9643
+ className: _dsh_plugin_shop_css_5f753521_default.detailRows,
9508
9644
  children: [
9509
9645
  npmUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9510
- className: _dsh_plugin_shop_css_d74581c2_default.detailRow,
9646
+ className: _dsh_plugin_shop_css_5f753521_default.detailRow,
9511
9647
  "data-shop-npm": true,
9512
9648
  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", {
9513
9649
  href: npmUrl,
@@ -9517,7 +9653,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9517
9653
  }) })]
9518
9654
  }),
9519
9655
  entry.repository !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9520
- className: _dsh_plugin_shop_css_d74581c2_default.detailRow,
9656
+ className: _dsh_plugin_shop_css_5f753521_default.detailRow,
9521
9657
  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", {
9522
9658
  href: entry.repository,
9523
9659
  target: "_blank",
@@ -9526,12 +9662,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9526
9662
  }) : entry.repository })]
9527
9663
  }),
9528
9664
  entry.license !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9529
- className: _dsh_plugin_shop_css_d74581c2_default.detailRow,
9665
+ className: _dsh_plugin_shop_css_5f753521_default.detailRow,
9530
9666
  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 })]
9531
9667
  })
9532
9668
  ]
9533
9669
  }), entry.tier === "verified-stale" && entry.review !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9534
- className: _dsh_plugin_shop_css_d74581c2_default.reviewedLine,
9670
+ className: _dsh_plugin_shop_css_5f753521_default.reviewedLine,
9535
9671
  children: entry.review.reviewedVersion !== void 0 ? t("reviewedVersionLine", {
9536
9672
  reviewed: versionLabel("npm", entry.review.reviewedVersion).text,
9537
9673
  current: version.text
@@ -9544,7 +9680,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9544
9680
  ]
9545
9681
  }),
9546
9682
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9547
- className: _dsh_plugin_shop_css_d74581c2_default.cardActions,
9683
+ className: _dsh_plugin_shop_css_5f753521_default.cardActions,
9548
9684
  "data-shop-actions": true,
9549
9685
  children: [installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [!inInstalledView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
9550
9686
  target: installTarget,
@@ -9576,7 +9712,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9576
9712
  restartBlocked,
9577
9713
  reload
9578
9714
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9579
- className: _dsh_plugin_shop_css_d74581c2_default.installedLabel,
9715
+ className: _dsh_plugin_shop_css_5f753521_default.installedLabel,
9580
9716
  "data-shop-installed": true,
9581
9717
  children: t("installed")
9582
9718
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
@@ -9601,15 +9737,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9601
9737
  flow: uninstallFlow
9602
9738
  })
9603
9739
  ] }), (size !== void 0 || author !== null || shelfDay !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9604
- className: _dsh_plugin_shop_css_d74581c2_default.cardMeta,
9740
+ className: _dsh_plugin_shop_css_5f753521_default.cardMeta,
9605
9741
  children: [
9606
9742
  shelfDay !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9607
- className: _dsh_plugin_shop_css_d74581c2_default.shelfDate,
9743
+ className: _dsh_plugin_shop_css_5f753521_default.shelfDate,
9608
9744
  "data-shop-shelf-date": true,
9609
9745
  children: t(order === "added" ? "listedOn" : "updatedOn", { date: shelfDay })
9610
9746
  }),
9611
9747
  size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9612
- className: _dsh_plugin_shop_css_d74581c2_default.size,
9748
+ className: _dsh_plugin_shop_css_5f753521_default.size,
9613
9749
  "data-shop-size": true,
9614
9750
  role: "img",
9615
9751
  "aria-label": t("sizeLabel", { size }),
@@ -9617,7 +9753,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9617
9753
  children: size
9618
9754
  }),
9619
9755
  author !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9620
- className: _dsh_plugin_shop_css_d74581c2_default.author,
9756
+ className: _dsh_plugin_shop_css_5f753521_default.author,
9621
9757
  "data-shop-author": true,
9622
9758
  children: t("authorLine", { author })
9623
9759
  })
@@ -9667,13 +9803,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9667
9803
  ...attribute,
9668
9804
  children: blocker.text
9669
9805
  }), blocker.remedy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9670
- className: _dsh_plugin_shop_css_d74581c2_default.remedy,
9806
+ className: _dsh_plugin_shop_css_5f753521_default.remedy,
9671
9807
  "data-shop-remedy": blocker.kind,
9672
9808
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9673
- className: _dsh_plugin_shop_css_d74581c2_default.remedyLead,
9809
+ className: _dsh_plugin_shop_css_5f753521_default.remedyLead,
9674
9810
  children: blocker.remedy.lead
9675
9811
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
9676
- className: _dsh_plugin_shop_css_d74581c2_default.allowCommand,
9812
+ className: _dsh_plugin_shop_css_5f753521_default.allowCommand,
9677
9813
  "data-shop-allow-command": true,
9678
9814
  children: blocker.remedy.command
9679
9815
  })]
@@ -9711,7 +9847,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9711
9847
  if (lead === void 0 || word === null) return null;
9712
9848
  const detail = blockers.map((blocker) => blocker.text).join("\n");
9713
9849
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9714
- className: _dsh_plugin_shop_css_d74581c2_default.incompatibleBadge,
9850
+ className: _dsh_plugin_shop_css_5f753521_default.incompatibleBadge,
9715
9851
  "data-shop-blocker": lead.kind,
9716
9852
  role: "img",
9717
9853
  "aria-label": detail,
@@ -9719,46 +9855,53 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9719
9855
  children: t(word)
9720
9856
  });
9721
9857
  }
9722
- /** One entry's install flow: the button, the §9.3 acknowledgement gate for
9723
- * community-tier entries, and the live view — running log, restart notice,
9724
- * failure detail, rejection detail — driven by `useInstallFlows`. Shared by the
9725
- * catalog cards (`variant: 'install'`) and the outdated rows' update button
9726
- * (`variant: 'update'`, which drives the same install flow for `name@latest`). */
9858
+ /** One entry's install flow: the button (or the refused LABEL that stands in
9859
+ * its place), and the live view — running log, restart notice, failure detail,
9860
+ * rejection detail — driven by `useInstallFlows`. Shared by the catalog cards
9861
+ * (`variant: 'install'`) and the outdated rows' update button (`variant:
9862
+ * 'update'`, which drives the same install flow for `name@latest`).
9863
+ *
9864
+ * There is no §9.3 dialog. Until 2026-10-14 a community entry opened an
9865
+ * acknowledgement gate first, and that gate was also the only thing standing
9866
+ * between a second press and a second RPC — it reopened instead of asking
9867
+ * again. What replaced it is one click that carries `acknowledged: true`, the
9868
+ * disclosure standing on the page where a reader cannot dismiss it unread, and
9869
+ * the in-flight guard in `useKeyedFlows`, which is what makes the missing
9870
+ * dialog safe. */
9727
9871
  function InstallPanel({ target, tier, blockers, blockersStated = false, variant = "install", flow, t, restart, restartBlocked, reload }) {
9728
- const [gateOpen, setGateOpen] = (0, react.useState)(false);
9729
- const { view, start } = flow;
9872
+ const { view, start, starting } = flow;
9730
9873
  if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9731
- className: _dsh_plugin_shop_css_d74581c2_default.installPanel,
9874
+ className: _dsh_plugin_shop_css_5f753521_default.installPanel,
9732
9875
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9733
- className: _dsh_plugin_shop_css_d74581c2_default.installing,
9876
+ className: _dsh_plugin_shop_css_5f753521_default.installing,
9734
9877
  children: t(installPhaseKey(view.phase))
9735
9878
  }), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9736
- className: _dsh_plugin_shop_css_d74581c2_default.log,
9879
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9737
9880
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9738
- className: _dsh_plugin_shop_css_d74581c2_default.logLine,
9881
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9739
9882
  "data-shop-log-line": true,
9740
9883
  children: line
9741
9884
  }, index))
9742
9885
  })]
9743
9886
  });
9744
9887
  if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9745
- className: _dsh_plugin_shop_css_d74581c2_default.installedActions,
9888
+ className: _dsh_plugin_shop_css_5f753521_default.installedActions,
9746
9889
  children: [
9747
9890
  view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9748
- className: _dsh_plugin_shop_css_d74581c2_default.log,
9891
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9749
9892
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9750
- className: _dsh_plugin_shop_css_d74581c2_default.logLine,
9893
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9751
9894
  "data-shop-log-line": true,
9752
9895
  children: line
9753
9896
  }, index))
9754
9897
  }),
9755
9898
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9756
- className: _dsh_plugin_shop_css_d74581c2_default.notice,
9899
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
9757
9900
  "data-shop-restart-notice": true,
9758
9901
  children: t(activationNoticeKey(view.activation, view.restartReason))
9759
9902
  }),
9760
9903
  view.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9761
- className: _dsh_plugin_shop_css_d74581c2_default.notice,
9904
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
9762
9905
  "data-shop-done-note": true,
9763
9906
  children: view.detail
9764
9907
  }),
@@ -9773,103 +9916,81 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9773
9916
  ]
9774
9917
  });
9775
9918
  if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9776
- className: _dsh_plugin_shop_css_d74581c2_default.installPanel,
9919
+ className: _dsh_plugin_shop_css_5f753521_default.installPanel,
9777
9920
  children: [
9778
9921
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9779
- className: _dsh_plugin_shop_css_d74581c2_default.failedHeading,
9922
+ className: _dsh_plugin_shop_css_5f753521_default.failedHeading,
9780
9923
  children: t("installFailed")
9781
9924
  }),
9782
9925
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9783
- className: _dsh_plugin_shop_css_d74581c2_default.log,
9926
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9784
9927
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9785
- className: _dsh_plugin_shop_css_d74581c2_default.logLine,
9928
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9786
9929
  "data-shop-log-line": true,
9787
9930
  children: line
9788
9931
  }, index))
9789
9932
  }),
9790
9933
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9791
- className: _dsh_plugin_shop_css_d74581c2_default.failedDetail,
9934
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
9792
9935
  children: view.detail === "" ? t("installTransportFailed") : view.detail
9793
9936
  })
9794
9937
  ]
9795
9938
  });
9796
9939
  if (view.kind === "rejected") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9797
- className: _dsh_plugin_shop_css_d74581c2_default.installPanel,
9940
+ className: _dsh_plugin_shop_css_5f753521_default.installPanel,
9798
9941
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9799
- className: _dsh_plugin_shop_css_d74581c2_default.rejectedCode,
9942
+ className: _dsh_plugin_shop_css_5f753521_default.rejectedCode,
9800
9943
  children: t(rejectionCodeKey(view.code))
9801
9944
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9802
- className: _dsh_plugin_shop_css_d74581c2_default.rejectedDetail,
9945
+ className: _dsh_plugin_shop_css_5f753521_default.rejectedDetail,
9803
9946
  children: view.detail
9804
9947
  })]
9805
9948
  });
9806
- if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9807
- className: _dsh_plugin_shop_css_d74581c2_default.gate,
9808
- children: [
9809
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9810
- className: _dsh_plugin_shop_css_d74581c2_default.gateTitle,
9811
- children: t("acknowledgementTitle")
9812
- }),
9813
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9814
- className: _dsh_plugin_shop_css_d74581c2_default.gateBody,
9815
- children: t("acknowledgementBody")
9816
- }),
9817
- !blockersStated && blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
9818
- blocker,
9819
- className: _dsh_plugin_shop_css_d74581c2_default.gateWarning,
9820
- surface: "warning"
9821
- }, blocker.kind)),
9822
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9823
- className: _dsh_plugin_shop_css_d74581c2_default.gateActions,
9824
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9825
- type: "button",
9826
- className: _dsh_plugin_shop_css_d74581c2_default.confirmButton,
9827
- "data-shop-confirm": true,
9828
- onClick: () => {
9829
- setGateOpen(false);
9830
- start({
9831
- ...target,
9832
- acknowledged: true
9833
- });
9834
- },
9835
- children: t("confirm")
9836
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9837
- type: "button",
9838
- className: _dsh_plugin_shop_css_d74581c2_default.cancelButton,
9839
- onClick: () => setGateOpen(false),
9840
- children: t("cancel")
9841
- })]
9842
- })
9843
- ]
9844
- });
9845
9949
  const update = variant === "update";
9846
9950
  const refused = refusesInstall(blockers);
9951
+ const word = blockerBadgeKey(blockers);
9952
+ const detail = blockers.map((blocker) => blocker.text).join("\n");
9953
+ if (refused && word !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9954
+ className: _dsh_plugin_shop_css_5f753521_default.refusedLabel,
9955
+ "data-shop-refused": true,
9956
+ "data-shop-blocker-kind": blockers[0]?.kind,
9957
+ role: "img",
9958
+ "aria-label": detail,
9959
+ title: detail,
9960
+ children: t(word)
9961
+ }), !blockersStated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FoldableRedBlock, {
9962
+ t,
9963
+ attrName: "data-shop-refusal",
9964
+ children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
9965
+ blocker,
9966
+ className: _dsh_plugin_shop_css_5f753521_default.gateWarning,
9967
+ surface: "warning"
9968
+ }, blocker.kind))
9969
+ })] });
9847
9970
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
9848
9971
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9849
9972
  type: "button",
9850
- className: _dsh_plugin_shop_css_d74581c2_default.installButton,
9851
- disabled: refused,
9852
- ...refused ? { "data-shop-refused": true } : {},
9973
+ className: _dsh_plugin_shop_css_5f753521_default.installButton,
9974
+ disabled: starting,
9853
9975
  ...update ? { "data-shop-update": true } : { "data-shop-install": true },
9854
9976
  onClick: () => {
9855
- if (tier === "verified") start({
9977
+ start({
9856
9978
  ...target,
9857
- acknowledged: void 0
9979
+ acknowledged: tier === "verified" ? void 0 : true
9858
9980
  });
9859
- else setGateOpen(true);
9860
9981
  },
9861
9982
  children: t(update ? "update" : "install")
9862
9983
  }),
9863
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
9984
+ blockers.length > 0 && !refused && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
9864
9985
  blockers,
9865
9986
  t
9866
9987
  }),
9867
- refused && !blockersStated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9868
- className: _dsh_plugin_shop_css_d74581c2_default.refusal,
9869
- "data-shop-refusal": true,
9988
+ !blockersStated && blockers.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FoldableRedBlock, {
9989
+ t,
9990
+ attrName: "data-shop-blocker-lines",
9870
9991
  children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
9871
9992
  blocker,
9872
- className: _dsh_plugin_shop_css_d74581c2_default.gateWarning,
9993
+ className: _dsh_plugin_shop_css_5f753521_default.gateWarning,
9873
9994
  surface: "warning"
9874
9995
  }, blocker.kind))
9875
9996
  })
@@ -9884,32 +10005,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9884
10005
  function UninstallPanel({ name, t, restart, restartBlocked, reload, flow }) {
9885
10006
  const { view, start, reset } = flow;
9886
10007
  if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9887
- className: _dsh_plugin_shop_css_d74581c2_default.installPanel,
10008
+ className: _dsh_plugin_shop_css_5f753521_default.installPanel,
9888
10009
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9889
- className: _dsh_plugin_shop_css_d74581c2_default.installing,
10010
+ className: _dsh_plugin_shop_css_5f753521_default.installing,
9890
10011
  children: t("uninstalling")
9891
10012
  }), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9892
- className: _dsh_plugin_shop_css_d74581c2_default.log,
10013
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9893
10014
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9894
- className: _dsh_plugin_shop_css_d74581c2_default.logLine,
10015
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9895
10016
  "data-shop-log-line": true,
9896
10017
  children: line
9897
10018
  }, index))
9898
10019
  })]
9899
10020
  });
9900
10021
  if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9901
- className: _dsh_plugin_shop_css_d74581c2_default.installedActions,
10022
+ className: _dsh_plugin_shop_css_5f753521_default.installedActions,
9902
10023
  children: [
9903
10024
  view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9904
- className: _dsh_plugin_shop_css_d74581c2_default.log,
10025
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9905
10026
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9906
- className: _dsh_plugin_shop_css_d74581c2_default.logLine,
10027
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9907
10028
  "data-shop-log-line": true,
9908
10029
  children: line
9909
10030
  }, index))
9910
10031
  }),
9911
10032
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9912
- className: _dsh_plugin_shop_css_d74581c2_default.notice,
10033
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
9913
10034
  "data-shop-uninstall-done": true,
9914
10035
  children: t(uninstallActivationNoticeKey(view.activation))
9915
10036
  }),
@@ -9923,7 +10044,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9923
10044
  }),
9924
10045
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9925
10046
  type: "button",
9926
- className: _dsh_plugin_shop_css_d74581c2_default.cancelButton,
10047
+ className: _dsh_plugin_shop_css_5f753521_default.cancelButton,
9927
10048
  "data-shop-uninstall-dismiss": true,
9928
10049
  onClick: reset,
9929
10050
  children: t("dismiss")
@@ -9931,27 +10052,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9931
10052
  ]
9932
10053
  });
9933
10054
  if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9934
- className: _dsh_plugin_shop_css_d74581c2_default.installPanel,
10055
+ className: _dsh_plugin_shop_css_5f753521_default.installPanel,
9935
10056
  children: [
9936
10057
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9937
- className: _dsh_plugin_shop_css_d74581c2_default.failedHeading,
10058
+ className: _dsh_plugin_shop_css_5f753521_default.failedHeading,
9938
10059
  children: t("uninstallFailed")
9939
10060
  }),
9940
10061
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9941
- className: _dsh_plugin_shop_css_d74581c2_default.log,
10062
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9942
10063
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9943
- className: _dsh_plugin_shop_css_d74581c2_default.logLine,
10064
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9944
10065
  "data-shop-log-line": true,
9945
10066
  children: line
9946
10067
  }, index))
9947
10068
  }),
9948
10069
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9949
- className: _dsh_plugin_shop_css_d74581c2_default.failedDetail,
10070
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
9950
10071
  children: view.detail === "" ? t("uninstallTransportFailed") : view.detail
9951
10072
  }),
9952
10073
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9953
10074
  type: "button",
9954
- className: _dsh_plugin_shop_css_d74581c2_default.uninstallButton,
10075
+ className: _dsh_plugin_shop_css_5f753521_default.uninstallButton,
9955
10076
  "data-shop-uninstall-retry": true,
9956
10077
  onClick: () => void start({ name }),
9957
10078
  children: t("retry")
@@ -9961,7 +10082,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9961
10082
  if (view.kind !== "idle") return null;
9962
10083
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9963
10084
  type: "button",
9964
- className: _dsh_plugin_shop_css_d74581c2_default.uninstallButton,
10085
+ className: _dsh_plugin_shop_css_5f753521_default.uninstallButton,
9965
10086
  "data-shop-uninstall": true,
9966
10087
  onClick: () => void start({ name }),
9967
10088
  children: t("uninstall")
@@ -10002,38 +10123,39 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10002
10123
  else if (outcome.kind === "unreachable") setRefusal(t("restartTransportFailed"));
10003
10124
  };
10004
10125
  if (monitor?.kind === "requesting" || monitor?.kind === "restarting") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10005
- className: _dsh_plugin_shop_css_d74581c2_default.notice,
10126
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10006
10127
  "data-shop-restarting": true,
10007
10128
  children: t("restarting")
10008
10129
  });
10009
10130
  if (monitor?.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10010
- className: _dsh_plugin_shop_css_d74581c2_default.failedDetail,
10131
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
10011
10132
  "data-shop-restart-error": true,
10012
10133
  children: monitor.logFile === void 0 ? t("restartFailedNotice") : t("restartFailedLogNotice", { log: monitor.logFile })
10013
10134
  });
10014
10135
  if (monitor?.kind === "not-restarted") return null;
10015
10136
  if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10016
- className: _dsh_plugin_shop_css_d74581c2_default.gate,
10137
+ className: _dsh_plugin_shop_css_5f753521_default.gate,
10138
+ "data-shop-restart-gate": true,
10017
10139
  children: [
10018
10140
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10019
- className: _dsh_plugin_shop_css_d74581c2_default.gateTitle,
10141
+ className: _dsh_plugin_shop_css_5f753521_default.gateTitle,
10020
10142
  children: t("restartTitle")
10021
10143
  }),
10022
10144
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10023
- className: _dsh_plugin_shop_css_d74581c2_default.gateBody,
10145
+ className: _dsh_plugin_shop_css_5f753521_default.gateBody,
10024
10146
  children: t("restartBody")
10025
10147
  }),
10026
10148
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10027
- className: _dsh_plugin_shop_css_d74581c2_default.gateActions,
10149
+ className: _dsh_plugin_shop_css_5f753521_default.gateActions,
10028
10150
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10029
10151
  type: "button",
10030
- className: _dsh_plugin_shop_css_d74581c2_default.confirmButton,
10152
+ className: _dsh_plugin_shop_css_5f753521_default.confirmButton,
10031
10153
  "data-shop-restart-confirm": true,
10032
10154
  onClick: () => void onConfirm(),
10033
10155
  children: t("restartConfirm")
10034
10156
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10035
10157
  type: "button",
10036
- className: _dsh_plugin_shop_css_d74581c2_default.cancelButton,
10158
+ className: _dsh_plugin_shop_css_5f753521_default.cancelButton,
10037
10159
  onClick: () => setGateOpen(false),
10038
10160
  children: t("cancel")
10039
10161
  })]
@@ -10041,14 +10163,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10041
10163
  ]
10042
10164
  });
10043
10165
  const refused = refusal === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10044
- className: _dsh_plugin_shop_css_d74581c2_default.failedDetail,
10166
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
10045
10167
  "data-shop-restart-error": true,
10046
10168
  children: refusal
10047
10169
  });
10048
10170
  if (gate !== void 0) return refused;
10049
10171
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refused, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10050
10172
  type: "button",
10051
- className: _dsh_plugin_shop_css_d74581c2_default.restartButton,
10173
+ className: _dsh_plugin_shop_css_5f753521_default.restartButton,
10052
10174
  "data-shop-restart": true,
10053
10175
  onClick: () => setGateOpen(true),
10054
10176
  children: t("restart")
@@ -10066,13 +10188,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10066
10188
  */
10067
10189
  function NotRestartedNotice({ t, pid, logFile }) {
10068
10190
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10069
- className: _dsh_plugin_shop_css_d74581c2_default.selfUpdatePanel,
10191
+ className: _dsh_plugin_shop_css_5f753521_default.selfUpdatePanel,
10070
10192
  "data-shop-not-restarted": true,
10071
10193
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10072
- className: _dsh_plugin_shop_css_d74581c2_default.failedDetail,
10194
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
10073
10195
  children: pid === void 0 ? t("notRestartedNotice") : t("notRestartedPidNotice", { pid: String(pid) })
10074
10196
  }), logFile !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10075
- className: _dsh_plugin_shop_css_d74581c2_default.failedDetail,
10197
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
10076
10198
  children: t("notRestartedLogNotice", { log: logFile })
10077
10199
  })]
10078
10200
  });
@@ -10102,7 +10224,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10102
10224
  restart,
10103
10225
  reload
10104
10226
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10105
- className: _dsh_plugin_shop_css_d74581c2_default.notice,
10227
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10106
10228
  "data-shop-restart-disabled": true,
10107
10229
  children: t(restartBlockedNoticeKey(restartBlocked))
10108
10230
  });
@@ -10113,14 +10235,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10113
10235
  * would help without knowing that now is a good time. */
10114
10236
  function ReloadPanel({ t, reload, where }) {
10115
10237
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10116
- className: _dsh_plugin_shop_css_d74581c2_default.reloadPanel,
10238
+ className: _dsh_plugin_shop_css_5f753521_default.reloadPanel,
10117
10239
  "data-shop-reload": where,
10118
10240
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10119
- className: _dsh_plugin_shop_css_d74581c2_default.notice,
10241
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10120
10242
  children: t("reloadNote")
10121
10243
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10122
10244
  type: "button",
10123
- className: _dsh_plugin_shop_css_d74581c2_default.reloadButton,
10245
+ className: _dsh_plugin_shop_css_5f753521_default.reloadButton,
10124
10246
  onClick: reload,
10125
10247
  children: t("reload")
10126
10248
  })]
@@ -10171,7 +10293,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10171
10293
  }
10172
10294
  };
10173
10295
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10174
- className: _dsh_plugin_shop_css_d74581c2_default.switchWrap,
10296
+ className: _dsh_plugin_shop_css_5f753521_default.switchWrap,
10175
10297
  "data-shop-enabled-switch": row.name,
10176
10298
  children: [
10177
10299
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -10180,18 +10302,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10180
10302
  "aria-checked": enabled,
10181
10303
  "aria-label": t("enabledSwitch"),
10182
10304
  "data-shop-toggle": true,
10183
- className: `${_dsh_plugin_shop_css_d74581c2_default.switch} ${enabled ? _dsh_plugin_shop_css_d74581c2_default.switchOn : ""}`,
10305
+ className: `${_dsh_plugin_shop_css_5f753521_default.switch} ${enabled ? _dsh_plugin_shop_css_5f753521_default.switchOn : ""}`,
10184
10306
  onClick: () => void onToggle(),
10185
10307
  disabled: toggle.kind === "saving",
10186
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_d74581c2_default.switchKnob })
10308
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_5f753521_default.switchKnob })
10187
10309
  }),
10188
10310
  toggle.kind === "saved" && owed === "nothing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10189
- className: _dsh_plugin_shop_css_d74581c2_default.notice,
10311
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10190
10312
  "data-shop-hot-apply": true,
10191
10313
  children: t("hotApplyNote")
10192
10314
  }),
10193
10315
  owed === "restart" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10194
- className: _dsh_plugin_shop_css_d74581c2_default.notice,
10316
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10195
10317
  "data-shop-toggle-restart": true,
10196
10318
  children: t("toggleRestartNote")
10197
10319
  }),
@@ -10204,7 +10326,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10204
10326
  where: "toggle"
10205
10327
  }),
10206
10328
  toggle.kind === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10207
- className: _dsh_plugin_shop_css_d74581c2_default.failedDetail,
10329
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
10208
10330
  "data-shop-toggle-error": true,
10209
10331
  children: toggle.detail
10210
10332
  })
@@ -10219,19 +10341,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10219
10341
  * field is narrowed at the parameter. */
10220
10342
  function OutdatedRow({ row, tier, missing, harness, t, setEnabled, flowFor, restart, restartBlocked, reload, restarts }) {
10221
10343
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10222
- className: _dsh_plugin_shop_css_d74581c2_default.outdatedRow,
10344
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedRow,
10223
10345
  "data-shop-outdated-entry": row.name,
10224
10346
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10225
- className: _dsh_plugin_shop_css_d74581c2_default.outdatedInfo,
10347
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedInfo,
10226
10348
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10227
- className: _dsh_plugin_shop_css_d74581c2_default.name,
10349
+ className: _dsh_plugin_shop_css_5f753521_default.name,
10228
10350
  children: row.name
10229
10351
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10230
- className: _dsh_plugin_shop_css_d74581c2_default.outdatedVersions,
10352
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedVersions,
10231
10353
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("installedVersion", { version: versionLabel(row.source, row.installed).text }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("latestVersion", { version: versionLabel(row.source, row.latest).text }) })]
10232
10354
  })]
10233
10355
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10234
- className: _dsh_plugin_shop_css_d74581c2_default.outdatedActions,
10356
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedActions,
10235
10357
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
10236
10358
  row,
10237
10359
  t,
@@ -10274,28 +10396,30 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10274
10396
  * "Installed" until 2026-09-16, which was the Installed filter's label
10275
10397
  * character for character in both languages, over a strict subset of what
10276
10398
  * that filter selects. Internals stay `outdated` — the Host field name. The
10277
- * tier for the update gate is looked up from the catalog by install IDENTITY,
10278
- * never by name (community → acknowledgement); an entry absent from the
10279
- * catalog defaults to the community gate (the safer read). */
10399
+ * tier is looked up from the catalog by install IDENTITY, never by name, and
10400
+ * decides whether the update's request carries the acknowledgement (a
10401
+ * community entry's does); an entry absent from the catalog is read as
10402
+ * community, so its request acknowledges, which the host accepts for any
10403
+ * tier. */
10280
10404
  function OutdatedSection({ state, entriesByKey, missingByKey, harnessVerdicts, t, setEnabled, flowFor, restart, restartBlocked, reload, restarts }) {
10281
10405
  if (state.kind === "loading") return null;
10282
10406
  if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10283
- className: _dsh_plugin_shop_css_d74581c2_default.stateLine,
10407
+ className: _dsh_plugin_shop_css_5f753521_default.stateLine,
10284
10408
  "data-shop-outdated-error": true,
10285
10409
  children: t("installedError")
10286
10410
  });
10287
10411
  const outdated = state.entries.filter((entry) => entry.outdated && entry.latest !== null);
10288
10412
  if (outdated.length === 0) return null;
10289
10413
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
10290
- className: _dsh_plugin_shop_css_d74581c2_default.outdatedSection,
10414
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedSection,
10291
10415
  "data-shop-outdated": true,
10292
10416
  "aria-labelledby": UPDATABLE_HEADING_ID,
10293
10417
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
10294
- className: _dsh_plugin_shop_css_d74581c2_default.updatableHeading,
10418
+ className: _dsh_plugin_shop_css_5f753521_default.updatableHeading,
10295
10419
  id: UPDATABLE_HEADING_ID,
10296
10420
  children: t("updatableSection")
10297
10421
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10298
- className: _dsh_plugin_shop_css_d74581c2_default.outdatedList,
10422
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedList,
10299
10423
  children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
10300
10424
  row,
10301
10425
  tier: entriesByKey.get(identityKey(row))?.tier ?? "community",
@@ -10323,37 +10447,37 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10323
10447
  const shadowed = entries.filter((entry) => entry.shadowed === true);
10324
10448
  if (shadowed.length === 0) return null;
10325
10449
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
10326
- className: _dsh_plugin_shop_css_d74581c2_default.shadowedSection,
10450
+ className: _dsh_plugin_shop_css_5f753521_default.shadowedSection,
10327
10451
  "data-shop-shadowed": true,
10328
10452
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10329
- className: _dsh_plugin_shop_css_d74581c2_default.outdatedList,
10453
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedList,
10330
10454
  children: shadowed.map((row) => {
10331
10455
  const key = identityKey(row);
10332
10456
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10333
- className: _dsh_plugin_shop_css_d74581c2_default.outdatedRow,
10457
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedRow,
10334
10458
  "data-shop-shadowed-entry": row.name,
10335
10459
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10336
- className: _dsh_plugin_shop_css_d74581c2_default.outdatedInfo,
10460
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedInfo,
10337
10461
  children: [
10338
10462
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10339
- className: _dsh_plugin_shop_css_d74581c2_default.name,
10463
+ className: _dsh_plugin_shop_css_5f753521_default.name,
10340
10464
  children: row.name
10341
10465
  }),
10342
10466
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10343
- className: _dsh_plugin_shop_css_d74581c2_default.badges,
10467
+ className: _dsh_plugin_shop_css_5f753521_default.badges,
10344
10468
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10345
- className: _dsh_plugin_shop_css_d74581c2_default.tierBadge,
10469
+ className: _dsh_plugin_shop_css_5f753521_default.tierBadge,
10346
10470
  "data-tier": "shadowed",
10347
10471
  children: t("shadowedBadge")
10348
10472
  })
10349
10473
  }),
10350
10474
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10351
- className: _dsh_plugin_shop_css_d74581c2_default.shadowedDetail,
10475
+ className: _dsh_plugin_shop_css_5f753521_default.shadowedDetail,
10352
10476
  children: t("shadowedDetail")
10353
10477
  })
10354
10478
  ]
10355
10479
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10356
- className: _dsh_plugin_shop_css_d74581c2_default.outdatedActions,
10480
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedActions,
10357
10481
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
10358
10482
  name: row.name,
10359
10483
  t,
@@ -10403,16 +10527,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10403
10527
  role: "alert",
10404
10528
  children: [
10405
10529
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10406
- className: _dsh_plugin_shop_css_d74581c2_default.stateLine,
10530
+ className: _dsh_plugin_shop_css_5f753521_default.stateLine,
10407
10531
  children: say("tabCrashed", en.tabCrashed)
10408
10532
  }),
10409
10533
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10410
- className: _dsh_plugin_shop_css_d74581c2_default.failedDetail,
10534
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
10411
10535
  children: error.message
10412
10536
  }),
10413
10537
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10414
10538
  type: "button",
10415
- className: _dsh_plugin_shop_css_d74581c2_default.actionButton,
10539
+ className: _dsh_plugin_shop_css_5f753521_default.actionButton,
10416
10540
  onClick: () => this.setState({ error: null }),
10417
10541
  children: say("retry", en.retry)
10418
10542
  })
@@ -10709,19 +10833,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10709
10833
  "data-shop-tab": true,
10710
10834
  "aria-busy": "true",
10711
10835
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10712
- className: _dsh_plugin_shop_css_d74581c2_default.srOnly,
10836
+ className: _dsh_plugin_shop_css_5f753521_default.srOnly,
10713
10837
  children: t("loading")
10714
10838
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10715
- className: _dsh_plugin_shop_css_d74581c2_default.skeletonGrid,
10839
+ className: _dsh_plugin_shop_css_5f753521_default.skeletonGrid,
10716
10840
  "data-shop-skeleton": true,
10717
10841
  "aria-hidden": "true",
10718
10842
  children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10719
- className: _dsh_plugin_shop_css_d74581c2_default.skeletonCard,
10843
+ className: _dsh_plugin_shop_css_5f753521_default.skeletonCard,
10720
10844
  children: [
10721
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_d74581c2_default.skeletonName }),
10722
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_d74581c2_default.skeletonSummary }),
10723
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_d74581c2_default.skeletonSummaryShort }),
10724
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_d74581c2_default.skeletonActions })
10845
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_5f753521_default.skeletonName }),
10846
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_5f753521_default.skeletonSummary }),
10847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_5f753521_default.skeletonSummaryShort }),
10848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_5f753521_default.skeletonActions })
10725
10849
  ]
10726
10850
  }, i))
10727
10851
  })]
@@ -10731,11 +10855,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10731
10855
  translate: "no",
10732
10856
  "data-shop-tab": true,
10733
10857
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10734
- className: _dsh_plugin_shop_css_d74581c2_default.stateLine,
10858
+ className: _dsh_plugin_shop_css_5f753521_default.stateLine,
10735
10859
  children: t("error")
10736
10860
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10737
10861
  type: "button",
10738
- className: _dsh_plugin_shop_css_d74581c2_default.actionButton,
10862
+ className: _dsh_plugin_shop_css_5f753521_default.actionButton,
10739
10863
  onClick: () => {
10740
10864
  setRequest({ kind: "retry" });
10741
10865
  setVersionReload((current) => current + 1);
@@ -10750,11 +10874,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10750
10874
  "data-shop-tab": true,
10751
10875
  children: [
10752
10876
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10753
- className: _dsh_plugin_shop_css_d74581c2_default.toolbar,
10877
+ className: _dsh_plugin_shop_css_5f753521_default.toolbar,
10754
10878
  children: [
10755
10879
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
10756
10880
  type: "search",
10757
- className: _dsh_plugin_shop_css_d74581c2_default.searchInput,
10881
+ className: _dsh_plugin_shop_css_5f753521_default.searchInput,
10758
10882
  "aria-label": t("search"),
10759
10883
  placeholder: t("search"),
10760
10884
  value: query,
@@ -10765,31 +10889,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10765
10889
  }),
10766
10890
  expansionMemo.intents.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10767
10891
  "data-shop-expansion-note": true,
10768
- className: _dsh_plugin_shop_css_d74581c2_default.expansionNote,
10892
+ className: _dsh_plugin_shop_css_5f753521_default.expansionNote,
10769
10893
  children: t("zhExpansionNote", { terms: expansionMemo.terms.slice(1).map((term) => term.startsWith("#") ? term.slice(1) : term).join(", ") })
10770
10894
  }),
10771
10895
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10772
- className: _dsh_plugin_shop_css_d74581c2_default.toolbarRight,
10896
+ className: _dsh_plugin_shop_css_5f753521_default.toolbarRight,
10773
10897
  children: [
10774
10898
  result.stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10775
- className: _dsh_plugin_shop_css_d74581c2_default.staleBadge,
10899
+ className: _dsh_plugin_shop_css_5f753521_default.staleBadge,
10776
10900
  children: t("staleLabel", { date: result.builtAt.slice(0, 10) })
10777
10901
  }),
10778
10902
  selfVersion !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10779
- className: _dsh_plugin_shop_css_d74581c2_default.versionBlock,
10903
+ className: _dsh_plugin_shop_css_5f753521_default.versionBlock,
10780
10904
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10781
- className: _dsh_plugin_shop_css_d74581c2_default.versionText,
10905
+ className: _dsh_plugin_shop_css_5f753521_default.versionText,
10782
10906
  "data-shop-version": true,
10783
10907
  children: ["v", selfVersion.installed]
10784
10908
  }), restartMonitor !== null ? null : selfPending && restartBlocked === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10785
10909
  type: "button",
10786
- className: _dsh_plugin_shop_css_d74581c2_default.restartSelfButton,
10910
+ className: _dsh_plugin_shop_css_5f753521_default.restartSelfButton,
10787
10911
  "data-shop-restart": true,
10788
10912
  onClick: () => setSelfRestartGate(true),
10789
10913
  children: t("restart")
10790
10914
  }) : !selfPending && selfVersion.outdated && selfVersion.latest !== null && selfUpdate.view.kind === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10791
10915
  type: "button",
10792
- className: _dsh_plugin_shop_css_d74581c2_default.updateSelfButton,
10916
+ className: _dsh_plugin_shop_css_5f753521_default.updateSelfButton,
10793
10917
  "data-shop-update-self": true,
10794
10918
  onClick: () => {
10795
10919
  if (selfVersion.latest !== null) selfUpdate.start({ version: selfVersion.latest });
@@ -10797,7 +10921,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10797
10921
  children: t("update")
10798
10922
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10799
10923
  type: "button",
10800
- className: _dsh_plugin_shop_css_d74581c2_default.checkUpdateButton,
10924
+ className: _dsh_plugin_shop_css_5f753521_default.checkUpdateButton,
10801
10925
  "data-shop-check-update": true,
10802
10926
  title: t("checkUpdateTitle"),
10803
10927
  disabled: checkState !== "idle",
@@ -10806,7 +10930,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10806
10930
  })]
10807
10931
  }),
10808
10932
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
10809
- className: _dsh_plugin_shop_css_d74581c2_default.githubLink,
10933
+ className: _dsh_plugin_shop_css_5f753521_default.githubLink,
10810
10934
  href: SHOP_REPO_URL,
10811
10935
  target: "_blank",
10812
10936
  rel: "noopener noreferrer",
@@ -10831,28 +10955,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10831
10955
  logFile: restartMonitor.logFile
10832
10956
  }),
10833
10957
  selfUpdate.view.kind === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10834
- className: _dsh_plugin_shop_css_d74581c2_default.selfUpdatePanel,
10958
+ className: _dsh_plugin_shop_css_5f753521_default.selfUpdatePanel,
10835
10959
  "data-shop-self-updating": true,
10836
10960
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10837
- className: _dsh_plugin_shop_css_d74581c2_default.installing,
10961
+ className: _dsh_plugin_shop_css_5f753521_default.installing,
10838
10962
  children: t(installPhaseKey(selfUpdate.view.phase))
10839
10963
  }), selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10840
- className: _dsh_plugin_shop_css_d74581c2_default.log,
10964
+ className: _dsh_plugin_shop_css_5f753521_default.log,
10841
10965
  children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10842
- className: _dsh_plugin_shop_css_d74581c2_default.logLine,
10966
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
10843
10967
  "data-shop-log-line": true,
10844
10968
  children: line
10845
10969
  }, index))
10846
10970
  })]
10847
10971
  }),
10848
10972
  selfPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10849
- className: _dsh_plugin_shop_css_d74581c2_default.selfUpdatePanel,
10973
+ className: _dsh_plugin_shop_css_5f753521_default.selfUpdatePanel,
10850
10974
  "data-shop-self-update-done": true,
10851
10975
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10852
- className: _dsh_plugin_shop_css_d74581c2_default.installedActions,
10976
+ className: _dsh_plugin_shop_css_5f753521_default.installedActions,
10853
10977
  children: [
10854
10978
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10855
- className: _dsh_plugin_shop_css_d74581c2_default.notice,
10979
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10856
10980
  children: t("installedRestartNotice")
10857
10981
  }),
10858
10982
  restartBlocked === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
@@ -10865,7 +10989,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10865
10989
  }
10866
10990
  }),
10867
10991
  restartBlocked !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10868
- className: _dsh_plugin_shop_css_d74581c2_default.notice,
10992
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10869
10993
  "data-shop-restart-disabled": true,
10870
10994
  children: t(restartBlockedNoticeKey(restartBlocked))
10871
10995
  })
@@ -10873,35 +10997,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10873
10997
  })
10874
10998
  }),
10875
10999
  selfUpdate.view.kind === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10876
- className: _dsh_plugin_shop_css_d74581c2_default.selfUpdatePanel,
11000
+ className: _dsh_plugin_shop_css_5f753521_default.selfUpdatePanel,
10877
11001
  "data-shop-self-update-failed": true,
10878
11002
  children: [
10879
11003
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10880
- className: _dsh_plugin_shop_css_d74581c2_default.failedHeading,
11004
+ className: _dsh_plugin_shop_css_5f753521_default.failedHeading,
10881
11005
  children: t("updateFailed")
10882
11006
  }),
10883
11007
  selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10884
- className: _dsh_plugin_shop_css_d74581c2_default.log,
11008
+ className: _dsh_plugin_shop_css_5f753521_default.log,
10885
11009
  children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10886
- className: _dsh_plugin_shop_css_d74581c2_default.logLine,
11010
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
10887
11011
  "data-shop-log-line": true,
10888
11012
  children: line
10889
11013
  }, index))
10890
11014
  }),
10891
11015
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10892
- className: _dsh_plugin_shop_css_d74581c2_default.failedDetail,
11016
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
10893
11017
  children: selfUpdate.view.detail === "" ? t("updateTransportFailed") : selfUpdate.view.detail
10894
11018
  })
10895
11019
  ]
10896
11020
  }),
10897
11021
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10898
- className: _dsh_plugin_shop_css_d74581c2_default.categoryBar,
11022
+ className: _dsh_plugin_shop_css_5f753521_default.categoryBar,
10899
11023
  role: "group",
10900
11024
  "aria-label": t("catalog"),
10901
11025
  children: [
10902
11026
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10903
11027
  type: "button",
10904
- className: category === null ? `${_dsh_plugin_shop_css_d74581c2_default.categoryButton} ${_dsh_plugin_shop_css_d74581c2_default.categoryButtonOn}` : _dsh_plugin_shop_css_d74581c2_default.categoryButton,
11028
+ className: category === null ? `${_dsh_plugin_shop_css_5f753521_default.categoryButton} ${_dsh_plugin_shop_css_5f753521_default.categoryButtonOn}` : _dsh_plugin_shop_css_5f753521_default.categoryButton,
10905
11029
  "aria-pressed": category === null,
10906
11030
  onClick: () => {
10907
11031
  setCategory(null);
@@ -10916,7 +11040,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10916
11040
  }),
10917
11041
  CATEGORY_ORDER.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10918
11042
  type: "button",
10919
- className: category === key ? `${_dsh_plugin_shop_css_d74581c2_default.categoryButton} ${_dsh_plugin_shop_css_d74581c2_default.categoryButtonOn}` : _dsh_plugin_shop_css_d74581c2_default.categoryButton,
11043
+ className: category === key ? `${_dsh_plugin_shop_css_5f753521_default.categoryButton} ${_dsh_plugin_shop_css_5f753521_default.categoryButtonOn}` : _dsh_plugin_shop_css_5f753521_default.categoryButton,
10920
11044
  "aria-pressed": category === key,
10921
11045
  "data-category": key,
10922
11046
  "data-shop-category-tab": key,
@@ -10932,7 +11056,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10932
11056
  }, key)),
10933
11057
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10934
11058
  type: "button",
10935
- className: category === "installed" ? `${_dsh_plugin_shop_css_d74581c2_default.categoryButton} ${_dsh_plugin_shop_css_d74581c2_default.categoryButtonOn}` : _dsh_plugin_shop_css_d74581c2_default.categoryButton,
11059
+ className: category === "installed" ? `${_dsh_plugin_shop_css_5f753521_default.categoryButton} ${_dsh_plugin_shop_css_5f753521_default.categoryButtonOn}` : _dsh_plugin_shop_css_5f753521_default.categoryButton,
10936
11060
  "aria-pressed": category === "installed",
10937
11061
  "data-shop-category-installed": true,
10938
11062
  onClick: () => {
@@ -10949,7 +11073,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10949
11073
  type: "button",
10950
11074
  role: "switch",
10951
11075
  "aria-checked": hideIncompatible,
10952
- className: hideIncompatible ? `${_dsh_plugin_shop_css_d74581c2_default.incompatibleFilter} ${_dsh_plugin_shop_css_d74581c2_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_d74581c2_default.incompatibleFilter,
11076
+ className: hideIncompatible ? `${_dsh_plugin_shop_css_5f753521_default.incompatibleFilter} ${_dsh_plugin_shop_css_5f753521_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_5f753521_default.incompatibleFilter,
10953
11077
  title: t(hideIncompatible ? "showIncompatibleTitle" : "incompatibleFilterTitle"),
10954
11078
  "data-shop-hide-incompatible": true,
10955
11079
  onClick: () => {
@@ -10957,18 +11081,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10957
11081
  setVisibleCount(48);
10958
11082
  },
10959
11083
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10960
- className: hideIncompatible ? `${_dsh_plugin_shop_css_d74581c2_default.switch} ${_dsh_plugin_shop_css_d74581c2_default.switchOn}` : _dsh_plugin_shop_css_d74581c2_default.switch,
11084
+ className: hideIncompatible ? `${_dsh_plugin_shop_css_5f753521_default.switch} ${_dsh_plugin_shop_css_5f753521_default.switchOn}` : _dsh_plugin_shop_css_5f753521_default.switch,
10961
11085
  "aria-hidden": "true",
10962
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_d74581c2_default.switchKnob })
11086
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_5f753521_default.switchKnob })
10963
11087
  }), t("hideIncompatible", { count: incompatibleCount })]
10964
11088
  })
10965
11089
  ]
10966
11090
  }),
10967
11091
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10968
- className: _dsh_plugin_shop_css_d74581c2_default.catalogStatsRow,
11092
+ className: _dsh_plugin_shop_css_5f753521_default.catalogStatsRow,
10969
11093
  children: [
10970
11094
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10971
- className: _dsh_plugin_shop_css_d74581c2_default.catalogStats,
11095
+ className: _dsh_plugin_shop_css_5f753521_default.catalogStats,
10972
11096
  "data-shop-catalog-stats": true,
10973
11097
  children: t("catalogStats", {
10974
11098
  count: String(browsable.length),
@@ -10977,7 +11101,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10977
11101
  }),
10978
11102
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10979
11103
  type: "button",
10980
- className: _dsh_plugin_shop_css_d74581c2_default.catalogRefreshButton,
11104
+ className: _dsh_plugin_shop_css_5f753521_default.catalogRefreshButton,
10981
11105
  "data-shop-catalog-refresh": true,
10982
11106
  disabled: reloading,
10983
11107
  onClick: () => {
@@ -10989,22 +11113,22 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10989
11113
  children: reloading ? t("refreshing") : t("refresh")
10990
11114
  }),
10991
11115
  reloadFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10992
- className: _dsh_plugin_shop_css_d74581c2_default.catalogRefreshFailed,
11116
+ className: _dsh_plugin_shop_css_5f753521_default.catalogRefreshFailed,
10993
11117
  "data-shop-catalog-refresh-failed": true,
10994
11118
  children: t("refreshFailed")
10995
11119
  }),
10996
11120
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10997
- className: _dsh_plugin_shop_css_d74581c2_default.sortGroup,
11121
+ className: _dsh_plugin_shop_css_5f753521_default.sortGroup,
10998
11122
  role: "group",
10999
11123
  "aria-labelledby": sortLabelId,
11000
11124
  "data-shop-sort": true,
11001
11125
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11002
- className: _dsh_plugin_shop_css_d74581c2_default.sortLabel,
11126
+ className: _dsh_plugin_shop_css_5f753521_default.sortLabel,
11003
11127
  id: sortLabelId,
11004
11128
  children: t("sortLabel")
11005
11129
  }), SHELF_ORDERS.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11006
11130
  type: "button",
11007
- className: order === name ? `${_dsh_plugin_shop_css_d74581c2_default.categoryButton} ${_dsh_plugin_shop_css_d74581c2_default.categoryButtonOn}` : _dsh_plugin_shop_css_d74581c2_default.categoryButton,
11131
+ className: order === name ? `${_dsh_plugin_shop_css_5f753521_default.categoryButton} ${_dsh_plugin_shop_css_5f753521_default.categoryButtonOn}` : _dsh_plugin_shop_css_5f753521_default.categoryButton,
11008
11132
  "aria-pressed": order === name,
11009
11133
  "data-shop-order": name,
11010
11134
  onClick: () => {
@@ -11016,15 +11140,20 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11016
11140
  })
11017
11141
  ]
11018
11142
  }),
11143
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11144
+ className: _dsh_plugin_shop_css_5f753521_default.disclosure,
11145
+ "data-shop-disclosure": true,
11146
+ children: t("disclosure")
11147
+ }),
11019
11148
  result.plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11020
- className: _dsh_plugin_shop_css_d74581c2_default.emptyLine,
11149
+ className: _dsh_plugin_shop_css_5f753521_default.emptyLine,
11021
11150
  children: t("empty")
11022
11151
  }) : filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11023
- className: _dsh_plugin_shop_css_d74581c2_default.emptyLine,
11152
+ className: _dsh_plugin_shop_css_5f753521_default.emptyLine,
11024
11153
  "data-shop-empty": true,
11025
11154
  children: matched.length > 0 ? t("emptyIncompatibleFiltered") : t("emptySearch")
11026
11155
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
11027
- className: _dsh_plugin_shop_css_d74581c2_default.cards,
11156
+ className: _dsh_plugin_shop_css_5f753521_default.cards,
11028
11157
  children: [visible.map((entry) => {
11029
11158
  const key = entryKey(entry);
11030
11159
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
@@ -11048,12 +11177,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11048
11177
  }) }, key);
11049
11178
  }), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
11050
11179
  ref: sentinelRef,
11051
- className: _dsh_plugin_shop_css_d74581c2_default.cardsSentry,
11180
+ className: _dsh_plugin_shop_css_5f753521_default.cardsSentry,
11052
11181
  "data-shop-sentry": true,
11053
11182
  "aria-hidden": "true"
11054
11183
  })]
11055
11184
  }), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11056
- className: _dsh_plugin_shop_css_d74581c2_default.showingLine,
11185
+ className: _dsh_plugin_shop_css_5f753521_default.showingLine,
11057
11186
  "aria-live": "polite",
11058
11187
  children: t("showing", {
11059
11188
  shown: String(visibleCount),