dsh-plugin-shop 0.8.7-beta.0 → 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
@@ -4328,6 +4328,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4328
4328
  _undefined(),
4329
4329
  literal(false),
4330
4330
  literal(true)
4331
+ ]).optional(),
4332
+ "forked": union([
4333
+ _undefined(),
4334
+ literal(false),
4335
+ literal(true)
4331
4336
  ]).optional()
4332
4337
  })),
4333
4338
  "denied": array(object({
@@ -6478,10 +6483,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6478
6483
  return blockerBadgeKey(blockers) === "incompatibleBadge";
6479
6484
  }
6480
6485
  /**
6481
- * Whether this install is refused before it starts, so its button is
6482
- * disabled rather than opening the acknowledgement gate for an install that
6483
- * cannot proceed: by the host, for a taken name, or by the running dsh, for
6484
- * 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.
6485
6490
  *
6486
6491
  * The second is the one harness reason that blocks (design
6487
6492
  * 2026-09-26-dsh-017-readiness, B1, amending "warn, never block"): dsh 0.1.7
@@ -6489,9 +6494,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6489
6494
  * entry after it, rolling the profile back, and the shop cannot install past
6490
6495
  * it. The verdict is dsh's own rule on the profile's own exemptions, whichever
6491
6496
  * channel the entry comes from (design
6492
- * 2026-09-28-bundle-components-and-github-peers, section 7.3), so a disabled
6493
- * button is dsh's answer given early, never the shop's opinion — and
6494
- * 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.
6495
6500
  */
6496
6501
  function refusesInstall(blockers) {
6497
6502
  return blockers.some((blocker) => blocker.kind === "name-taken" || blocker.kind === "harness-peers");
@@ -6527,11 +6532,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6527
6532
  if (spec === void 0) return void 0;
6528
6533
  return specVerdict(entry, spec) === "same" ? void 0 : holderLabel(spec, entry.name);
6529
6534
  }
6530
- /** Spec §9.3 verbatim — the community-tier acknowledgement. The zh dictionary
6531
- * states the same facts in its own register; never soften this wording. */
6532
- 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.";
6533
- /** Spec §9.3 in the zh register — the same facts, the same force. */
6534
- 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 命令,以及读取和修改发送给模型的请求。未标「已审核」的插件未经审核。";
6535
6548
  /** Install rejection code → locale key, for the rejected-state code label
6536
6549
  * (§7.2): every rejection the host can return has a human label here. */
6537
6550
  function rejectionCodeKey(code) {
@@ -6546,15 +6559,61 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6546
6559
  case "desktop-profile": return "desktopProfileCode";
6547
6560
  }
6548
6561
  }
6549
- /** The version a card displays: full for npm, the short commit for github. */
6550
- function displayVersion(entry) {
6551
- return entry.source === "github" ? entry.version.slice(0, 7) : entry.version;
6562
+ /** A commit pin as the registry writes one: forty lowercase hex. */
6563
+ const COMMIT_PIN = /^[0-9a-f]{40}$/;
6564
+ /**
6565
+ * How a version string reads on the shelf, decided by its source and its
6566
+ * shape alone (market borrowings §11).
6567
+ *
6568
+ * Shape, never the entry's `tarball`, because an installed row carries only
6569
+ * the pin the shop recorded at install — the catalog `version` of that moment
6570
+ * — and no `tarball`. The registry pins a commit as forty lowercase hex and a
6571
+ * release by its tag, so the two shapes do not cross; `present.test.ts`
6572
+ * checks that against the committed live catalog.
6573
+ *
6574
+ * - An npm version takes the `v` the shelf has always shown, and only before
6575
+ * a digit: an installed row reports the manifest's spec, and a range there
6576
+ * (`^1.2.3`) is not a version to prefix.
6577
+ * - A commit is cut to the seven characters git abbreviates to, with no `v`:
6578
+ * it is not a version number.
6579
+ * - A release tag prints exactly as tagged, never cut and never prefixed:
6580
+ * cutting `v0.29.10` to seven characters printed `v0.29.1`, another
6581
+ * version, and the `v` this function no longer adds doubled the tag's own.
6582
+ */
6583
+ function versionLabel(source, version) {
6584
+ if (source === "npm") return {
6585
+ kind: "npm",
6586
+ text: /^\d/.test(version) ? `v${version}` : version,
6587
+ value: version
6588
+ };
6589
+ if (COMMIT_PIN.test(version)) {
6590
+ const short = version.slice(0, 7);
6591
+ return {
6592
+ kind: "commit",
6593
+ text: short,
6594
+ value: short
6595
+ };
6596
+ }
6597
+ return {
6598
+ kind: "release",
6599
+ text: version,
6600
+ value: version
6601
+ };
6602
+ }
6603
+ /** The copy that names a version's kind, for the accessible name and the
6604
+ * tooltip; its `{version}` takes {@link VersionLabel.value}. */
6605
+ function versionLabelKey(kind) {
6606
+ switch (kind) {
6607
+ case "npm": return "npmVersionLabel";
6608
+ case "commit": return "commitVersionLabel";
6609
+ case "release": return "releaseVersionLabel";
6610
+ }
6552
6611
  }
6553
6612
  /** The hash pin a stale line names for a repo or release-rescued entry — the
6554
6613
  * reviewed commit, or the reviewed release tarball sha256 (market borrowings
6555
- * §3.1). Shortened to 7 chars like the card's own version; '' only for a
6556
- * hand-built fixture carrying no pin. The npm form is a version and keeps the
6557
- * version line's `v` prefix instead. */
6614
+ * §3.1). Shortened to 7 chars like a commit on the card; '' only for a
6615
+ * hand-built fixture carrying no pin. The npm form is a version, which
6616
+ * {@link versionLabel} prints instead. */
6558
6617
  function reviewHashPin(review) {
6559
6618
  return (review.reviewedCommit ?? review.reviewedSha256 ?? "").slice(0, 7);
6560
6619
  }
@@ -6679,6 +6738,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6679
6738
  }
6680
6739
  }
6681
6740
  }
6741
+ /**
6742
+ * Which order the shelf is in (design 2026-10-12-shelf-orders). `stars` is
6743
+ * the default AND the tie-break of both date orders, so the two date orders
6744
+ * are stable re-sorts of it rather than orders with comparators of their own.
6745
+ */
6746
+ const SHELF_ORDER_KEYS = {
6747
+ stars: "sortStars",
6748
+ added: "sortAdded",
6749
+ updated: "sortUpdated"
6750
+ };
6751
+ /** The three orders in display order (map insertion order). `readonly`: this
6752
+ * is a shared vocabulary, and an order list a caller can push to is a bug
6753
+ * waiting for its second caller. */
6754
+ const SHELF_ORDERS = Object.keys(SHELF_ORDER_KEYS);
6755
+ /** The locale key naming one order (the toggle's buttons). */
6756
+ function shelfOrderLocaleKey(order) {
6757
+ return SHELF_ORDER_KEYS[order];
6758
+ }
6682
6759
  const CATEGORY_KEYS = {
6683
6760
  tool: "categoryTool",
6684
6761
  provider: "categoryProvider",
@@ -6825,6 +6902,77 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6825
6902
  return NAME_COLLATOR.compare(a.name, b.name);
6826
6903
  });
6827
6904
  }
6905
+ /** The day form the registry writes into `added` (`emit.ts` E9 enforces it
6906
+ * on every build on its side; a catalog is untrusted input on this side). */
6907
+ const DAY_FORM = /^\d{4}-\d{2}-\d{2}$/;
6908
+ /**
6909
+ * The ES date-time form WITH an explicit zone. The zone is required, and not
6910
+ * for tidiness: a date-time string with no zone is parsed as LOCAL time, so
6911
+ * accepting one would make the shelf's order depend on the machine.
6912
+ */
6913
+ const INSTANT_FORM = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})$/;
6914
+ /**
6915
+ * One date string as milliseconds, or undefined when it cannot be trusted.
6916
+ *
6917
+ * The form is checked BEFORE `Date.parse`, and that order is load-bearing:
6918
+ * V8 answers a real instant for `2026-02-31T00:00:00Z` (3 March) and for
6919
+ * `October 12` (a 2001 date), so `Date.parse` alone is not a validity test.
6920
+ */
6921
+ function parseForm(value, form) {
6922
+ if (typeof value !== "string" || !form.test(value)) return void 0;
6923
+ const ms = Date.parse(value);
6924
+ return Number.isNaN(ms) ? void 0 : ms;
6925
+ }
6926
+ /** `shelfTime` with the catalog's build time already parsed, for the n calls
6927
+ * `orderShelf` makes in one pass. */
6928
+ function timeOf(entry, order, built) {
6929
+ if (order === "stars") return void 0;
6930
+ const ms = order === "added" ? parseForm(entry.added, DAY_FORM) : parseForm(entry.publishedAt, INSTANT_FORM);
6931
+ if (ms === void 0 || built === void 0 || ms > built) return void 0;
6932
+ return ms;
6933
+ }
6934
+ /**
6935
+ * The entry's sort key under a date order, or undefined when it has no usable
6936
+ * date (design §3): absent, off-form, unparseable, or later than the catalog's
6937
+ * own `builtAt`. Milliseconds since the epoch. `'stars'` carries no date, so
6938
+ * this answers undefined for it.
6939
+ */
6940
+ function shelfTime(entry, order, builtAt) {
6941
+ return timeOf(entry, order, parseForm(builtAt, INSTANT_FORM));
6942
+ }
6943
+ /**
6944
+ * A UTC calendar day for display: "2026-10-12". Derived from the instant the
6945
+ * sort ranked by, so a card's label and its position cannot disagree. UTC and
6946
+ * never a local getter: a reader in UTC+8 must see the same days the catalog
6947
+ * was built in, the calendar the stats row already prints.
6948
+ */
6949
+ function utcDay(ms) {
6950
+ return new Date(ms).toISOString().slice(0, 10);
6951
+ }
6952
+ /**
6953
+ * The shelf in `order` (design 2026-10-12-shelf-orders §2, §5). `'stars'`
6954
+ * returns the array it was handed, so the default path allocates nothing and
6955
+ * does not invalidate the memos downstream of it.
6956
+ *
6957
+ * Each key is computed ONCE per entry rather than inside the comparator, and
6958
+ * the sort is stable (ES2019), so entries sharing a date keep the star order
6959
+ * they arrived in — which is how the tie-break is defined without a second
6960
+ * comparator that could drift from `sortByStars`.
6961
+ */
6962
+ function orderShelf(starOrdered, order, builtAt) {
6963
+ if (order === "stars") return starOrdered;
6964
+ const built = parseForm(builtAt, INSTANT_FORM);
6965
+ const keyed = starOrdered.map((entry) => ({
6966
+ entry,
6967
+ time: timeOf(entry, order, built)
6968
+ }));
6969
+ keyed.sort((a, b) => {
6970
+ if (a.time === void 0) return b.time === void 0 ? 0 : 1;
6971
+ if (b.time === void 0) return -1;
6972
+ return b.time - a.time;
6973
+ });
6974
+ return keyed.map((item) => item.entry);
6975
+ }
6828
6976
  /** 999 → "999"; 1000 → "1k"; 1234 → "1.2k"; 1500 → "1.5k"; 99999 → "100k". */
6829
6977
  function formatStars(n) {
6830
6978
  if (n < 1e3) return String(n);
@@ -6897,6 +7045,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6897
7045
  all: "全部",
6898
7046
  catalog: "插件目录",
6899
7047
  catalogStats: "{count} 个插件 · 构建于 {date}",
7048
+ /** The shelf's order control, and the date each card carries under the two
7049
+ * date orders (design 2026-10-12-shelf-orders §4). */
7050
+ sortLabel: "排序",
7051
+ sortStars: "星标",
7052
+ sortAdded: "最新上架",
7053
+ sortUpdated: "最近更新",
7054
+ listedOn: "上架 {date}",
7055
+ updatedOn: "更新 {date}",
6900
7056
  stars: "{count} 星",
6901
7057
  categoryTool: "工具",
6902
7058
  categoryProvider: "模型服务",
@@ -6916,11 +7072,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6916
7072
  archivedBadge: "已归档",
6917
7073
  capabilitiesNote: "作者自述,无强制约束",
6918
7074
  install: "安装",
6919
- confirm: "确认",
6920
7075
  cancel: "取消",
6921
7076
  dismiss: "知道了",
6922
- acknowledgementTitle: "需要确认",
6923
- 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: "收起",
6924
7085
  installing: "正在安装…",
6925
7086
  downloading: "正在下载…",
6926
7087
  installedRestartNotice: "已安装;重启 dsh 后生效",
@@ -6932,8 +7093,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6932
7093
  * "unsupported" leaves the reader guessing which side is off. */
6933
7094
  harnessRangeDetail: "作者声明支持的 dsh 版本:{range};本机运行的是 {running}。",
6934
7095
  harnessProfileDetail: "作者声明支持的 profile:{declared};此 dsh 以 {running} profile 启动。",
6935
- /** dsh's OWN refusal (0.1.7 on), not the author's claim: the install button
6936
- * 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}`
6937
7099
  * is each refused peer with the range the package declared. The remedy
6938
7100
  * leads into the exemption command, rendered on its own line after it, and
6939
7101
  * names the Refresh button by its label. */
@@ -6981,7 +7143,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6981
7143
  emptyIncompatibleFiltered: "匹配的插件都标为不兼容,已被「隐藏不兼容」筛掉。",
6982
7144
  license: "许可",
6983
7145
  customLicense: "自定义许可",
6984
- reviewedVersionLine: "已审核 v{reviewed} / 当前 v{current} 未审核",
7146
+ reviewedVersionLine: "已审核 {reviewed} / 当前 {current} 未审核",
6985
7147
  reviewedPinLine: "已审核 {pin} / 当前 {current} 未审核",
6986
7148
  deniedCode: "已拒绝",
6987
7149
  notInCatalogCode: "不在目录中",
@@ -6995,6 +7157,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6995
7157
  nameTakenDetail: "已安装同名的另一个插件({holder})。\n两个同名插件声明相同的加载项 id,无法共存;安装它会替换掉已装的那个。",
6996
7158
  npmSource: "npm 包",
6997
7159
  githubSource: "源码在 GitHub",
7160
+ /** The accessible name and the tooltip of a card's version, which say which
7161
+ * of three things the printed string is (market borrowings §11): the string
7162
+ * alone cannot tell a release tag from a version number, and a short commit
7163
+ * printed with a `v` read as one. */
7164
+ npmVersionLabel: "npm 版本 {version}",
7165
+ commitVersionLabel: "GitHub 提交 {version}",
7166
+ releaseVersionLabel: "GitHub 发布 {version}",
6998
7167
  /** The badge and the explanation for an installed github entry no catalog
6999
7168
  * row claims (C10 MVP). The mechanism synthesizes a row for ANY github spec
7000
7169
  * with no catalog row — shadowed by a same-named npm package, an unlisted
@@ -7004,8 +7173,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7004
7173
  shadowedBadge: "目录未列出此包",
7005
7174
  shadowedDetail: "此包已安装,但当前的插件目录没有它(可能是被同名 npm 包遮蔽、作者已下架、或目录尚未收录);它照常运行。",
7006
7175
  updatableSection: "可更新",
7007
- installedVersion: "已安装 v{version}",
7008
- latestVersion: "最新 v{version}",
7176
+ /** `{version}` is the shelf's own version text (`versionLabel`), which
7177
+ * carries the `v` an npm version takes and no other kind does. */
7178
+ installedVersion: "已安装 {version}",
7179
+ latestVersion: "最新 {version}",
7009
7180
  update: "更新",
7010
7181
  installed: "已安装",
7011
7182
  uninstall: "卸载",
@@ -7061,6 +7232,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7061
7232
  all: "All",
7062
7233
  catalog: "Plugin catalog",
7063
7234
  catalogStats: "{count} packages · built {date}",
7235
+ /** The shelf's order control, and the date each card carries under the two
7236
+ * date orders (design 2026-10-12-shelf-orders §4). */
7237
+ sortLabel: "Sort",
7238
+ sortStars: "Stars",
7239
+ sortAdded: "Newest",
7240
+ sortUpdated: "Recently updated",
7241
+ listedOn: "Listed {date}",
7242
+ updatedOn: "Updated {date}",
7064
7243
  stars: "{count} stars",
7065
7244
  categoryTool: "Tool",
7066
7245
  categoryProvider: "Provider",
@@ -7080,11 +7259,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7080
7259
  tierCommunity: "Community",
7081
7260
  capabilitiesNote: "self-declared by the author, not enforced",
7082
7261
  install: "Install",
7083
- confirm: "Confirm",
7084
7262
  cancel: "Cancel",
7085
7263
  dismiss: "Dismiss",
7086
- acknowledgementTitle: "Acknowledgement required",
7087
- acknowledgementBody: ACKNOWLEDGEMENT_EN,
7264
+ disclosure: DISCLOSURE_EN,
7265
+ moreRedLines: "Show more",
7266
+ lessRedLines: "Show less",
7088
7267
  installing: "Installing…",
7089
7268
  downloading: "Downloading…",
7090
7269
  installedRestartNotice: "installed; restart dsh to activate",
@@ -7121,7 +7300,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7121
7300
  emptyIncompatibleFiltered: "Every matching plugin is marked incompatible, and the incompatible filter is leaving them out.",
7122
7301
  license: "License",
7123
7302
  customLicense: "Custom license",
7124
- reviewedVersionLine: "reviewed v{reviewed} / current v{current} unreviewed",
7303
+ reviewedVersionLine: "reviewed {reviewed} / current {current} unreviewed",
7125
7304
  reviewedPinLine: "reviewed {pin} / current {current} unreviewed",
7126
7305
  deniedCode: "Denied",
7127
7306
  notInCatalogCode: "Not in the catalog",
@@ -7135,11 +7314,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7135
7314
  nameTakenDetail: "A different plugin of this name is installed ({holder}).\nTwo plugins of one name declare the same loader entry id and cannot both load; installing this one would replace it.",
7136
7315
  npmSource: "npm package",
7137
7316
  githubSource: "source on GitHub",
7317
+ npmVersionLabel: "npm version {version}",
7318
+ commitVersionLabel: "GitHub commit {version}",
7319
+ releaseVersionLabel: "GitHub release {version}",
7138
7320
  shadowedBadge: "Not in the current catalog",
7139
7321
  shadowedDetail: "This package is installed but the current catalog does not list it (it may be shadowed by a same-named npm package, removed by the author, or never listed); it runs as before.",
7140
7322
  updatableSection: "Updatable",
7141
- installedVersion: "installed v{version}",
7142
- latestVersion: "latest v{version}",
7323
+ installedVersion: "installed {version}",
7324
+ latestVersion: "latest {version}",
7143
7325
  update: "Update",
7144
7326
  installed: "Installed",
7145
7327
  uninstall: "Uninstall",
@@ -7693,6 +7875,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7693
7875
  */
7694
7876
  function useKeyedFlows(begin, installStatus, onSettled) {
7695
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());
7696
7880
  const settled = (0, react.useRef)(onSettled);
7697
7881
  settled.current = onSettled;
7698
7882
  const put = (0, react.useCallback)((key, view) => {
@@ -7713,8 +7897,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7713
7897
  });
7714
7898
  }, []);
7715
7899
  const start = (0, react.useCallback)(async (key, args) => {
7716
- put(key, { kind: "idle" });
7717
- 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
+ }
7718
7915
  }, [begin, put]);
7719
7916
  const reset = (0, react.useCallback)((key) => {
7720
7917
  setViews((current) => {
@@ -7778,10 +7975,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7778
7975
  };
7779
7976
  }
7780
7977
  const view = views.get(key) ?? IDLE_VIEW;
7978
+ const isStarting = starting.has(key);
7781
7979
  const cached = flowCache.current.get(key);
7782
- if (cached !== void 0 && cached.view === view) return cached;
7980
+ if (cached !== void 0 && cached.view === view && cached.starting === isStarting) return cached;
7783
7981
  const flow = {
7784
7982
  view,
7983
+ starting: isStarting,
7785
7984
  start: (args) => start(key, args),
7786
7985
  reset: () => reset(key)
7787
7986
  };
@@ -7789,6 +7988,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7789
7988
  return flow;
7790
7989
  }, [
7791
7990
  views,
7991
+ starting,
7792
7992
  start,
7793
7993
  reset
7794
7994
  ]),
@@ -9047,129 +9247,232 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9047
9247
  };
9048
9248
  }
9049
9249
  //#endregion
9050
- //#region \0dsh-plugin-shop-css:e3675a89
9051
- 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.s8583e3_client.s942a79_config.s5f67aa_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.s8583e3_client.sfd9d8a_spec.s5f67aa_ts` pins this. */\n\n.s255e5a_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 `.s232ec4_searchInput` sits at\n * exactly (0, 0) of this box — it is the first child of `.s646509_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.s44a3ec_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.s3a0ece_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.se0c3b4_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s520416_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.s520416_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.s8c7d6f_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.sf5179e_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.sbb0d25_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.s96e910_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.s520416_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 .s520416_skeletonCard span { animation: none; opacity: 0.75; }\n .s520416_skeletonCard::after { content: none; }\n}\n\n.s73242a_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.s73242a_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.s73242a_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s646509_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s745e3c_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.s4fe61f_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.sb9b924_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.sa04455_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.sa04455_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.sa04455_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.s614a38_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.s614a38_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s614a38_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s614a38_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.sa987f7_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.sa987f7_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sa987f7_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.sb81ba5_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.s232ec4_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.s232ec4_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.s76fb13_expansionNote {\n margin: 4px 0 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s3946f1_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.sb2c859_categoryBar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s26d58e_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.s26d58e_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 `.s26d58e_categoryButton.s5f68c4_categoryButtonOn` — TWO classes — so it\n * matches `.s26d58e_categoryButton:hover`'s specificity and, coming after it, wins.\n * As a bare `.s5f68c4_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.s8583e3_client.sfd9d8a_spec.s5f67aa_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.s26d58e_categoryButton.s5f68c4_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 .s26d58e_categoryButton.s5f68c4_categoryButtonOn {\n outline: 2px solid ButtonText;\n outline-offset: -4px;\n }\n}\n\n.s26d58e_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 `.s26d58e_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.sfd887b_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: `.sfd887b_incompatibleFilter:hover .s11c019_switch` outranks `.s7580e8_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.sfd887b_incompatibleFilter:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n.sfd887b_incompatibleFilter:hover .s11c019_switch:not(.s7580e8_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 `.s26d58e_categoryButton.s5f68c4_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.sfd887b_incompatibleFilter.s6e4cba_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.sfd887b_incompatibleFilter .s11c019_switch:not(.s7580e8_switchOn) .saab2b3_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.sfd887b_incompatibleFilter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 999px;\n}\n\n.sebf017_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.seb0a1e_catalogStatsRow {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 6px 0 0;\n}\n\n.sccc4d1_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 `.s73242a_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.sff243d_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.sff243d_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.sff243d_catalogRefreshButton:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.sff243d_catalogRefreshButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s194168_catalogRefreshFailed {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s6de1ab_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.sf657f5_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 .sf657f5_cards { animation: none; }\n}\n\n.s65f383_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.sd7ee01_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s705cc6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s65f383_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 .s65f383_card { transition: none; }\n .s65f383_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.s4ce25d_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. `.s26d58e_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.s65f383_card[data-category='tool'], .s26d58e_categoryButton[data-category='tool'] { --category-hue: #4C8DFF; }\n.s65f383_card[data-category='provider'], .s26d58e_categoryButton[data-category='provider'] { --category-hue: #A78BFA; }\n.s65f383_card[data-category='ui'], .s26d58e_categoryButton[data-category='ui'] { --category-hue: #2DD4BF; }\n.s65f383_card[data-category='workflow'], .s26d58e_categoryButton[data-category='workflow'] { --category-hue: #F59E0B; }\n.s65f383_card[data-category='integration'], .s26d58e_categoryButton[data-category='integration'] { --category-hue: #34D399; }\n.s65f383_card[data-category='theme'], .s26d58e_categoryButton[data-category='theme'] { --category-hue: #F472B6; }\n.s65f383_card[data-category='other'], .s26d58e_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.s9c8eaa_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.sa97234_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.sa97234_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.sc3f149_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.s513026_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.s9b5048_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.s9b5048_cardActions > .sc4f2ac_gate,\n.s9b5048_cardActions > .s4d374c_installPanel,\n.s9b5048_cardActions > .sf53ca0_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.sc6ec6e_cardVersion {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The octocat marking a github-sourced entry, beside its short commit. */\n.s71f10f_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s5e82cb_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s5e82cb_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.s5e82cb_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.s5e82cb_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.s5e82cb_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.s5e82cb_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.s4bbe02_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.sff43b4_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s73f7fb_chevron, .s61393c_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s61393c_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s73f7fb_chevron, .s61393c_chevronOpen { transition: none; }\n}\n\n.s820ebf_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sb8fbc8_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.se507db_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.s10baf0_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sb9dba9_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.s6eca19_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.s3d0044_remedy {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.sdf203f_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.sc32335_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.sc74941_refusal {\n display: flex;\n flex-direction: column;\n gap: 6px;\n flex-basis: 100%;\n}\n\n.s9d2029_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s3bdbd5_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s441702_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s441702_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.sde3f0c_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.se98179_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sfb4d2f_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sfb4d2f_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sfb4d2f_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.sfb4d2f_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sfb4d2f_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sfb4d2f_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 * `.s9b5048_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.s5fbaf9_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: `.s5fbaf9_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.s2d3c05_size {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n letter-spacing: 0.02em;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The publishing account, at the outer end of `.s5fbaf9_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 `.s5fbaf9_cardMeta`. There is no middot:\n * `.s5fbaf9_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.s1ff66f_author {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sd58c6d_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.s4d374c_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.se7aaed_installButton, .sb43a49_confirmButton, .s6e45d1_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.se7aaed_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.se7aaed_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sb43a49_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.sb43a49_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s6e45d1_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.s6e45d1_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.se7aaed_installButton:focus-visible, .sb43a49_confirmButton:focus-visible, .s6e45d1_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.sb6fc74_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.sf53ca0_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.s96967f_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.s96967f_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s96967f_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.sb00c36_restartSelfButton {\n /* The version row's third state. Geometry matches .s614a38_checkUpdateButton and\n * .sa04455_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.sb00c36_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.s6ac237_restartButton,\n.s64a5ff_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.s6ac237_restartButton:hover,\n.s64a5ff_reloadButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s6ac237_restartButton:focus-visible,\n.s64a5ff_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 * `.sf53ca0_installedActions` the way the restart gate is — a column of its own\n * rather than inline with the row. */\n.sf79aef_reloadPanel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n\n.sc4f2ac_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.s559c38_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s3c0ace_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sc323e5_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.s4b2373_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s52b5a5_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s64ab71_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.s1531a8_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf20dda_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.seb9b08_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.sfd3021_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s50ba35_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.se75489_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.sa276cf_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sceae6e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s8d2072_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.s5c2545_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s5c2545_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.s877c68_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 .s513026_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.s6c4649_shadowedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.scc2a21_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.sa1f556_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: `.sfd887b_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 `.s11c019_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.s11c019_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.s7580e8_switchOn {\n border-color: var(--track-hue);\n background: color-mix(in srgb, var(--track-hue) 55%, transparent);\n}\n\n.saab2b3_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.s7580e8_switchOn .saab2b3_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s11c019_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 .s11c019_switch, .saab2b3_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.sd9f9e4_switchWrap {\n display: inline-flex;\n align-items: center;\n /* Same reason as `.sa1f556_outdatedActions`: `.sf79aef_reloadPanel`'s own comment calls\n * itself \"a column of its own rather than inline with the row\", which held\n * for `.sf53ca0_installedActions` and not here until this line. */\n flex-wrap: wrap;\n gap: 8px;\n}\n";
9052
- if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
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\"]")) {
9053
9329
  const tag = document.createElement("style");
9054
- tag.dataset.pluginCss = "e3675a89";
9330
+ tag.dataset.pluginCss = "5f753521";
9055
9331
  tag.textContent = css;
9056
9332
  document.head.appendChild(tag);
9057
9333
  }
9058
- var _dsh_plugin_shop_css_e3675a89_default = {
9059
- "client": "s8583e3_client",
9060
- "config": "s942a79_config",
9061
- "ts": "s5f67aa_ts",
9062
- "spec": "sfd9d8a_spec",
9063
- "panel": "s255e5a_panel",
9064
- "searchInput": "s232ec4_searchInput",
9065
- "toolbar": "s646509_toolbar",
9066
- "stateLine": "s44a3ec_stateLine",
9067
- "srOnly": "s3a0ece_srOnly",
9068
- "skeletonGrid": "se0c3b4_skeletonGrid",
9069
- "skeletonCard": "s520416_skeletonCard",
9070
- "skeletonName": "s8c7d6f_skeletonName",
9071
- "skeletonSummary": "sf5179e_skeletonSummary",
9072
- "skeletonSummaryShort": "sbb0d25_skeletonSummaryShort",
9073
- "skeletonActions": "s96e910_skeletonActions",
9074
- "actionButton": "s73242a_actionButton",
9075
- "toolbarRight": "s745e3c_toolbarRight",
9076
- "versionBlock": "s4fe61f_versionBlock",
9077
- "versionText": "sb9b924_versionText",
9078
- "updateSelfButton": "sa04455_updateSelfButton",
9079
- "checkUpdateButton": "s614a38_checkUpdateButton",
9080
- "githubLink": "sa987f7_githubLink",
9081
- "selfUpdatePanel": "sb81ba5_selfUpdatePanel",
9082
- "expansionNote": "s76fb13_expansionNote",
9083
- "staleBadge": "s3946f1_staleBadge",
9084
- "categoryBar": "sb2c859_categoryBar",
9085
- "categoryButton": "s26d58e_categoryButton",
9086
- "categoryButtonOn": "s5f68c4_categoryButtonOn",
9087
- "incompatibleFilter": "sfd887b_incompatibleFilter",
9088
- "switch": "s11c019_switch",
9089
- "switchOn": "s7580e8_switchOn",
9090
- "incompatibleFilterOn": "s6e4cba_incompatibleFilterOn",
9091
- "switchKnob": "saab2b3_switchKnob",
9092
- "updatableHeading": "sebf017_updatableHeading",
9093
- "catalogStatsRow": "seb0a1e_catalogStatsRow",
9094
- "catalogStats": "sccc4d1_catalogStats",
9095
- "catalogRefreshButton": "sff243d_catalogRefreshButton",
9096
- "catalogRefreshFailed": "s194168_catalogRefreshFailed",
9097
- "emptyLine": "s6de1ab_emptyLine",
9098
- "cards": "sf657f5_cards",
9099
- "card": "s65f383_card",
9100
- "cardsSentry": "sd7ee01_cardsSentry",
9101
- "showingLine": "s705cc6_showingLine",
9102
- "cardSpine": "s4ce25d_cardSpine",
9103
- "categoryBadge": "s9c8eaa_categoryBadge",
9104
- "entryHeader": "sa97234_entryHeader",
9105
- "name": "sc3f149_name",
9106
- "badges": "s513026_badges",
9107
- "cardActions": "s9b5048_cardActions",
9108
- "gate": "sc4f2ac_gate",
9109
- "installPanel": "s4d374c_installPanel",
9110
- "installedActions": "sf53ca0_installedActions",
9111
- "cardVersion": "sc6ec6e_cardVersion",
9112
- "sourceBadge": "s71f10f_sourceBadge",
9113
- "tierBadge": "s5e82cb_tierBadge",
9114
- "incompatibleBadge": "s4bbe02_incompatibleBadge",
9115
- "starsBadge": "sff43b4_starsBadge",
9116
- "chevron": "s73f7fb_chevron",
9117
- "chevronOpen": "s61393c_chevronOpen",
9118
- "body": "s820ebf_body",
9119
- "summary": "sb8fbc8_summary",
9120
- "summaryZh": "se507db_summaryZh",
9121
- "summaryExpanded": "s10baf0_summaryExpanded",
9122
- "summaryZhExpanded": "sb9dba9_summaryZhExpanded",
9123
- "incompatibleDetail": "s6eca19_incompatibleDetail",
9124
- "remedy": "s3d0044_remedy",
9125
- "remedyLead": "sdf203f_remedyLead",
9126
- "allowCommand": "sc32335_allowCommand",
9127
- "refusal": "sc74941_refusal",
9128
- "capabilitiesBlock": "s9d2029_capabilitiesBlock",
9129
- "capabilitiesNote": "s3bdbd5_capabilitiesNote",
9130
- "capabilities": "s441702_capabilities",
9131
- "detail": "sde3f0c_detail",
9132
- "detailRows": "se98179_detailRows",
9133
- "detailRow": "sfb4d2f_detailRow",
9134
- "cardMeta": "s5fbaf9_cardMeta",
9135
- "size": "s2d3c05_size",
9136
- "author": "s1ff66f_author",
9137
- "reviewedLine": "sd58c6d_reviewedLine",
9138
- "installButton": "se7aaed_installButton",
9139
- "confirmButton": "sb43a49_confirmButton",
9140
- "cancelButton": "s6e45d1_cancelButton",
9141
- "installedLabel": "sb6fc74_installedLabel",
9142
- "uninstallButton": "s96967f_uninstallButton",
9143
- "restartSelfButton": "sb00c36_restartSelfButton",
9144
- "restartButton": "s6ac237_restartButton",
9145
- "reloadButton": "s64a5ff_reloadButton",
9146
- "reloadPanel": "sf79aef_reloadPanel",
9147
- "gateTitle": "s559c38_gateTitle",
9148
- "gateBody": "s3c0ace_gateBody",
9149
- "gateWarning": "sc323e5_gateWarning",
9150
- "gateActions": "s4b2373_gateActions",
9151
- "installing": "s52b5a5_installing",
9152
- "log": "s64ab71_log",
9153
- "logLine": "s1531a8_logLine",
9154
- "notice": "sf20dda_notice",
9155
- "failedHeading": "seb9b08_failedHeading",
9156
- "failedDetail": "sfd3021_failedDetail",
9157
- "rejectedCode": "s50ba35_rejectedCode",
9158
- "rejectedDetail": "se75489_rejectedDetail",
9159
- "outdatedSection": "sa276cf_outdatedSection",
9160
- "outdatedList": "sceae6e_outdatedList",
9161
- "outdatedRow": "s8d2072_outdatedRow",
9162
- "outdatedInfo": "s5c2545_outdatedInfo",
9163
- "outdatedVersions": "s877c68_outdatedVersions",
9164
- "shadowedSection": "s6c4649_shadowedSection",
9165
- "shadowedDetail": "scc2a21_shadowedDetail",
9166
- "outdatedActions": "sa1f556_outdatedActions",
9167
- "switchWrap": "sd9f9e4_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"
9168
9452
  };
9169
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
9170
9473
  //#region src/client/ShopTab.tsx
9171
- /** The shop tab: browse the catalog, detail an entry, install with the
9172
- * 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).
9173
9476
  * Everything the tab renders is text — summaries, capabilities, log lines,
9174
9477
  * details — never markup, so hostile npm descriptions cannot inject (spec
9175
9478
  * §11.3.4): no render path here may ever use dangerouslySetInnerHTML. */
@@ -9181,14 +9484,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9181
9484
  * those nodes, and React's next commit throws on the ones it no longer finds
9182
9485
  * (design 2026-09-26-market-borrowings §4). The tab renders no portals, so its
9183
9486
  * root is its whole surface. */
9184
- const PANEL_ROOT = `${_dsh_plugin_shop_css_e3675a89_default.panel} notranslate`;
9487
+ const PANEL_ROOT = `${_dsh_plugin_shop_css_5f753521_default.panel} notranslate`;
9185
9488
  function ChevronIcon({ open }) {
9186
9489
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
9187
9490
  viewBox: "0 0 16 16",
9188
9491
  width: "14",
9189
9492
  height: "14",
9190
9493
  "aria-hidden": "true",
9191
- className: open ? _dsh_plugin_shop_css_e3675a89_default.chevronOpen : _dsh_plugin_shop_css_e3675a89_default.chevron,
9494
+ className: open ? _dsh_plugin_shop_css_5f753521_default.chevronOpen : _dsh_plugin_shop_css_5f753521_default.chevron,
9192
9495
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
9193
9496
  fill: "currentColor",
9194
9497
  d: "M3.5 6 8 10.5 12.5 6l1 1L8 12.5 2.5 7l1-1Z"
@@ -9201,7 +9504,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9201
9504
  * install controls. An installed plugin's card carries its installed row:
9202
9505
  * current → the non-interactive installed label, behind → the update button;
9203
9506
  * uninstalled → the install button. */
9204
- const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, missing, harness, nameTakenBy, t, flow, uninstallFlow, restart, restartBlocked, reload, setEnabled, inInstalledView, restarts }) {
9507
+ const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, order, shelfDay, installed, missing, harness, nameTakenBy, t, flow, uninstallFlow, restart, restartBlocked, reload, setEnabled, inInstalledView, restarts }) {
9205
9508
  const [open, setOpen] = (0, react.useState)(false);
9206
9509
  const blockers = stateBlockers(blockersOf(missing, harness, nameTakenBy), t);
9207
9510
  const detailId = (0, react.useId)();
@@ -9209,6 +9512,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9209
9512
  const npmUrl = npmPageUrl(entry);
9210
9513
  const author = authorOf(entry);
9211
9514
  const size = formatSize(entry.installSize);
9515
+ const version = versionLabel(entry.source, entry.version);
9516
+ const versionName = t(versionLabelKey(version.kind), { version: version.value });
9212
9517
  const category = entry.catalog?.category ?? "other";
9213
9518
  const installTarget = {
9214
9519
  name: entry.name,
@@ -9218,32 +9523,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9218
9523
  subdir: entry.subdir
9219
9524
  };
9220
9525
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9221
- className: _dsh_plugin_shop_css_e3675a89_default.card,
9526
+ className: _dsh_plugin_shop_css_5f753521_default.card,
9222
9527
  "data-shop-entry": entry.name,
9223
9528
  "data-category": category,
9224
9529
  children: [
9225
9530
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9226
- className: _dsh_plugin_shop_css_e3675a89_default.cardSpine,
9531
+ className: _dsh_plugin_shop_css_5f753521_default.cardSpine,
9227
9532
  "aria-hidden": "true"
9228
9533
  }),
9229
9534
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9230
9535
  type: "button",
9231
- className: _dsh_plugin_shop_css_e3675a89_default.entryHeader,
9536
+ className: _dsh_plugin_shop_css_5f753521_default.entryHeader,
9232
9537
  "aria-expanded": open,
9233
9538
  "aria-controls": detailId,
9234
9539
  onClick: () => setOpen((current) => !current),
9235
9540
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9236
- className: _dsh_plugin_shop_css_e3675a89_default.name,
9541
+ className: _dsh_plugin_shop_css_5f753521_default.name,
9237
9542
  children: entry.name
9238
9543
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9239
- className: _dsh_plugin_shop_css_e3675a89_default.badges,
9544
+ className: _dsh_plugin_shop_css_5f753521_default.badges,
9240
9545
  children: [
9241
9546
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9242
- className: _dsh_plugin_shop_css_e3675a89_default.categoryBadge,
9547
+ className: _dsh_plugin_shop_css_5f753521_default.categoryBadge,
9243
9548
  children: t(categoryKey(entry))
9244
9549
  }),
9245
9550
  entry.source === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9246
- className: _dsh_plugin_shop_css_e3675a89_default.sourceBadge,
9551
+ className: _dsh_plugin_shop_css_5f753521_default.sourceBadge,
9247
9552
  "data-shop-source-npm": true,
9248
9553
  role: "img",
9249
9554
  "aria-label": t("npmSource"),
@@ -9260,7 +9565,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9260
9565
  })
9261
9566
  }),
9262
9567
  hasGithubHome(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9263
- className: _dsh_plugin_shop_css_e3675a89_default.sourceBadge,
9568
+ className: _dsh_plugin_shop_css_5f753521_default.sourceBadge,
9264
9569
  "data-shop-source-github": true,
9265
9570
  role: "img",
9266
9571
  "aria-label": t("githubSource"),
@@ -9273,23 +9578,26 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9273
9578
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" })
9274
9579
  })
9275
9580
  }),
9276
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9277
- className: _dsh_plugin_shop_css_e3675a89_default.cardVersion,
9581
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9582
+ className: _dsh_plugin_shop_css_5f753521_default.cardVersion,
9278
9583
  "data-card-version": true,
9279
- children: ["v", displayVersion(entry)]
9584
+ role: "img",
9585
+ "aria-label": versionName,
9586
+ title: versionName,
9587
+ children: version.text
9280
9588
  }),
9281
9589
  entry.tier !== "community" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9282
- className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
9590
+ className: _dsh_plugin_shop_css_5f753521_default.tierBadge,
9283
9591
  "data-tier": entry.tier,
9284
9592
  children: t(tierKey(entry.tier))
9285
9593
  }),
9286
9594
  entry.archived === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9287
- className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
9595
+ className: _dsh_plugin_shop_css_5f753521_default.tierBadge,
9288
9596
  "data-tier": "archived",
9289
9597
  children: t("archivedBadge")
9290
9598
  }),
9291
9599
  stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9292
- className: _dsh_plugin_shop_css_e3675a89_default.starsBadge,
9600
+ className: _dsh_plugin_shop_css_5f753521_default.starsBadge,
9293
9601
  role: "img",
9294
9602
  "aria-label": t("stars", { count: stars }),
9295
9603
  children: ["★ ", formatStars(stars)]
@@ -9299,39 +9607,43 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9299
9607
  })]
9300
9608
  }),
9301
9609
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9302
- className: _dsh_plugin_shop_css_e3675a89_default.body,
9610
+ className: _dsh_plugin_shop_css_5f753521_default.body,
9303
9611
  children: [
9304
9612
  summary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9305
- className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summary} ${_dsh_plugin_shop_css_e3675a89_default.summaryExpanded}` : _dsh_plugin_shop_css_e3675a89_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,
9306
9614
  children: summary.en
9307
9615
  }),
9308
9616
  summary?.zh !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9309
- className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summaryZh} ${_dsh_plugin_shop_css_e3675a89_default.summaryZhExpanded}` : _dsh_plugin_shop_css_e3675a89_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,
9310
9618
  children: summary.zh
9311
9619
  }),
9312
- blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
9313
- blocker,
9314
- className: _dsh_plugin_shop_css_e3675a89_default.incompatibleDetail,
9315
- surface: "detail"
9316
- }, 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
+ }),
9317
9629
  open && entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9318
- className: _dsh_plugin_shop_css_e3675a89_default.capabilitiesBlock,
9630
+ className: _dsh_plugin_shop_css_5f753521_default.capabilitiesBlock,
9319
9631
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9320
- className: _dsh_plugin_shop_css_e3675a89_default.capabilitiesNote,
9632
+ className: _dsh_plugin_shop_css_5f753521_default.capabilitiesNote,
9321
9633
  children: t("capabilitiesNote")
9322
9634
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
9323
- className: _dsh_plugin_shop_css_e3675a89_default.capabilities,
9635
+ className: _dsh_plugin_shop_css_5f753521_default.capabilities,
9324
9636
  children: entry.catalog.capabilities.map((capability) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: capability }, capability))
9325
9637
  })]
9326
9638
  }),
9327
9639
  open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
9328
9640
  id: detailId,
9329
- className: _dsh_plugin_shop_css_e3675a89_default.detail,
9641
+ className: _dsh_plugin_shop_css_5f753521_default.detail,
9330
9642
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
9331
- className: _dsh_plugin_shop_css_e3675a89_default.detailRows,
9643
+ className: _dsh_plugin_shop_css_5f753521_default.detailRows,
9332
9644
  children: [
9333
9645
  npmUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9334
- className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
9646
+ className: _dsh_plugin_shop_css_5f753521_default.detailRow,
9335
9647
  "data-shop-npm": true,
9336
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", {
9337
9649
  href: npmUrl,
@@ -9341,7 +9653,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9341
9653
  }) })]
9342
9654
  }),
9343
9655
  entry.repository !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9344
- className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
9656
+ className: _dsh_plugin_shop_css_5f753521_default.detailRow,
9345
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", {
9346
9658
  href: entry.repository,
9347
9659
  target: "_blank",
@@ -9350,25 +9662,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9350
9662
  }) : entry.repository })]
9351
9663
  }),
9352
9664
  entry.license !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9353
- className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
9665
+ className: _dsh_plugin_shop_css_5f753521_default.detailRow,
9354
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 })]
9355
9667
  })
9356
9668
  ]
9357
9669
  }), entry.tier === "verified-stale" && entry.review !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9358
- className: _dsh_plugin_shop_css_e3675a89_default.reviewedLine,
9670
+ className: _dsh_plugin_shop_css_5f753521_default.reviewedLine,
9359
9671
  children: entry.review.reviewedVersion !== void 0 ? t("reviewedVersionLine", {
9360
- reviewed: entry.review.reviewedVersion,
9361
- current: entry.version
9672
+ reviewed: versionLabel("npm", entry.review.reviewedVersion).text,
9673
+ current: version.text
9362
9674
  }) : t("reviewedPinLine", {
9363
9675
  pin: reviewHashPin(entry.review),
9364
- current: displayVersion(entry)
9676
+ current: version.text
9365
9677
  })
9366
9678
  })]
9367
9679
  })
9368
9680
  ]
9369
9681
  }),
9370
9682
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9371
- className: _dsh_plugin_shop_css_e3675a89_default.cardActions,
9683
+ className: _dsh_plugin_shop_css_5f753521_default.cardActions,
9372
9684
  "data-shop-actions": true,
9373
9685
  children: [installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [!inInstalledView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
9374
9686
  target: installTarget,
@@ -9400,7 +9712,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9400
9712
  restartBlocked,
9401
9713
  reload
9402
9714
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9403
- className: _dsh_plugin_shop_css_e3675a89_default.installedLabel,
9715
+ className: _dsh_plugin_shop_css_5f753521_default.installedLabel,
9404
9716
  "data-shop-installed": true,
9405
9717
  children: t("installed")
9406
9718
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
@@ -9424,20 +9736,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9424
9736
  reload,
9425
9737
  flow: uninstallFlow
9426
9738
  })
9427
- ] }), (size !== void 0 || author !== null) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9428
- className: _dsh_plugin_shop_css_e3675a89_default.cardMeta,
9429
- children: [size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9430
- className: _dsh_plugin_shop_css_e3675a89_default.size,
9431
- "data-shop-size": true,
9432
- role: "img",
9433
- "aria-label": t("sizeLabel", { size }),
9434
- title: t("sizeLabel", { size }),
9435
- children: size
9436
- }), author !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9437
- className: _dsh_plugin_shop_css_e3675a89_default.author,
9438
- "data-shop-author": true,
9439
- children: t("authorLine", { author })
9440
- })]
9739
+ ] }), (size !== void 0 || author !== null || shelfDay !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9740
+ className: _dsh_plugin_shop_css_5f753521_default.cardMeta,
9741
+ children: [
9742
+ shelfDay !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9743
+ className: _dsh_plugin_shop_css_5f753521_default.shelfDate,
9744
+ "data-shop-shelf-date": true,
9745
+ children: t(order === "added" ? "listedOn" : "updatedOn", { date: shelfDay })
9746
+ }),
9747
+ size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9748
+ className: _dsh_plugin_shop_css_5f753521_default.size,
9749
+ "data-shop-size": true,
9750
+ role: "img",
9751
+ "aria-label": t("sizeLabel", { size }),
9752
+ title: t("sizeLabel", { size }),
9753
+ children: size
9754
+ }),
9755
+ author !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9756
+ className: _dsh_plugin_shop_css_5f753521_default.author,
9757
+ "data-shop-author": true,
9758
+ children: t("authorLine", { author })
9759
+ })
9760
+ ]
9441
9761
  })]
9442
9762
  })
9443
9763
  ]
@@ -9483,13 +9803,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9483
9803
  ...attribute,
9484
9804
  children: blocker.text
9485
9805
  }), blocker.remedy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9486
- className: _dsh_plugin_shop_css_e3675a89_default.remedy,
9806
+ className: _dsh_plugin_shop_css_5f753521_default.remedy,
9487
9807
  "data-shop-remedy": blocker.kind,
9488
9808
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9489
- className: _dsh_plugin_shop_css_e3675a89_default.remedyLead,
9809
+ className: _dsh_plugin_shop_css_5f753521_default.remedyLead,
9490
9810
  children: blocker.remedy.lead
9491
9811
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
9492
- className: _dsh_plugin_shop_css_e3675a89_default.allowCommand,
9812
+ className: _dsh_plugin_shop_css_5f753521_default.allowCommand,
9493
9813
  "data-shop-allow-command": true,
9494
9814
  children: blocker.remedy.command
9495
9815
  })]
@@ -9527,7 +9847,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9527
9847
  if (lead === void 0 || word === null) return null;
9528
9848
  const detail = blockers.map((blocker) => blocker.text).join("\n");
9529
9849
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9530
- className: _dsh_plugin_shop_css_e3675a89_default.incompatibleBadge,
9850
+ className: _dsh_plugin_shop_css_5f753521_default.incompatibleBadge,
9531
9851
  "data-shop-blocker": lead.kind,
9532
9852
  role: "img",
9533
9853
  "aria-label": detail,
@@ -9535,46 +9855,53 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9535
9855
  children: t(word)
9536
9856
  });
9537
9857
  }
9538
- /** One entry's install flow: the button, the §9.3 acknowledgement gate for
9539
- * community-tier entries, and the live view — running log, restart notice,
9540
- * failure detail, rejection detail — driven by `useInstallFlows`. Shared by the
9541
- * catalog cards (`variant: 'install'`) and the outdated rows' update button
9542
- * (`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. */
9543
9871
  function InstallPanel({ target, tier, blockers, blockersStated = false, variant = "install", flow, t, restart, restartBlocked, reload }) {
9544
- const [gateOpen, setGateOpen] = (0, react.useState)(false);
9545
- const { view, start } = flow;
9872
+ const { view, start, starting } = flow;
9546
9873
  if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9547
- className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
9874
+ className: _dsh_plugin_shop_css_5f753521_default.installPanel,
9548
9875
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9549
- className: _dsh_plugin_shop_css_e3675a89_default.installing,
9876
+ className: _dsh_plugin_shop_css_5f753521_default.installing,
9550
9877
  children: t(installPhaseKey(view.phase))
9551
9878
  }), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9552
- className: _dsh_plugin_shop_css_e3675a89_default.log,
9879
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9553
9880
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9554
- className: _dsh_plugin_shop_css_e3675a89_default.logLine,
9881
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9555
9882
  "data-shop-log-line": true,
9556
9883
  children: line
9557
9884
  }, index))
9558
9885
  })]
9559
9886
  });
9560
9887
  if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9561
- className: _dsh_plugin_shop_css_e3675a89_default.installedActions,
9888
+ className: _dsh_plugin_shop_css_5f753521_default.installedActions,
9562
9889
  children: [
9563
9890
  view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9564
- className: _dsh_plugin_shop_css_e3675a89_default.log,
9891
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9565
9892
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9566
- className: _dsh_plugin_shop_css_e3675a89_default.logLine,
9893
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9567
9894
  "data-shop-log-line": true,
9568
9895
  children: line
9569
9896
  }, index))
9570
9897
  }),
9571
9898
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9572
- className: _dsh_plugin_shop_css_e3675a89_default.notice,
9899
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
9573
9900
  "data-shop-restart-notice": true,
9574
9901
  children: t(activationNoticeKey(view.activation, view.restartReason))
9575
9902
  }),
9576
9903
  view.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9577
- className: _dsh_plugin_shop_css_e3675a89_default.notice,
9904
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
9578
9905
  "data-shop-done-note": true,
9579
9906
  children: view.detail
9580
9907
  }),
@@ -9589,103 +9916,81 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9589
9916
  ]
9590
9917
  });
9591
9918
  if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9592
- className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
9919
+ className: _dsh_plugin_shop_css_5f753521_default.installPanel,
9593
9920
  children: [
9594
9921
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9595
- className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
9922
+ className: _dsh_plugin_shop_css_5f753521_default.failedHeading,
9596
9923
  children: t("installFailed")
9597
9924
  }),
9598
9925
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9599
- className: _dsh_plugin_shop_css_e3675a89_default.log,
9926
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9600
9927
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9601
- className: _dsh_plugin_shop_css_e3675a89_default.logLine,
9928
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9602
9929
  "data-shop-log-line": true,
9603
9930
  children: line
9604
9931
  }, index))
9605
9932
  }),
9606
9933
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9607
- className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
9934
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
9608
9935
  children: view.detail === "" ? t("installTransportFailed") : view.detail
9609
9936
  })
9610
9937
  ]
9611
9938
  });
9612
9939
  if (view.kind === "rejected") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9613
- className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
9940
+ className: _dsh_plugin_shop_css_5f753521_default.installPanel,
9614
9941
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9615
- className: _dsh_plugin_shop_css_e3675a89_default.rejectedCode,
9942
+ className: _dsh_plugin_shop_css_5f753521_default.rejectedCode,
9616
9943
  children: t(rejectionCodeKey(view.code))
9617
9944
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9618
- className: _dsh_plugin_shop_css_e3675a89_default.rejectedDetail,
9945
+ className: _dsh_plugin_shop_css_5f753521_default.rejectedDetail,
9619
9946
  children: view.detail
9620
9947
  })]
9621
9948
  });
9622
- if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9623
- className: _dsh_plugin_shop_css_e3675a89_default.gate,
9624
- children: [
9625
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9626
- className: _dsh_plugin_shop_css_e3675a89_default.gateTitle,
9627
- children: t("acknowledgementTitle")
9628
- }),
9629
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9630
- className: _dsh_plugin_shop_css_e3675a89_default.gateBody,
9631
- children: t("acknowledgementBody")
9632
- }),
9633
- !blockersStated && blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
9634
- blocker,
9635
- className: _dsh_plugin_shop_css_e3675a89_default.gateWarning,
9636
- surface: "warning"
9637
- }, blocker.kind)),
9638
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9639
- className: _dsh_plugin_shop_css_e3675a89_default.gateActions,
9640
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9641
- type: "button",
9642
- className: _dsh_plugin_shop_css_e3675a89_default.confirmButton,
9643
- "data-shop-confirm": true,
9644
- onClick: () => {
9645
- setGateOpen(false);
9646
- start({
9647
- ...target,
9648
- acknowledged: true
9649
- });
9650
- },
9651
- children: t("confirm")
9652
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9653
- type: "button",
9654
- className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
9655
- onClick: () => setGateOpen(false),
9656
- children: t("cancel")
9657
- })]
9658
- })
9659
- ]
9660
- });
9661
9949
  const update = variant === "update";
9662
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
+ })] });
9663
9970
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
9664
9971
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9665
9972
  type: "button",
9666
- className: _dsh_plugin_shop_css_e3675a89_default.installButton,
9667
- disabled: refused,
9668
- ...refused ? { "data-shop-refused": true } : {},
9973
+ className: _dsh_plugin_shop_css_5f753521_default.installButton,
9974
+ disabled: starting,
9669
9975
  ...update ? { "data-shop-update": true } : { "data-shop-install": true },
9670
9976
  onClick: () => {
9671
- if (tier === "verified") start({
9977
+ start({
9672
9978
  ...target,
9673
- acknowledged: void 0
9979
+ acknowledged: tier === "verified" ? void 0 : true
9674
9980
  });
9675
- else setGateOpen(true);
9676
9981
  },
9677
9982
  children: t(update ? "update" : "install")
9678
9983
  }),
9679
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
9984
+ blockers.length > 0 && !refused && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
9680
9985
  blockers,
9681
9986
  t
9682
9987
  }),
9683
- refused && !blockersStated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9684
- className: _dsh_plugin_shop_css_e3675a89_default.refusal,
9685
- "data-shop-refusal": true,
9988
+ !blockersStated && blockers.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FoldableRedBlock, {
9989
+ t,
9990
+ attrName: "data-shop-blocker-lines",
9686
9991
  children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
9687
9992
  blocker,
9688
- className: _dsh_plugin_shop_css_e3675a89_default.gateWarning,
9993
+ className: _dsh_plugin_shop_css_5f753521_default.gateWarning,
9689
9994
  surface: "warning"
9690
9995
  }, blocker.kind))
9691
9996
  })
@@ -9700,32 +10005,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9700
10005
  function UninstallPanel({ name, t, restart, restartBlocked, reload, flow }) {
9701
10006
  const { view, start, reset } = flow;
9702
10007
  if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9703
- className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
10008
+ className: _dsh_plugin_shop_css_5f753521_default.installPanel,
9704
10009
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9705
- className: _dsh_plugin_shop_css_e3675a89_default.installing,
10010
+ className: _dsh_plugin_shop_css_5f753521_default.installing,
9706
10011
  children: t("uninstalling")
9707
10012
  }), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9708
- className: _dsh_plugin_shop_css_e3675a89_default.log,
10013
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9709
10014
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9710
- className: _dsh_plugin_shop_css_e3675a89_default.logLine,
10015
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9711
10016
  "data-shop-log-line": true,
9712
10017
  children: line
9713
10018
  }, index))
9714
10019
  })]
9715
10020
  });
9716
10021
  if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9717
- className: _dsh_plugin_shop_css_e3675a89_default.installedActions,
10022
+ className: _dsh_plugin_shop_css_5f753521_default.installedActions,
9718
10023
  children: [
9719
10024
  view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9720
- className: _dsh_plugin_shop_css_e3675a89_default.log,
10025
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9721
10026
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9722
- className: _dsh_plugin_shop_css_e3675a89_default.logLine,
10027
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9723
10028
  "data-shop-log-line": true,
9724
10029
  children: line
9725
10030
  }, index))
9726
10031
  }),
9727
10032
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9728
- className: _dsh_plugin_shop_css_e3675a89_default.notice,
10033
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
9729
10034
  "data-shop-uninstall-done": true,
9730
10035
  children: t(uninstallActivationNoticeKey(view.activation))
9731
10036
  }),
@@ -9739,7 +10044,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9739
10044
  }),
9740
10045
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9741
10046
  type: "button",
9742
- className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
10047
+ className: _dsh_plugin_shop_css_5f753521_default.cancelButton,
9743
10048
  "data-shop-uninstall-dismiss": true,
9744
10049
  onClick: reset,
9745
10050
  children: t("dismiss")
@@ -9747,27 +10052,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9747
10052
  ]
9748
10053
  });
9749
10054
  if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9750
- className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
10055
+ className: _dsh_plugin_shop_css_5f753521_default.installPanel,
9751
10056
  children: [
9752
10057
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9753
- className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
10058
+ className: _dsh_plugin_shop_css_5f753521_default.failedHeading,
9754
10059
  children: t("uninstallFailed")
9755
10060
  }),
9756
10061
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9757
- className: _dsh_plugin_shop_css_e3675a89_default.log,
10062
+ className: _dsh_plugin_shop_css_5f753521_default.log,
9758
10063
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9759
- className: _dsh_plugin_shop_css_e3675a89_default.logLine,
10064
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
9760
10065
  "data-shop-log-line": true,
9761
10066
  children: line
9762
10067
  }, index))
9763
10068
  }),
9764
10069
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9765
- className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10070
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
9766
10071
  children: view.detail === "" ? t("uninstallTransportFailed") : view.detail
9767
10072
  }),
9768
10073
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9769
10074
  type: "button",
9770
- className: _dsh_plugin_shop_css_e3675a89_default.uninstallButton,
10075
+ className: _dsh_plugin_shop_css_5f753521_default.uninstallButton,
9771
10076
  "data-shop-uninstall-retry": true,
9772
10077
  onClick: () => void start({ name }),
9773
10078
  children: t("retry")
@@ -9777,7 +10082,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9777
10082
  if (view.kind !== "idle") return null;
9778
10083
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9779
10084
  type: "button",
9780
- className: _dsh_plugin_shop_css_e3675a89_default.uninstallButton,
10085
+ className: _dsh_plugin_shop_css_5f753521_default.uninstallButton,
9781
10086
  "data-shop-uninstall": true,
9782
10087
  onClick: () => void start({ name }),
9783
10088
  children: t("uninstall")
@@ -9818,38 +10123,39 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9818
10123
  else if (outcome.kind === "unreachable") setRefusal(t("restartTransportFailed"));
9819
10124
  };
9820
10125
  if (monitor?.kind === "requesting" || monitor?.kind === "restarting") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9821
- className: _dsh_plugin_shop_css_e3675a89_default.notice,
10126
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
9822
10127
  "data-shop-restarting": true,
9823
10128
  children: t("restarting")
9824
10129
  });
9825
10130
  if (monitor?.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9826
- className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10131
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
9827
10132
  "data-shop-restart-error": true,
9828
10133
  children: monitor.logFile === void 0 ? t("restartFailedNotice") : t("restartFailedLogNotice", { log: monitor.logFile })
9829
10134
  });
9830
10135
  if (monitor?.kind === "not-restarted") return null;
9831
10136
  if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9832
- className: _dsh_plugin_shop_css_e3675a89_default.gate,
10137
+ className: _dsh_plugin_shop_css_5f753521_default.gate,
10138
+ "data-shop-restart-gate": true,
9833
10139
  children: [
9834
10140
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9835
- className: _dsh_plugin_shop_css_e3675a89_default.gateTitle,
10141
+ className: _dsh_plugin_shop_css_5f753521_default.gateTitle,
9836
10142
  children: t("restartTitle")
9837
10143
  }),
9838
10144
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9839
- className: _dsh_plugin_shop_css_e3675a89_default.gateBody,
10145
+ className: _dsh_plugin_shop_css_5f753521_default.gateBody,
9840
10146
  children: t("restartBody")
9841
10147
  }),
9842
10148
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9843
- className: _dsh_plugin_shop_css_e3675a89_default.gateActions,
10149
+ className: _dsh_plugin_shop_css_5f753521_default.gateActions,
9844
10150
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9845
10151
  type: "button",
9846
- className: _dsh_plugin_shop_css_e3675a89_default.confirmButton,
10152
+ className: _dsh_plugin_shop_css_5f753521_default.confirmButton,
9847
10153
  "data-shop-restart-confirm": true,
9848
10154
  onClick: () => void onConfirm(),
9849
10155
  children: t("restartConfirm")
9850
10156
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9851
10157
  type: "button",
9852
- className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
10158
+ className: _dsh_plugin_shop_css_5f753521_default.cancelButton,
9853
10159
  onClick: () => setGateOpen(false),
9854
10160
  children: t("cancel")
9855
10161
  })]
@@ -9857,14 +10163,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9857
10163
  ]
9858
10164
  });
9859
10165
  const refused = refusal === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9860
- className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10166
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
9861
10167
  "data-shop-restart-error": true,
9862
10168
  children: refusal
9863
10169
  });
9864
10170
  if (gate !== void 0) return refused;
9865
10171
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refused, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9866
10172
  type: "button",
9867
- className: _dsh_plugin_shop_css_e3675a89_default.restartButton,
10173
+ className: _dsh_plugin_shop_css_5f753521_default.restartButton,
9868
10174
  "data-shop-restart": true,
9869
10175
  onClick: () => setGateOpen(true),
9870
10176
  children: t("restart")
@@ -9882,13 +10188,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9882
10188
  */
9883
10189
  function NotRestartedNotice({ t, pid, logFile }) {
9884
10190
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9885
- className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
10191
+ className: _dsh_plugin_shop_css_5f753521_default.selfUpdatePanel,
9886
10192
  "data-shop-not-restarted": true,
9887
10193
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9888
- className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10194
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
9889
10195
  children: pid === void 0 ? t("notRestartedNotice") : t("notRestartedPidNotice", { pid: String(pid) })
9890
10196
  }), logFile !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9891
- className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10197
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
9892
10198
  children: t("notRestartedLogNotice", { log: logFile })
9893
10199
  })]
9894
10200
  });
@@ -9918,7 +10224,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9918
10224
  restart,
9919
10225
  reload
9920
10226
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9921
- className: _dsh_plugin_shop_css_e3675a89_default.notice,
10227
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
9922
10228
  "data-shop-restart-disabled": true,
9923
10229
  children: t(restartBlockedNoticeKey(restartBlocked))
9924
10230
  });
@@ -9929,14 +10235,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9929
10235
  * would help without knowing that now is a good time. */
9930
10236
  function ReloadPanel({ t, reload, where }) {
9931
10237
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9932
- className: _dsh_plugin_shop_css_e3675a89_default.reloadPanel,
10238
+ className: _dsh_plugin_shop_css_5f753521_default.reloadPanel,
9933
10239
  "data-shop-reload": where,
9934
10240
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9935
- className: _dsh_plugin_shop_css_e3675a89_default.notice,
10241
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
9936
10242
  children: t("reloadNote")
9937
10243
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9938
10244
  type: "button",
9939
- className: _dsh_plugin_shop_css_e3675a89_default.reloadButton,
10245
+ className: _dsh_plugin_shop_css_5f753521_default.reloadButton,
9940
10246
  onClick: reload,
9941
10247
  children: t("reload")
9942
10248
  })]
@@ -9987,7 +10293,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9987
10293
  }
9988
10294
  };
9989
10295
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9990
- className: _dsh_plugin_shop_css_e3675a89_default.switchWrap,
10296
+ className: _dsh_plugin_shop_css_5f753521_default.switchWrap,
9991
10297
  "data-shop-enabled-switch": row.name,
9992
10298
  children: [
9993
10299
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -9996,18 +10302,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9996
10302
  "aria-checked": enabled,
9997
10303
  "aria-label": t("enabledSwitch"),
9998
10304
  "data-shop-toggle": true,
9999
- className: `${_dsh_plugin_shop_css_e3675a89_default.switch} ${enabled ? _dsh_plugin_shop_css_e3675a89_default.switchOn : ""}`,
10305
+ className: `${_dsh_plugin_shop_css_5f753521_default.switch} ${enabled ? _dsh_plugin_shop_css_5f753521_default.switchOn : ""}`,
10000
10306
  onClick: () => void onToggle(),
10001
10307
  disabled: toggle.kind === "saving",
10002
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.switchKnob })
10308
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_5f753521_default.switchKnob })
10003
10309
  }),
10004
10310
  toggle.kind === "saved" && owed === "nothing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10005
- className: _dsh_plugin_shop_css_e3675a89_default.notice,
10311
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10006
10312
  "data-shop-hot-apply": true,
10007
10313
  children: t("hotApplyNote")
10008
10314
  }),
10009
10315
  owed === "restart" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10010
- className: _dsh_plugin_shop_css_e3675a89_default.notice,
10316
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10011
10317
  "data-shop-toggle-restart": true,
10012
10318
  children: t("toggleRestartNote")
10013
10319
  }),
@@ -10020,7 +10326,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10020
10326
  where: "toggle"
10021
10327
  }),
10022
10328
  toggle.kind === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10023
- className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10329
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
10024
10330
  "data-shop-toggle-error": true,
10025
10331
  children: toggle.detail
10026
10332
  })
@@ -10035,19 +10341,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10035
10341
  * field is narrowed at the parameter. */
10036
10342
  function OutdatedRow({ row, tier, missing, harness, t, setEnabled, flowFor, restart, restartBlocked, reload, restarts }) {
10037
10343
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10038
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedRow,
10344
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedRow,
10039
10345
  "data-shop-outdated-entry": row.name,
10040
10346
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10041
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedInfo,
10347
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedInfo,
10042
10348
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10043
- className: _dsh_plugin_shop_css_e3675a89_default.name,
10349
+ className: _dsh_plugin_shop_css_5f753521_default.name,
10044
10350
  children: row.name
10045
10351
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10046
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersions,
10047
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("installedVersion", { version: row.source === "github" ? row.installed.slice(0, 7) : row.installed }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("latestVersion", { version: row.latest }) })]
10352
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedVersions,
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 }) })]
10048
10354
  })]
10049
10355
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10050
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedActions,
10356
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedActions,
10051
10357
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
10052
10358
  row,
10053
10359
  t,
@@ -10090,28 +10396,30 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10090
10396
  * "Installed" until 2026-09-16, which was the Installed filter's label
10091
10397
  * character for character in both languages, over a strict subset of what
10092
10398
  * that filter selects. Internals stay `outdated` — the Host field name. The
10093
- * tier for the update gate is looked up from the catalog by install IDENTITY,
10094
- * never by name (community → acknowledgement); an entry absent from the
10095
- * 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. */
10096
10404
  function OutdatedSection({ state, entriesByKey, missingByKey, harnessVerdicts, t, setEnabled, flowFor, restart, restartBlocked, reload, restarts }) {
10097
10405
  if (state.kind === "loading") return null;
10098
10406
  if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10099
- className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
10407
+ className: _dsh_plugin_shop_css_5f753521_default.stateLine,
10100
10408
  "data-shop-outdated-error": true,
10101
10409
  children: t("installedError")
10102
10410
  });
10103
10411
  const outdated = state.entries.filter((entry) => entry.outdated && entry.latest !== null);
10104
10412
  if (outdated.length === 0) return null;
10105
10413
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
10106
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedSection,
10414
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedSection,
10107
10415
  "data-shop-outdated": true,
10108
10416
  "aria-labelledby": UPDATABLE_HEADING_ID,
10109
10417
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
10110
- className: _dsh_plugin_shop_css_e3675a89_default.updatableHeading,
10418
+ className: _dsh_plugin_shop_css_5f753521_default.updatableHeading,
10111
10419
  id: UPDATABLE_HEADING_ID,
10112
10420
  children: t("updatableSection")
10113
10421
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10114
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedList,
10422
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedList,
10115
10423
  children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
10116
10424
  row,
10117
10425
  tier: entriesByKey.get(identityKey(row))?.tier ?? "community",
@@ -10139,37 +10447,37 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10139
10447
  const shadowed = entries.filter((entry) => entry.shadowed === true);
10140
10448
  if (shadowed.length === 0) return null;
10141
10449
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
10142
- className: _dsh_plugin_shop_css_e3675a89_default.shadowedSection,
10450
+ className: _dsh_plugin_shop_css_5f753521_default.shadowedSection,
10143
10451
  "data-shop-shadowed": true,
10144
10452
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10145
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedList,
10453
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedList,
10146
10454
  children: shadowed.map((row) => {
10147
10455
  const key = identityKey(row);
10148
10456
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10149
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedRow,
10457
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedRow,
10150
10458
  "data-shop-shadowed-entry": row.name,
10151
10459
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10152
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedInfo,
10460
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedInfo,
10153
10461
  children: [
10154
10462
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10155
- className: _dsh_plugin_shop_css_e3675a89_default.name,
10463
+ className: _dsh_plugin_shop_css_5f753521_default.name,
10156
10464
  children: row.name
10157
10465
  }),
10158
10466
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10159
- className: _dsh_plugin_shop_css_e3675a89_default.badges,
10467
+ className: _dsh_plugin_shop_css_5f753521_default.badges,
10160
10468
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10161
- className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
10469
+ className: _dsh_plugin_shop_css_5f753521_default.tierBadge,
10162
10470
  "data-tier": "shadowed",
10163
10471
  children: t("shadowedBadge")
10164
10472
  })
10165
10473
  }),
10166
10474
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10167
- className: _dsh_plugin_shop_css_e3675a89_default.shadowedDetail,
10475
+ className: _dsh_plugin_shop_css_5f753521_default.shadowedDetail,
10168
10476
  children: t("shadowedDetail")
10169
10477
  })
10170
10478
  ]
10171
10479
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10172
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedActions,
10480
+ className: _dsh_plugin_shop_css_5f753521_default.outdatedActions,
10173
10481
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
10174
10482
  name: row.name,
10175
10483
  t,
@@ -10219,16 +10527,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10219
10527
  role: "alert",
10220
10528
  children: [
10221
10529
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10222
- className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
10530
+ className: _dsh_plugin_shop_css_5f753521_default.stateLine,
10223
10531
  children: say("tabCrashed", en.tabCrashed)
10224
10532
  }),
10225
10533
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10226
- className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10534
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
10227
10535
  children: error.message
10228
10536
  }),
10229
10537
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10230
10538
  type: "button",
10231
- className: _dsh_plugin_shop_css_e3675a89_default.actionButton,
10539
+ className: _dsh_plugin_shop_css_5f753521_default.actionButton,
10232
10540
  onClick: () => this.setState({ error: null }),
10233
10541
  children: say("retry", en.retry)
10234
10542
  })
@@ -10315,11 +10623,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10315
10623
  const [reloadFailed, setReloadFailed] = (0, react.useState)(false);
10316
10624
  const [query, setQuery] = (0, react.useState)("");
10317
10625
  const [category, setCategory] = (0, react.useState)(null);
10626
+ const [order, setOrder] = (0, react.useState)("stars");
10318
10627
  const [hideIncompatible, setHideIncompatible] = (0, react.useState)(false);
10319
10628
  const incremental = typeof IntersectionObserver !== "undefined";
10320
10629
  const [visibleCount, setVisibleCount] = (0, react.useState)(48);
10321
10630
  const sentinelRef = (0, react.useRef)(null);
10322
10631
  const filteredLenRef = (0, react.useRef)(0);
10632
+ const sortLabelId = (0, react.useId)();
10323
10633
  const pendingRefreshRef = (0, react.useRef)(null);
10324
10634
  (0, react.useEffect)(() => {
10325
10635
  let cancelled = false;
@@ -10414,7 +10724,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10414
10724
  return map;
10415
10725
  }, [installedState]);
10416
10726
  const stars = (0, react.useMemo)(() => catalogState.kind === "ready" ? catalogState.result.stars : {}, [catalogState]);
10417
- const sortedCatalog = (0, react.useMemo)(() => catalogState.kind === "ready" ? sortByStars(catalogState.result.plugins, stars) : [], [catalogState, stars]);
10727
+ const starOrder = (0, react.useMemo)(() => catalogState.kind === "ready" ? sortByStars(catalogState.result.plugins, stars) : [], [catalogState, stars]);
10728
+ const builtAt = catalogState.kind === "ready" ? catalogState.result.builtAt : "";
10729
+ const sortedCatalog = (0, react.useMemo)(() => orderShelf(starOrder, order, builtAt), [
10730
+ starOrder,
10731
+ order,
10732
+ builtAt
10733
+ ]);
10418
10734
  const browsable = (0, react.useMemo)(() => {
10419
10735
  if (catalogState.kind !== "ready") return [];
10420
10736
  const cleared = new Set(catalogState.result.notAShop ?? []);
@@ -10487,6 +10803,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10487
10803
  filtered.length
10488
10804
  ]);
10489
10805
  const visible = incremental ? filtered.slice(0, visibleCount) : filtered;
10806
+ /** The date a card shows under the current order, or undefined under the
10807
+ * star order and for an entry with no usable date. Computed from the same
10808
+ * `shelfTime` the order keyed on, so a label can never name a date the
10809
+ * order ignored. */
10810
+ const shelfDayOf = (entry) => {
10811
+ const ms = shelfTime(entry, order, builtAt);
10812
+ return ms === void 0 ? void 0 : utcDay(ms);
10813
+ };
10490
10814
  const categoryCounts = (0, react.useMemo)(() => {
10491
10815
  const counts = /* @__PURE__ */ new Map();
10492
10816
  for (const entry of browsable) {
@@ -10509,19 +10833,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10509
10833
  "data-shop-tab": true,
10510
10834
  "aria-busy": "true",
10511
10835
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10512
- className: _dsh_plugin_shop_css_e3675a89_default.srOnly,
10836
+ className: _dsh_plugin_shop_css_5f753521_default.srOnly,
10513
10837
  children: t("loading")
10514
10838
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10515
- className: _dsh_plugin_shop_css_e3675a89_default.skeletonGrid,
10839
+ className: _dsh_plugin_shop_css_5f753521_default.skeletonGrid,
10516
10840
  "data-shop-skeleton": true,
10517
10841
  "aria-hidden": "true",
10518
10842
  children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10519
- className: _dsh_plugin_shop_css_e3675a89_default.skeletonCard,
10843
+ className: _dsh_plugin_shop_css_5f753521_default.skeletonCard,
10520
10844
  children: [
10521
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonName }),
10522
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonSummary }),
10523
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonSummaryShort }),
10524
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_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 })
10525
10849
  ]
10526
10850
  }, i))
10527
10851
  })]
@@ -10531,11 +10855,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10531
10855
  translate: "no",
10532
10856
  "data-shop-tab": true,
10533
10857
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10534
- className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
10858
+ className: _dsh_plugin_shop_css_5f753521_default.stateLine,
10535
10859
  children: t("error")
10536
10860
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10537
10861
  type: "button",
10538
- className: _dsh_plugin_shop_css_e3675a89_default.actionButton,
10862
+ className: _dsh_plugin_shop_css_5f753521_default.actionButton,
10539
10863
  onClick: () => {
10540
10864
  setRequest({ kind: "retry" });
10541
10865
  setVersionReload((current) => current + 1);
@@ -10550,11 +10874,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10550
10874
  "data-shop-tab": true,
10551
10875
  children: [
10552
10876
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10553
- className: _dsh_plugin_shop_css_e3675a89_default.toolbar,
10877
+ className: _dsh_plugin_shop_css_5f753521_default.toolbar,
10554
10878
  children: [
10555
10879
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
10556
10880
  type: "search",
10557
- className: _dsh_plugin_shop_css_e3675a89_default.searchInput,
10881
+ className: _dsh_plugin_shop_css_5f753521_default.searchInput,
10558
10882
  "aria-label": t("search"),
10559
10883
  placeholder: t("search"),
10560
10884
  value: query,
@@ -10565,31 +10889,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10565
10889
  }),
10566
10890
  expansionMemo.intents.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10567
10891
  "data-shop-expansion-note": true,
10568
- className: _dsh_plugin_shop_css_e3675a89_default.expansionNote,
10892
+ className: _dsh_plugin_shop_css_5f753521_default.expansionNote,
10569
10893
  children: t("zhExpansionNote", { terms: expansionMemo.terms.slice(1).map((term) => term.startsWith("#") ? term.slice(1) : term).join(", ") })
10570
10894
  }),
10571
10895
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10572
- className: _dsh_plugin_shop_css_e3675a89_default.toolbarRight,
10896
+ className: _dsh_plugin_shop_css_5f753521_default.toolbarRight,
10573
10897
  children: [
10574
10898
  result.stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10575
- className: _dsh_plugin_shop_css_e3675a89_default.staleBadge,
10899
+ className: _dsh_plugin_shop_css_5f753521_default.staleBadge,
10576
10900
  children: t("staleLabel", { date: result.builtAt.slice(0, 10) })
10577
10901
  }),
10578
10902
  selfVersion !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10579
- className: _dsh_plugin_shop_css_e3675a89_default.versionBlock,
10903
+ className: _dsh_plugin_shop_css_5f753521_default.versionBlock,
10580
10904
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10581
- className: _dsh_plugin_shop_css_e3675a89_default.versionText,
10905
+ className: _dsh_plugin_shop_css_5f753521_default.versionText,
10582
10906
  "data-shop-version": true,
10583
10907
  children: ["v", selfVersion.installed]
10584
10908
  }), restartMonitor !== null ? null : selfPending && restartBlocked === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10585
10909
  type: "button",
10586
- className: _dsh_plugin_shop_css_e3675a89_default.restartSelfButton,
10910
+ className: _dsh_plugin_shop_css_5f753521_default.restartSelfButton,
10587
10911
  "data-shop-restart": true,
10588
10912
  onClick: () => setSelfRestartGate(true),
10589
10913
  children: t("restart")
10590
10914
  }) : !selfPending && selfVersion.outdated && selfVersion.latest !== null && selfUpdate.view.kind === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10591
10915
  type: "button",
10592
- className: _dsh_plugin_shop_css_e3675a89_default.updateSelfButton,
10916
+ className: _dsh_plugin_shop_css_5f753521_default.updateSelfButton,
10593
10917
  "data-shop-update-self": true,
10594
10918
  onClick: () => {
10595
10919
  if (selfVersion.latest !== null) selfUpdate.start({ version: selfVersion.latest });
@@ -10597,7 +10921,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10597
10921
  children: t("update")
10598
10922
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10599
10923
  type: "button",
10600
- className: _dsh_plugin_shop_css_e3675a89_default.checkUpdateButton,
10924
+ className: _dsh_plugin_shop_css_5f753521_default.checkUpdateButton,
10601
10925
  "data-shop-check-update": true,
10602
10926
  title: t("checkUpdateTitle"),
10603
10927
  disabled: checkState !== "idle",
@@ -10606,7 +10930,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10606
10930
  })]
10607
10931
  }),
10608
10932
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
10609
- className: _dsh_plugin_shop_css_e3675a89_default.githubLink,
10933
+ className: _dsh_plugin_shop_css_5f753521_default.githubLink,
10610
10934
  href: SHOP_REPO_URL,
10611
10935
  target: "_blank",
10612
10936
  rel: "noopener noreferrer",
@@ -10631,28 +10955,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10631
10955
  logFile: restartMonitor.logFile
10632
10956
  }),
10633
10957
  selfUpdate.view.kind === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10634
- className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
10958
+ className: _dsh_plugin_shop_css_5f753521_default.selfUpdatePanel,
10635
10959
  "data-shop-self-updating": true,
10636
10960
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10637
- className: _dsh_plugin_shop_css_e3675a89_default.installing,
10961
+ className: _dsh_plugin_shop_css_5f753521_default.installing,
10638
10962
  children: t(installPhaseKey(selfUpdate.view.phase))
10639
10963
  }), selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10640
- className: _dsh_plugin_shop_css_e3675a89_default.log,
10964
+ className: _dsh_plugin_shop_css_5f753521_default.log,
10641
10965
  children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10642
- className: _dsh_plugin_shop_css_e3675a89_default.logLine,
10966
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
10643
10967
  "data-shop-log-line": true,
10644
10968
  children: line
10645
10969
  }, index))
10646
10970
  })]
10647
10971
  }),
10648
10972
  selfPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10649
- className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
10973
+ className: _dsh_plugin_shop_css_5f753521_default.selfUpdatePanel,
10650
10974
  "data-shop-self-update-done": true,
10651
10975
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10652
- className: _dsh_plugin_shop_css_e3675a89_default.installedActions,
10976
+ className: _dsh_plugin_shop_css_5f753521_default.installedActions,
10653
10977
  children: [
10654
10978
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10655
- className: _dsh_plugin_shop_css_e3675a89_default.notice,
10979
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10656
10980
  children: t("installedRestartNotice")
10657
10981
  }),
10658
10982
  restartBlocked === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
@@ -10665,7 +10989,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10665
10989
  }
10666
10990
  }),
10667
10991
  restartBlocked !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10668
- className: _dsh_plugin_shop_css_e3675a89_default.notice,
10992
+ className: _dsh_plugin_shop_css_5f753521_default.notice,
10669
10993
  "data-shop-restart-disabled": true,
10670
10994
  children: t(restartBlockedNoticeKey(restartBlocked))
10671
10995
  })
@@ -10673,35 +10997,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10673
10997
  })
10674
10998
  }),
10675
10999
  selfUpdate.view.kind === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10676
- className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
11000
+ className: _dsh_plugin_shop_css_5f753521_default.selfUpdatePanel,
10677
11001
  "data-shop-self-update-failed": true,
10678
11002
  children: [
10679
11003
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10680
- className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
11004
+ className: _dsh_plugin_shop_css_5f753521_default.failedHeading,
10681
11005
  children: t("updateFailed")
10682
11006
  }),
10683
11007
  selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10684
- className: _dsh_plugin_shop_css_e3675a89_default.log,
11008
+ className: _dsh_plugin_shop_css_5f753521_default.log,
10685
11009
  children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10686
- className: _dsh_plugin_shop_css_e3675a89_default.logLine,
11010
+ className: _dsh_plugin_shop_css_5f753521_default.logLine,
10687
11011
  "data-shop-log-line": true,
10688
11012
  children: line
10689
11013
  }, index))
10690
11014
  }),
10691
11015
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10692
- className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
11016
+ className: _dsh_plugin_shop_css_5f753521_default.failedDetail,
10693
11017
  children: selfUpdate.view.detail === "" ? t("updateTransportFailed") : selfUpdate.view.detail
10694
11018
  })
10695
11019
  ]
10696
11020
  }),
10697
11021
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10698
- className: _dsh_plugin_shop_css_e3675a89_default.categoryBar,
11022
+ className: _dsh_plugin_shop_css_5f753521_default.categoryBar,
10699
11023
  role: "group",
10700
11024
  "aria-label": t("catalog"),
10701
11025
  children: [
10702
11026
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10703
11027
  type: "button",
10704
- className: category === null ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_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,
10705
11029
  "aria-pressed": category === null,
10706
11030
  onClick: () => {
10707
11031
  setCategory(null);
@@ -10716,7 +11040,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10716
11040
  }),
10717
11041
  CATEGORY_ORDER.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10718
11042
  type: "button",
10719
- className: category === key ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_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,
10720
11044
  "aria-pressed": category === key,
10721
11045
  "data-category": key,
10722
11046
  "data-shop-category-tab": key,
@@ -10732,7 +11056,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10732
11056
  }, key)),
10733
11057
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10734
11058
  type: "button",
10735
- className: category === "installed" ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_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,
10736
11060
  "aria-pressed": category === "installed",
10737
11061
  "data-shop-category-installed": true,
10738
11062
  onClick: () => {
@@ -10749,7 +11073,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10749
11073
  type: "button",
10750
11074
  role: "switch",
10751
11075
  "aria-checked": hideIncompatible,
10752
- className: hideIncompatible ? `${_dsh_plugin_shop_css_e3675a89_default.incompatibleFilter} ${_dsh_plugin_shop_css_e3675a89_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_e3675a89_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,
10753
11077
  title: t(hideIncompatible ? "showIncompatibleTitle" : "incompatibleFilterTitle"),
10754
11078
  "data-shop-hide-incompatible": true,
10755
11079
  onClick: () => {
@@ -10757,18 +11081,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10757
11081
  setVisibleCount(48);
10758
11082
  },
10759
11083
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10760
- className: hideIncompatible ? `${_dsh_plugin_shop_css_e3675a89_default.switch} ${_dsh_plugin_shop_css_e3675a89_default.switchOn}` : _dsh_plugin_shop_css_e3675a89_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,
10761
11085
  "aria-hidden": "true",
10762
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.switchKnob })
11086
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_5f753521_default.switchKnob })
10763
11087
  }), t("hideIncompatible", { count: incompatibleCount })]
10764
11088
  })
10765
11089
  ]
10766
11090
  }),
10767
11091
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10768
- className: _dsh_plugin_shop_css_e3675a89_default.catalogStatsRow,
11092
+ className: _dsh_plugin_shop_css_5f753521_default.catalogStatsRow,
10769
11093
  children: [
10770
11094
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10771
- className: _dsh_plugin_shop_css_e3675a89_default.catalogStats,
11095
+ className: _dsh_plugin_shop_css_5f753521_default.catalogStats,
10772
11096
  "data-shop-catalog-stats": true,
10773
11097
  children: t("catalogStats", {
10774
11098
  count: String(browsable.length),
@@ -10777,7 +11101,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10777
11101
  }),
10778
11102
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10779
11103
  type: "button",
10780
- className: _dsh_plugin_shop_css_e3675a89_default.catalogRefreshButton,
11104
+ className: _dsh_plugin_shop_css_5f753521_default.catalogRefreshButton,
10781
11105
  "data-shop-catalog-refresh": true,
10782
11106
  disabled: reloading,
10783
11107
  onClick: () => {
@@ -10789,26 +11113,54 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10789
11113
  children: reloading ? t("refreshing") : t("refresh")
10790
11114
  }),
10791
11115
  reloadFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10792
- className: _dsh_plugin_shop_css_e3675a89_default.catalogRefreshFailed,
11116
+ className: _dsh_plugin_shop_css_5f753521_default.catalogRefreshFailed,
10793
11117
  "data-shop-catalog-refresh-failed": true,
10794
11118
  children: t("refreshFailed")
11119
+ }),
11120
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11121
+ className: _dsh_plugin_shop_css_5f753521_default.sortGroup,
11122
+ role: "group",
11123
+ "aria-labelledby": sortLabelId,
11124
+ "data-shop-sort": true,
11125
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11126
+ className: _dsh_plugin_shop_css_5f753521_default.sortLabel,
11127
+ id: sortLabelId,
11128
+ children: t("sortLabel")
11129
+ }), SHELF_ORDERS.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11130
+ type: "button",
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,
11132
+ "aria-pressed": order === name,
11133
+ "data-shop-order": name,
11134
+ onClick: () => {
11135
+ setOrder(name);
11136
+ setVisibleCount(48);
11137
+ },
11138
+ children: t(shelfOrderLocaleKey(name))
11139
+ }, name))]
10795
11140
  })
10796
11141
  ]
10797
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
+ }),
10798
11148
  result.plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10799
- className: _dsh_plugin_shop_css_e3675a89_default.emptyLine,
11149
+ className: _dsh_plugin_shop_css_5f753521_default.emptyLine,
10800
11150
  children: t("empty")
10801
11151
  }) : filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10802
- className: _dsh_plugin_shop_css_e3675a89_default.emptyLine,
11152
+ className: _dsh_plugin_shop_css_5f753521_default.emptyLine,
10803
11153
  "data-shop-empty": true,
10804
11154
  children: matched.length > 0 ? t("emptyIncompatibleFiltered") : t("emptySearch")
10805
11155
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
10806
- className: _dsh_plugin_shop_css_e3675a89_default.cards,
11156
+ className: _dsh_plugin_shop_css_5f753521_default.cards,
10807
11157
  children: [visible.map((entry) => {
10808
11158
  const key = entryKey(entry);
10809
11159
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
10810
11160
  entry,
10811
11161
  stars: starsOf(entry, stars),
11162
+ order,
11163
+ shelfDay: shelfDayOf(entry),
10812
11164
  installed: installedByKey.get(key),
10813
11165
  missing: missingByKey.get(key) ?? [],
10814
11166
  harness: harnessVerdictOf(harnessVerdicts, key),
@@ -10825,12 +11177,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10825
11177
  }) }, key);
10826
11178
  }), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
10827
11179
  ref: sentinelRef,
10828
- className: _dsh_plugin_shop_css_e3675a89_default.cardsSentry,
11180
+ className: _dsh_plugin_shop_css_5f753521_default.cardsSentry,
10829
11181
  "data-shop-sentry": true,
10830
11182
  "aria-hidden": "true"
10831
11183
  })]
10832
11184
  }), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10833
- className: _dsh_plugin_shop_css_e3675a89_default.showingLine,
11185
+ className: _dsh_plugin_shop_css_5f753521_default.showingLine,
10834
11186
  "aria-live": "polite",
10835
11187
  children: t("showing", {
10836
11188
  shown: String(visibleCount),